gwt 测试_GWT应用程序的单元和集成测试

gwt 测试

GWT是Google开发的框架,用于使用Java编程语言构建支持AJAX的Web应用程序。 它包括:

  1. 用于创建GUI的API(类似于Swing),用于处理Web浏览器的文档对象模型(DOM)。
  2. Java到JavaScript的编译器。
  3. 用于运行和调试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 测试_GWT应用程序的单元和集成测试_第1张图片

注意:框架技术实现的介绍不属于本文范围。 我们将专注于其用法。

我们已将此框架作为一个开源项目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 测试_GWT应用程序的单元和集成测试_第2张图片

因此,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发布 。

翻译自: https://www.infoq.com/articles/gwt_unit_testing/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

gwt 测试

你可能感兴趣的:(单元测试,java,python,linux,spring)