The Dawn of ZK Application Test Suite:Mimic Library
Hawk Chen, Engineer, Potix Corporation
0.9.0 Freshly
Opening
In agile software development, developers modify their codes frequently for requirement change or refactoring, they therefore also perform unit tests frequently to ensure the software quality. In ZK-based applications, it is hard to execute an unit test on the composer which is tightly-coupled to ZUL because it is instantiated when a ZUL is requested by a browser. The same problem arises if you want to verify a ZUL's zkbind expression with ViewModel. Hence TDD (Test-Driven Development) cannot proceed under this situation.
In some cases, agile developers may deploy their web applications to a server and test it within a browser. However, writing an automation test to control a browser is an issue, and testing for different browsers is also a trouble. Not to mention that running an unit test in an application server is time-consuming and no doubt an agile developer's darkest time. But, don't be depressed, let me enlighten your path with the first light of ZK Test Suite - Mimic Library.
Mimic Library : No Server Test
Mimic library enable testers to test their composer without an application server, and of course without a browser, either. Through this library, testers can mimic user interactions to applications such as clicking or typing to verify composer's (controller layer) data and logic. All they have to do is to write a regular unit test case with JUnit and use mimic library's utility class to interact components on ZUL. Then, run the test case, it will load your project's ZUL with a server emulator but no screen is rendered, it just simulates user interaction to the server. Testers can verify the results by checking component's property or model.
No deploying to server, no rendering on browser, the unit test case can be executed in a very short period of time - this is very helpful for frequent unit testing during agile development processes.
The concept is as follows:
Testers write test cases to simulate user action such as clicking or typing with operation agents. The operation agent communicates with server emulator and triggers the composer's event handlers to change the component's status. Testers can check component's properties from component agent to verify the result of user action. It might be a label changing its value or a Listbox increases by one item. These behaviors that reflect on the component's properties can be verified.
Limitation
As this library focuses on testing the composer's logic on the server side, there are some limitations you should know:
- Functions that depends on the application server cannot work.
- Test cases run in simulated environment; all functions that requires an application server does not work (e.g. JNDI, or JTA). If user's AUT (Application Under Test) project adopts such container-provided services, they need extra work to make it work normally out of a container, e.g. use Test Double like a fake object.
- Cannot test browser’s behavior.
- In ZK-based applications, some behaviors are handled by browser (JavaScript), e.g. popup menu. As server side is not aware of these behaviors, we cannot verify it.
- Cannot test visual effects.
- It cannot verify any behaviors that doesn't reflect upon component's properties such as animations, or a component's visual effect.
Hello Mimic Test
To present the basic usage of mimic library, I will demonstrate how to test a simple application. This application has only one button and one label, after clicking it, the label will show "Hello Mimic".
Setup
For simplicity, you can just download the example project we provide and try to write some test cases in it.
If you want to use mimic library in your project, include all zats-*.jar and jetty-*.jar under example project's "/lib" folder.
Write a Test Case
The steps to write a test case are as follows:
- Setup web application content path
- Open a conversation with a ZUL
- Find a component
- Perform an operation on a component
- Verify result by checking a component’s property
- Tear down, stop server emulator
Before diving into the source code of a test case, let me introduce some basic classes used in a test case.
- Conversation
- It acts like a client to the server emulator and also maintains connection state.
- Conversations
- (Notice the plural form) It contains several utility methods to control Conversation and emulator.
- ComponentAgent
- To mimic a ZK component, determine which operation you can perform on it. We can also get ZK component property's value from it.
- OperationAgent (ClickAgent, TypeAgent, SelectAgent...)
- To mimic available user operation to a ZK component.
- We name it "Agent" because it's not the user operation itself. It's an agent to mimic user operation on a component.
- Searcher
- To retrieve ZK component with ZK selector syntax supported in SelectorComposer
- For available selector syntax, please refer to javadoc or Small Talks/2011/January/Envisage ZK 6: An Annotation Based Composer For MVC
We write the test case with JUnit 4 annotation, please refer to JUnit 4 in 60 seconds.
HelloTest.java"
//remove import for brevity
public class HelloTest {
@BeforeClass
public static void init() {
Conversations.start("./src/main/webapp");
}
@AfterClass
public static void end() {
Conversations.stop();
}
@Test
public void test() {
Conversations.open("/hello.zul");
ComponentAgent button = Searcher.find("button");
ComponentAgent label = Searcher.find("label");
//button.as(ClickAgent.class).click();
button.click();
assertEquals("Hello Mimic", label.as(Label.class).getValue());
}
@After
public void after() {
Conversations.clean();
}
}
- Before starting a test, we have to setup root directory where ZUL pages locate by Conversations.start() . Mostly it's your web application's content root folder. In our example, we use maven default project structure. This method also starts the server emulator. (line 5)
- As a matter of course, we start the server emulator at @BeforeClass , we have to stop it by Conversations.stop() . (line 10)
- The first statement of a test case is to open a ZUL page, like a browser visits a ZUL. (line 15)
- Before we can mimic a user action to a component, we should retrieve a ComponentAgent. Empowered by selector syntax, Searcher.find() is a powerful tool to retrieve it. Because the ZUL contains only 1 button, we can just find by component name: find("button") (line 17)
- As we don't have a browser screen to view, we cannot interact with a component by mouse's pointer. To mimic a user action, we have to convert ComponentAgent to one of operation agents. The conversion method as() will check available operation for the target ComponentAgent . For example, you cannot type something in a Label. If you try to convert to a non-available operation agent, you will get an exception. (line 20)
- For convenience, ComponentAgent provides shortcut methods for most-used operation like click() . It just converts for you. (line 21)
- To verify test result, we also can use ComponentAgent.as() to convert it as a ZK component then get its property by getter methods. (line 22)
- We should call Conversations.clean() to clear desktop before opening another ZUL. (line 27)
CRUD Application Test
For now that you should have know the basic usage. Let's take a look at a more real case: a simple todo list application. It allows us to add, update, delete, and view todo items. Each item has 3 fields: item name, priority, and date.
todo.zul
<listbox id="listbox" rows="4" >
<listhead>
<listheader label="Name" />
<listheader label="Priority" width="50px" />
<listheader label="Date" width="90px" />
</listhead>
</lisbox>
<!-- other components -->
Name:
<textbox id="itemBox" cols="25" />
Priority:
<intbox id="priorityBox" cols="1" />
Date:
<datebox id="dateBox" cols="8" format="yyyy-MM-dd"/>
<button id="add" label="Add" height="24px" />
<button id="update" label="Update" height="24px" />
<button id="delete" label="Delete" height="24px" />
<button id="reset" label="Reset" height="24px" />
<!-- other components -->
The test case is to verify composer's logic. In our example application, there are 4 main function: Add, Update, Delete, Reset, the test case verify them one by one.
TodoTest.java
import static org.junit.Assert.*;
import static org.zkoss.zats.mimic.Searcher.*;
//other imports omitted for brevity
public class TodoTest {
//remove setup and tear down method for brevity
@Test
public void test() {
//visit the target page
Conversations.open("/todo.zul");
//find components
ComponentAgent itemName = find("textbox");
ComponentAgent priority = find("intbox");
ComponentAgent date = find("datebox");
//add
//itemName.as(TypeAgent.class).type("one-item");
itemName.type("one-item");
priority.type("3");
date.type("2012-03-16");
find("button[label='Add']").click();
//verify each listcell's label
ComponentAgent listbox = find("listbox");
List<ComponentAgent> cells = listbox.findAll("listitem").get(0).getChildren();
assertEquals("one-item",cells.get(0).as(Listcell.class).getLabel());
assertEquals("3",cells.get(1).as(Listcell.class).getLabel());
assertEquals("2012/03/16",cells.get(2).as(Listcell.class).getLabel());
- When clicking "Add" button, it adds one item to the listbox.
- We can use "import static" syntax to make source code shorter, because we use Searcher.find() and Assert.assertEquals() heavily.
- As you learn in previous example, this is a shortcut method. The complete calling is previous line. (line 21)
- When typing in a Datebox, you should use the date format that you specify in Datebox "format" attribute. (line 23)
- Find listitem to get listcell. (line 28)
TodoTest.java
//continue from previous code segment ...
//update
listbox.select(0);
//verify selected
assertEquals("one-item",itemName.as(Textbox.class).getValue());
assertEquals((Integer)3,priority.as(Intbox.class).getValue());
assertEquals("2012-03-16",date.as(Datebox.class).getRawText());
//modify the todo item
itemName.type("one-item modified");
priority.type("5");
find("button[label='Update']").click();
//retrieve Listitem again to verify it
cells = listbox.findAll("listitem").get(0).getChildren();
assertEquals("one-item modified",cells.get(0).as(Listcell.class).getLabel());
assertEquals("5",cells.get(1).as(Listcell.class).getLabel());
- When selecting a listitem, it's value will be loaded to 3 input fields. User can click "Update" button after modifying todo item's properties.
- SelectAgent.select(index) is to mimic selecting a listitem in a listbox. The parameter "index" is th target listitem's index you want to select and starts from 0.
- Notice that ZK will replace listbox's children component with newly-created one when re-rendering it. Therefore we cannot reuse cells variable for verification, it holds old data. We should retrieve listitem again to verify.
TodoTest.java
//continue from previous code segment ...
//reset
listbox.select(0);
assertNotNull(itemName.as(Textbox.class).getValue());
find("button[label='Reset']").click();
assertEquals("",itemName.as(Textbox.class).getValue());
assertEquals((Integer)0,priority.as(Intbox.class).getValue());
assertEquals(true, date.as(Datebox.class).getValue()==null);
- "Reset" button will clear all input fields into default value.
TodoTest.java
//continue from previous code segment ...
//delete
assertEquals(2,listbox.getChildren().size());
listbox.select(0);
find("button[label='Delete']").click();
assertEquals(1,listbox.getChildren().size());
//The next line causes IllegalStateException: Components can be accessed only in event listeners
//find("textbox").as(Textbox.class).setValue("abc");
}
- "Delete" button will remove the selected listitem.
- Watch out! You should not manipulate component's properties directly in a test case. (line 10)
Summary
Mimic library is a unit test library that help agile developers assure quality of composer (or ViewModel) and ZUL. Developer can write test case to mimic user interaction then verify result by inspecting component's properties. It loads ZUL pages and runs the test case in a server emulator environment without deploying to a real application server. This reduces test running time largely which is very suitable for test-driven development or agile software development.
Download
The example project is an eclipse project archive. You can import it into workspace as an existing project. Test cases are under "src/test/java". You can also find the example ("hello.zul" and "todo.zul") we talked in this small talk.
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |