领先一步
VMware提供培训和认证,以加速你的进步。
了解更多在过去的几个月里,我一直在与不同的客户合作,参与使用Google Web Toolkit [GWT]的项目。我主要喜欢GWT是因为它的Java到Javascript编译器。这是关键,让普通的Java开发者能够创建RIA,而无需学习新的语言。
我一直以来都是测试驱动开发的粉丝,令我失望的是,起初看起来TDD和GWT似乎无法兼容。
测试GWT代码有点棘手。问题的核心在于GWT代码在运行前被编译成Javascript。在很多情况下,使用GWT.create()语句来连接动态绑定机制。在普通的Java环境中执行此语句会导致异常。
即使你使用EasyMock之类的模拟库来模拟罪魁祸首,如果它例如是从构造函数触发的,你仍然可能遇到这个问题。为所有小部件创建接口工作量太大,即使你这样做了,你也无法测试包含GWT.create()的特定类。
GWT在GWTTestCase中为此问题提供了解决方案,但这个类本身也有一些问题。仅举几例:
当你进行测试驱动开发时,你需要能够编写至少具有以下特性的测试:
那么,我们如何才能防止代码因缺乏测试而变成意大利面条代码,同时又将那些缓慢的GWTTestCases降到最低呢?**MVC来救援**
MVC是一个久经考验的模式。它有很多应用,其中一些与原始MVC模式除了名称外几乎没有任何共同之处,但我想要指出的总体方向最好由Martin Fowler用“Humble View”(谦逊的视图)来概括。
我更简短的总结是这样的:视图通常很难测试,因此它们应该尽可能少地了解和做。在GWT中,视图与小部件同义。现在这里有一个小小的陷阱:在客户端运行的任何东西都由小部件拥有。大多数GWT代码几乎所有的逻辑都在小部件中,因此大多数使用GWT的开发人员扩展一个小部件并只添加一些额外的逻辑。
我的建议很简单:不要那样做。类型安全有助于获得IDE支持,但它并不能阻止你编写糟糕的代码。所以,如果你不进行单元测试,你的代码最终会一团糟。
如果你不走将逻辑放入你的视图(小部件子类)的道路,你需要另一个地方来放置它。这就是控制器发挥作用的地方。回到我之前提到的陷阱,我们需要从小部件或EntryPoint引导控制器。这实际上是无法避免的,所以让我们这样做,看看它看起来有多糟糕。
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用于将控制器与Window解耦,以便偶尔发出警报。我觉得这还不错,我可以轻松地入睡而无需为此代码编写测试。麻烦还没有完全结束,因为我们想要在视图中使用的任何元素(按钮、标签等)都需要在视图中实例化,然后向控制器注册。
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")));
控制器通过它们的接口(SourcesClickEvents)接受按钮,这作为一个额外的好处,允许我们将按钮替换为具有不同外观的小部件,而无需更改我们的控制器。这里没有什么新的,这正是MVC关注点分离的重点。说实话,我通常更喜欢编写一个测试来检查注册是否正确完成,但这在没有GWTTesCase的情况下是无法完成的。现在是时候让我们的IDE为我们创建控制器+方法并编写测试,以便我们可以实现逻辑了。例如,加载按钮的测试看起来像这样:
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模式的简要概述。但是,你当然可以选择你自己的风格,只要你将你的逻辑放在可测试的类中。