gwt 测试
GWT是Google开发的框架,用于使用Java编程语言构建支持AJAX的Web应用程序。 它包括:
- 用于创建GUI的API(类似于Swing),用于处理Web浏览器的文档对象模型(DOM)。
- Java到JavaScript的编译器。
- 用于运行和调试GWT应用程序的环境。
这种方法具有一些优点:
- 假设您了解Java,则无需学习新的编程语言。
- 您可以构建支持AJAX的Web应用程序,而无需编写JavaScript或HTML。
- 一切都是用Java编写的,因此可以使用Java的高级开发工具,包括调试和重构支持。
- GWT使开发人员免受浏览器特性的影响
最后,由于所有内容都在Java中(甚至MVC模式的View部分),我们应该能够轻松创建UI测试。 本文探讨了一些方法。
1.第一次尝试
1.1使用TDD的 GWT
我们将从一个简单的示例开始:我们要显示一个文本输入字段,一个按钮和一个标签。 当我们单击按钮时,用户输入的文本字段内容将放置在标签中。 相应的测试(在JUnit 4中 )将是:
1 @Test
2public void testClick () {
3 GwtExample view = new GwtExample();
4 Assert . assertNull (view. getLabel (). getText());
5 view. getTextBox().setText ("my text");
6 // creation of a basic "click" event
7 NativeEvent event = Document. get().createClickEvent (0, 0, 0, 0, 0, false, false, false, false); // dispatch de l'évènement DomEvent.fireNativeEvent(event, view.getButton());
8 Assert . assertEquals ("my text", view. getLabel().getText());
9 }
然后,我们可以编写相应的View代码:
1public class GwtExample extends Composite {
2 private Label label;
3 private TextBox textBox;
4 private Button button;
5
6 public GwtExample() {
7 FlowPanel fp = new FlowPanel();
8 textBox = new TextBox();
9 fp.add(textBox);
10 button = new Button (" validate ");
11 fp.add(button);
12 button.addClickHandler( new ClickHandler() {
13
14 public void onClick(ClickEvent event) {
15 label.setText(textBox.getText());
16
17 }
18 });
19 label = new Label("init");
20 fp.add(label);
21 initWidget(fp);
22 }
23
24 public Label getLabel() { return label; }
25 public TextBox getTextBox() { return textBox; }
26 public Button getButton() { return button; }
27 }
最后,我们启动前面的JUnit测试:
1 java.lang . ExceptionInInitializerError ...
2 Caused by: java. lang . UnsupportedOperationException : ERROR: GWT. create () is only usable in
3 client code! It cannot be called, for example, from server code. If you are running a unit
4 test, check that your test case extends GWTTestCase and that GWT. create () is not called
5 from within an initializer or constructor.
6 at com. google.gwt.core.client.GWT.create (GWT. java : 85 )
7 at com. google.gwt.user.client.ui.UIObject .(UIObject. java : 140 )
8 ... 23 more
上面的错误意味着在标准JVM中不使用GWT类。 它们只有在将它们编译为JavaScript并在浏览器中执行后才能工作。
1.2一些现有的解决方案
1.2.1 GWTTestCase
GWT提供了一个用于执行单元测试的类,称为GWTTestCase,但是它受到许多限制:
- 本机事件的管理仅从GWT 1.6开始实施。 GWTTestCase不允许按照以前的版本以有效的方式测试View部件。
- 缓慢。 实际上,此类启动了一个屏蔽的HostedMode(GWT的开发环境),这意味着它需要几秒钟的时间来初始化自身。
- 查找文件。 单元测试必须由GWT编译。 因此,必须由MyApp.gwt.xml文件引用它们。 这使应用程序的启动和打包变得相当复杂,尤其是使用Maven时 。
此外,使用GWTTestCase和GWTTestSuite极大地限制了对Java API的访问:单元测试必须与GWT编译器兼容(该编译器负责将Java UI代码编译为JavaScript)。 这意味着,例如,不可能使用Java反射。 因此,无法使用Unitils或Easymock等测试库。
Java类JavaScript中模拟名单可在这里 。
GWT 2.0对GWTTestCase进行了改进,因为该类不再使用本机库来运行测试。 使用HtmlUnit代替托管模式使用的浏览器。 在GWT 2中,GWTTestCase与平台无关。 但是GWTTestCase仍然存在一些局限性:测试执行缓慢,并且不可能使用标准的测试框架。
1.2.2使用界面
测试GWT应用程序的一种解决方案是在测试时不使用GWT对象,而是将所有GWT对象替换为可在标准JVM中工作的模拟对象。 然而,该解决方案带来了很大的不便。 由于GWT对象不是接口,而是具体的类,因此我们将必须修改应用程序的代码,使其使用接口(您可以在此处找到示例) 。 该解决方案影响我们应用程序的设计。
1.3问题的核心
为了前进,我们调查了Google为什么在标准JVM中阻止GWT类的执行。 原因很简单:这些类的大部分代码使用JSNI代码。 JSNI代码以以下方式显示:
1public static native void alert( String msg) /*-{ $wnd.alert(msg); }-*/;
这是本机功能。 这意味着在执行期间,JVM将尝试执行与DLL(或.so)同名的函数。 将这些类编译为JavaScript后,该方法将替换为/ *和* /中的代码。 这就是我们无法使用非JavaScript在标准JVM中执行的原因。
此外,部分GWT行为不是用Java实现,而是用JavaScript(或浏览器HTML呈现系统)实现。 即使我们成功解决了本机方法的问题,我们也必须找到解决方案以重新实现此行为。
2. Gwt-Test-Utils框架
2.1目的
我们有效实现GWT测试的目标如下:
- 我们的测试课程不需要任何烦人的加载时间。
- 我们应该能够直接操作GWT类,而无需使项目变得更加复杂的中间接口。
- 我们应该能够使用所有Java标准API,特别是自省Java API(以使用诸如Unitils之类的工具)。
- 我们想要一些轻巧且与Maven兼容的东西。
2.2“ gwt-test-utils”框架
在客户项目期间,我们开发了一个框架来满足我们的目标。
我们设计了一个测试框架来修改GWT类,而无需开发人员进行任何其他工作。 它从对GWT对象的类的字节码的“热”修改开始,以用Java方法替换本机JSNI方法,如下图所示:
注意:框架技术实现的介绍不属于本文范围。 我们将专注于其用法。
我们已将此框架作为一个开源项目Gwt-Test-Utils发布 ,以便任何人都可以使用它。
2.3使用框架
我们将从编写一个简单的Junit 4测试开始,以验证GWT按钮的创建:
@Testpublic void checkText() {
Button b = new Button ();
b. setText (" toto ");
Assert . assertEquals ("toto", b. getText ());
}
如前所述,这样的测试会产生错误:
1 java.lang . ExceptionInInitializerError ...
2 Caused by: java. lang . UnsupportedOperationException : ERROR: GWT. create() is only usable in
3 client code! It cannot be called, for example, from server code. If you are running a unit
4 test, check that your test case extends GWTTestCase and that GWT. create() is not called
5 from within an initializer or constructor.
6 at com. google.gwt.core.client.GWT.create (GWT. java : 85 )
7 at com. google.gwt.user.client.ui.UIObject .(UIObject. java : 140 )
8 ... 23 more
为了使Gwt-Test-Utils能够修改GWT类的字节码,必须使用我们专门开发的Java代理执行测试。 因此,我们必须在JVM启动命令中添加一个新参数:-javaagent:path_to_bootstrap.jar。
之后,我们必须在测试代码中安装“ Gwt-Test-Utils”:
1 @Before
2public static void setUpClass() throws Exception {
3 // patch GWT standard components
4 PatchGWT.init();
5 }
6
现在可以验证测试:Gwt-Test-Utils即时替换GWT类的字节码。 这样,将不会启动GWT HostedMode,并且执行时间约为几毫秒。 我们可以使用所有标准工具。
例如,我们可以使用Easymock测试对GWT-RPC服务的调用:
1static interface MyRemoteService extends RemoteService {
2 String myMethod( String param1);
3 }
4
5 static class MyGwtClass {
6 public String myValue;
7
8 public void run() {
9 MyRemoteServiceAsync service = GWT.create(MyRemoteService. class );
10 service.myMethod(" myParamValue ", new AsyncCallback
gt;() {
11
public
void onFailure(
Throwable caught) {myValue = "
error ";}
12
public
void onSuccess(
String result) {myValue = result;}
13 });
14 }
15 }
16
17 @Mock
18
private MyRemoteServiceAsync mockedService;
19
20 @Test
21
public
void checkGwtRpcOk() {
22
// Setup
23
24
// mock remote call 25 mockedService.myMethod(EasyMock.eq("
myParamValue "), EasyMock.isA(AsyncCallback.
class ));
26 expectServiceAndCallbackOnSuccess("
returnValue ");
27
28 replay();
29
30
// Test
31 MyGwtClass gwtClass =
new MyGwtClass();
32 gwtClass.myValue = "
toto ";
33
Assert .assertEquals("
toto ", gwtClass.myValue);
34 gwtClass.run();
35
36
// Assert 37 verify(); 38 39
Assert .assertEquals("returnValue", gwtClass.myValue);
40 }
注意:@Mock注释类似于我们在 Unitils中 可以找到的 注释 。 它用于声明模拟对象。
2.4此框架的约束和非约束
- 无需更改/重新开发GWT应用程序的设计即可使其可测试。
- 您需要通过添加参数-javaagent:path_to_bootstrap.jar来修改这些单元测试的启动命令。 这必须在IDE设置和/或Maven配置(在surefire插件配置中)中完成。
- 必须使用Java 6 JVM来执行测试(Java 5 JVM不允许您修改本机方法的代码)。 通过更改JRE执行,使用Eclipse很容易。 使用Maven,您只需要更改surefire插件使用的JVM。
这些限制并不是微不足道的,但是我们认为测试框架的优势胜过了它们。
有关Maven配置的完整示例,请参见Gwt-Test-Utils demo1项目 。
2.5结果
在我们的项目中(使用JRockit 1.5编译的26k行GWT应用程序,在Hotspot 1.6下进行了测试),我们完成了600%的单元测试(14k行测试代码),覆盖了85%的代码。 但是,我们将测试集中在GWT应用程序的控制器部分,其目的不是要重新测试GWT,而是要验证我们已实现的行为。
3整合测试
3.1第一限制
Gwt-Test-Utils框架允许我们有效地测试UI。 这些测试的弱点在于它们是单元测试:我们正在测试单个视图的行为。 服务器部分(接收GWT-RPC调用)被模拟。 我们遇到的大多数问题都在视图链接上,视图中有很多GWT-RPC调用。
3.2编写集成测试
在我们的例子中,服务器后端使用Spring。 因此,我们使用SpringJUnit4ClassRunner对其进行测试,它将在JUnit下为我们启动整个服务器后端。 因此,通过添加一些粘合剂,我们“封闭了循环”:我们没有模拟GWT应用程序服务器后端,而是将UI部分连接到服务器后端。
因此,GWT应用程序及其服务器已完全在唯一的JVM中启动并运行,并准备执行测试。 例如,我们可以编写一个测试方案,其中:
- 启动服务器后端
- 启动GWT应用程序(只需调用其EntryPoint)
- 刺激GWT应用程序,它将调用服务器后端
- 通过意见
这些测试不再是单元测试。 它们是真正的集成测试。
3.3实践
服务器端无需修改。 我们仅添加了一些粘合代码即可将GWT应用程序连接到Spring部件。 我们已经进行了集成测试,可以模拟服务器使用的服务,例如数据库,Webservices等。 我们只是简单地重用了这种环境。
为了模拟GWT应用程序,我们可以编写一些Java。 例如:
1MyView myView = (MyView) RootPanel.get( 0 ) ;
2myView. getButton().click();
这不是很实用。 我们需要在各处添加吸气剂。 而且,我们经常会想获取“表X中的第四个复选框,它本身位于容器Y中,其自身位于RootPanel中”。 这就是为什么我们开发了一种类似于XPath的小型语言,并提供了一种在给定组件上调用方法的机制。
例如,要到达位于容器第一个小部件中的标签,而标签本身位于RootPanel的第三个小部件中,我们可以编写:
1 /rootPanel/widget(2)/widget(0)
此标签的包含文本,通常可通过getText()方法访问,可通过以下方式访问
1 /rootPanel/widget(2)/widget(0)/text
大量使用Java反射使所有这些成为可能。
可以使用这些XPath在CSV文件中编写测试方案。 这是CSV方案的示例,其中:
- 启动GWT应用程序
- 检查标签内容是否包含“ foo”
- 模拟单击按钮,该按钮通过GWT-RPC调用Spring服务,并替换标签内容
- 检查标签的内容是否已更改并且现在包含“ bar”
这是方案:
1 initApp;
2 assertExact;foo;/rootPanel/widget(2)/widget(0)/text
3 click;/rootPanel/widget(2)/widget(1)
4 assertExact;bar;/rootPanel/widget(2)/widget(0)/text
少量的代码使我们可以在JUnit中启动此方案。 因此,这些测试的执行方式与单元测试的执行方式相同,只是执行时间更长(由于服务器部分的启动)。
集成测试部分也在Gwt-Test-Utils项目中。 这里提供了文档。
我们可以用Selenium做同样的事情。 有三个主要区别:
- 由于组件ID,GWT与Selenium不太适合(但是可以做到)。
- 我们认为,我们的本地化语言更加简单高效。
- 我们的测试是由JUnit启动的,这意味着我们可以从Eclipse或在Maven构建期间启动它们,这使它们的执行更加容易。
3.4结论
在我们的项目中,我们编写了900个UI单元测试和大约40个集成测试。
这一系列测试确保了我们应用程序所有功能的整体不退缩:
- 最初的投资并不算大,因为测试系统是在我们构建应用程序时开发的。
- 好处是巨大的:在大约一年的发展中,我们几乎没有退化。
- 方案的维护成本很高(每15天几个小时),但是与所产生的收益相比,我们认为这是完全合理的。
- 完整的应用程序非回归测试套件(在模拟环境中)只需3分钟即可执行。
- 我们经常在GWT应用程序中进行重构,甚至没有启动GWT:如果非回归测试通过,我们可以确保没有任何问题。
事实证明,GWT是一种UI技术,它通过一些工具使我们能够执行高级测试,从而进一步提高了该技术的生产率。
测试框架及其文档已作为开源项目Gwt-Test-Utils发布 。
gwt 测试