领先一步
VMware 提供培训和认证,助你快速提升。
了解更多在过去几个月里,我一直与各种客户合作开展使用 Google Web Toolkit [GWT] 的项目。我喜欢 GWT,主要是因为它提供了 Java 到 javascript 的编译器。这是让普通 Java 开发人员无需学习新语言即可创建 RIA 的关键。
我一直以来都是测试驱动开发(TDD)的拥趸,令我失望的是,乍一看,GWT 和 TDD 似乎无法协同工作。
测试 GWT 代码有些问题。核心问题在于 GWT 代码在运行前被编译成 javascript。在许多情况下,GWT.create() 语句用于连接动态绑定机制。在普通的 Java 环境中执行时,这个语句会导致异常。
即使你使用像 EasyMock 这样的模拟库来模拟出导致问题的部分,如果它是在构造函数中触发的,你仍然可能遇到问题。为所有 widget 创建接口开销太大了,而且即使你这样做了,你也无法测试包含 GWT.create() 的特定类。
GWT 在 GWTTestCase 中提供了这个问题的解决方案,但这个类本身也存在不少问题。仅举几例:
当你进行测试驱动开发时,你需要能够编写至少具有以下特性的测试:
那么,我们该怎么做才能避免因为缺乏测试导致代码变成“面条”,同时尽量减少那些缓慢的 GWTTestCases 呢? MVC 来拯救!
MVC 是一个久经考验的模式。它有很多应用,其中一些除了名字之外与原始 MVC 模式几乎没有什么共同点,但我想指出的总体方向最好由 Martin Fowler 在 Humble View(谦逊视图)这个名字下总结。
我更简短的总结是这样的:视图通常很难测试,因此它们应该知道得越少越好,做得也越少越好。在 GWT 中,视图等同于 Widget。这里有一个小问题:任何在客户端运行的东西都由 Widget 拥有。大多数 GWT 代码几乎把所有的逻辑都放在了 widget 中,所以大多数使用 GWT 的开发者都通过继承一个 widget 然后添加一些逻辑来完成工作。
我的建议很简单:不要那样做。类型安全对于获得 IDE 支持来说很好,但它并不能阻止你写出糟糕的代码。所以如果你不做单元测试,你的代码最终会变得一团糟。
如果你不把逻辑放在视图(Widget 的子类)中,你需要另一个地方来放它。这就是 Controller 发挥作用的地方。回到我之前提到的小问题,我们将需要从 Widget 或 EntryPoint 中引导 Controller。这确实是无法避免的,所以我们就这样做,看看它看起来有多糟。
public class NoteEditor extends Composite {
public NoteEditor() {
//do the dependency injection stuff
NoteModel noteModel = new NoteModel();
NoteEditorController controller =
new NoteEditorController(noteModel, NoteService.App.getInstance(), new AlertCallback());
//...
}
}
如你所见,我在这里进行了一些代码中的依赖注入,因为客户端还没有 Spring。我说“还没有”,是因为之后我们可以为此使用 GWToolbox 或 Rocket。服务通过 GWT.create() 来引导,而 AlertCallback 用于将 Controller 与 Window 解耦,以便处理偶尔的弹窗。我觉得这还不算太糟,不为此代码写测试我也能睡个好觉。问题还没有完全解决,因为我们想在视图中使用的任何元素(按钮、标签等)都需要在视图中实例化,然后注册到 Controller 中。
controller.registerDetailViewSelector(new DeckPanelSelector(detailView));
detailView.addStyleName("detailPanel");
main.add(detailView, DockPanel.CENTER);
//some buttons at the bottom of the screen
buttonPanel.add((Widget)controller.registerClearButton(new Button("Clear")));
buttonPanel.add((Widget)controller.registerLoadButton(new Button("Load existing Note")));
Controller 通过它们的接口 (SourcesClickEvents) 接受按钮,这带来的好处是我们可以用外观不同的 Widget 替换按钮,而无需修改 Controller。这没什么新鲜的,这正是 MVC 所倡导的关注点分离。老实说,我通常更喜欢写一个测试来检查注册是否正确发生,但这是没有 GWTTesCase 就无法做到的。现在是时候让我们的 IDE 为我们创建那个 Controller + 方法,并编写测试,这样我们就可以实现逻辑了。例如,加载按钮的测试看起来是这样的:
public void testLoadButtonPressed_success() throws Exception {
final Foo expectedFoo = new Foo("expected");
fooServiceMock.loadFoo(isA(String.class), isA(AsyncCallback.class));
expectLastCall().andAnswer(new IAnswer() {
public Object answer() throws Throwable {
((AsyncCallback) getCurrentArguments()[1]).onSuccess(expectedFoo);
return null;
}
});
fooModelMock.setFoo(expectedFoo);
replay(allMocks);
loadButton.fireClick();
verify(allMocks);
}
然后就可以开始了!我附上了一个快速示例来展示这个想法。你可以将其用作实验的起点。
更新于 2008 年 10 月:示例源码已过时。总体建议仍然有效。
总结一下 GWT 风格的 MVC 模式的快速概览。但当然你可以选择自己的风格,只要你将逻辑保存在可测试的类中。