The Dawn of ZK Application Test Suite:Mimic Library
Redirect page
Redirect to:
Hawk Chen, Engineer, Potix Corporation
03 April, 2012
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 can be an agile developer's darkest moment. But, don't be depressed, let me enlighten your path with the first light of ZK Application Test Suite (ZATS) - Mimic Library. This project is very much inspired by Georgi Rahnev of Telesoft Consulting GmbH, Vienna, along with a few other users and contributors. Your feedback is very valuable to us and very much appreciated.
This library is currently "under development" This article will preview some of the basic ideas and concepts of what we have done so far, we are open to any feedback or comments.
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 depend 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 or message dialog created at client side. 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
- Query 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.
Conversations
- (Notice the plural form) It contains several utility methods to control
Conversation
and emulator. - It also helps us to retrieve ZK components with 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
Conversation
- It acts like a client to the server emulator and also maintains connection state.
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 a 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.
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 = Conversations.query("button");
ComponentAgent label = Conversations.query("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 call
Conversations.start()
and pass root directory where ZUL pages are stored as a parameter. Most of the times, it is located in 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)
- Of course, if we start the server emulator at
@BeforeClass
, we stop it byConversations.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,
Conversations.query()
is a powerful tool to retrieve it. As the ZUL contains only one button, we can query it by component name:query("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 the operation agents. The conversion methodas()
will check for available operation for the targetComponentAgent
. For example, you cannot type something in a Label, If you try to convert it to a non-available operation agent, you will get an exception. (line 20)
- For convenience,
ComponentAgent
provides shortcut methods for commonly used operations likeclick()
. It automatically 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
Now that you are more familiar with 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 three 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 -->
This test case is to verify composer's logic. In our example application, there are four main function: Add, Update, Delete, and Reset, the test case verifies them one by one.
Test Add
When clicking "Add" button, it adds one item to the listbox.
TodoTest.java
import static org.junit.Assert.*;
import static org.zkoss.zats.mimic.Conversations.query;
//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");
//query components
ComponentAgent itemName = query("textbox");
ComponentAgent priority = query("intbox");
ComponentAgent date = query("datebox");
//add
//itemName.as(TypeAgent.class).type("one-item");
itemName.type("one-item");
priority.type("3");
date.type("2012-03-16");
query("button[label='Add']").click();
//verify each listcell's label
ComponentAgent listbox = query("listbox");
List<ComponentAgent> cells = listbox.queryAll("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());
- As we use
Conversations.query()
andAssert.assertEquals()
heavily, we can use "import static" syntax to make source code shorter. - As seen in the previous example, this is a shortcut method. The complete calling is in the previous line. (line 21)
- When typing in a Datebox, use the date format that you have specified in Datebox's "format" attribute. (line 23)
- The query syntax means "retrieve a button whose label is 'Add'". (line 24)
- If we call
ComponentAgent.query()
, it'll only query the ComponentAgent's child components. Here, we find listitem to get listcell. (line 28)
Test Update
When selecting a listitem, its value will be loaded to three different input fields. Users can click "Update" button after modifying todo item's properties.
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");
query("button[label='Update']").click();
//retrieve Listitem again to verify it
cells = listbox.queryAll("listitem").get(0).getChildren();
assertEquals("one-item modified",cells.get(0).as(Listcell.class).getLabel());
assertEquals("5",cells.get(1).as(Listcell.class).getLabel());
SelectAgent.select(index)
is used to mimic the selecting of a listitem in a listbox. The parameter "index" is the target of the listitem's index in which you want to select and it starts from zero.
- Notice that ZK will replace listbox's children component with newly-created ones when re-rendering occurs. Therefore we cannot reuse
cells
variable for verification, as it holds old data. We should retrieve listitem again to verify.
Test Reset
"Reset" button will clear all input fields into default value.
TodoTest.java
//continue from previous code segment ...
//reset
listbox.select(0);
assertNotNull(itemName.as(Textbox.class).getValue());
query("button[label='Reset']").click();
assertEquals("",itemName.as(Textbox.class).getValue());
assertEquals((Integer)0,priority.as(Intbox.class).getValue());
assertNull(date.as(Datebox.class).getValue());
Test Delete
"Delete" button will remove the selected listitem.
TodoTest.java
//continue from previous code segment ...
//delete
assertEquals(1,listbox.queryAll("listitem").size());
listbox.select(0);
query("button[label='Delete']").click();
assertEquals(0,listbox.queryAll("listitem").size());
//The next line causes IllegalStateException: Components can be accessed only in event listeners
//query("textbox").as(Textbox.class).setValue("abc");
}
- We verify the number of listitem, from 1 to 0 for deletion. (line 4, 7)
- Watch out! You should not manipulate component's properties directly in a test case. (line 10)
Summary
Mimic library is an unit test library that helps agile developers to assure the quality of a composer (or ViewModel) and ZUL. Developers are able to write test cases to mimic user interaction then verify its 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 your workspace as an existing project. Test cases are under "src/test/java". You can also find the examples ("hello.zul" and "todo.zul") we went through in this small talk.
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |