gwt 测试
GWT是Google开发的框架,用于使用Java编程语言构建支持AJAX的Web应用程序。 它包括:
这种方法具有一些优点:
最后,由于所有内容都在Java中(甚至MVC模式的View部分),我们应该能够轻松创建UI测试。 本文探讨了一些方法。
我们将从一个简单的示例开始:我们要显示一个文本输入字段,一个按钮和一个标签。 当我们单击按钮时,用户输入的文本字段内容将放置在标签中。 相应的测试(在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并在浏览器中执行后才能工作。
GWT提供了一个用于执行单元测试的类,称为GWTTestCase,但是它受到许多限制:
此外,使用GWTTestCase和GWTTestSuite极大地限制了对Java API的访问:单元测试必须与GWT编译器兼容(该编译器负责将Java UI代码编译为JavaScript)。 这意味着,例如,不可能使用Java反射。 因此,无法使用Unitils或Easymock等测试库。
Java类JavaScript中模拟名单可在这里 。
GWT 2.0对GWTTestCase进行了改进,因为该类不再使用本机库来运行测试。 使用HtmlUnit代替托管模式使用的浏览器。 在GWT 2中,GWTTestCase与平台无关。 但是GWTTestCase仍然存在一些局限性:测试执行缓慢,并且不可能使用标准的测试框架。
测试GWT应用程序的一种解决方案是在测试时不使用GWT对象,而是将所有GWT对象替换为可在标准JVM中工作的模拟对象。 然而,该解决方案带来了很大的不便。 由于GWT对象不是接口,而是具体的类,因此我们将必须修改应用程序的代码,使其使用接口(您可以在此处找到示例) 。 该解决方案影响我们应用程序的设计。
为了前进,我们调查了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呈现系统)实现。 即使我们成功解决了本机方法的问题,我们也必须找到解决方案以重新实现此行为。
我们有效实现GWT测试的目标如下:
在客户项目期间,我们开发了一个框架来满足我们的目标。
我们设计了一个测试框架来修改GWT类,而无需开发人员进行任何其他工作。 它从对GWT对象的类的字节码的“热”修改开始,以用Java方法替换本机JSNI方法,如下图所示:
注意:框架技术实现的介绍不属于本文范围。 我们将专注于其用法。
我们已将此框架作为一个开源项目Gwt-Test-Utils发布 ,以便任何人都可以使用它。
我们将从编写一个简单的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中 可以找到的 注释 。 它用于声明模拟对象。
这些限制并不是微不足道的,但是我们认为测试框架的优势胜过了它们。
有关Maven配置的完整示例,请参见Gwt-Test-Utils demo1项目 。
在我们的项目中(使用JRockit 1.5编译的26k行GWT应用程序,在Hotspot 1.6下进行了测试),我们完成了600%的单元测试(14k行测试代码),覆盖了85%的代码。 但是,我们将测试集中在GWT应用程序的控制器部分,其目的不是要重新测试GWT,而是要验证我们已实现的行为。
Gwt-Test-Utils框架允许我们有效地测试UI。 这些测试的弱点在于它们是单元测试:我们正在测试单个视图的行为。 服务器部分(接收GWT-RPC调用)被模拟。 我们遇到的大多数问题都在视图链接上,视图中有很多GWT-RPC调用。
在我们的例子中,服务器后端使用Spring。 因此,我们使用SpringJUnit4ClassRunner对其进行测试,它将在JUnit下为我们启动整个服务器后端。 因此,通过添加一些粘合剂,我们“封闭了循环”:我们没有模拟GWT应用程序服务器后端,而是将UI部分连接到服务器后端。
因此,GWT应用程序及其服务器已完全在唯一的JVM中启动并运行,并准备执行测试。 例如,我们可以编写一个测试方案,其中:
这些测试不再是单元测试。 它们是真正的集成测试。
服务器端无需修改。 我们仅添加了一些粘合代码即可将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方案的示例,其中:
这是方案:
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做同样的事情。 有三个主要区别:
在我们的项目中,我们编写了900个UI单元测试和大约40个集成测试。
这一系列测试确保了我们应用程序所有功能的整体不退缩:
事实证明,GWT是一种UI技术,它通过一些工具使我们能够执行高级测试,从而进一步提高了该技术的生产率。
测试框架及其文档已作为开源项目Gwt-Test-Utils发布 。
翻译自: https://www.infoq.com/articles/gwt_unit_testing/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
gwt 测试