Shining ZATS Mimic"

From Documentation
m (correct highlight (via JWB))
 
(74 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
{{Template:Smalltalk_Author|
 
{{Template:Smalltalk_Author|
 
|author=Hawk Chen, Engineer, Potix Corporation
 
|author=Hawk Chen, Engineer, Potix Corporation
|date=April, 2012
+
|date=April 25, 2012
|version=1.0.0-RC
+
|version=1.0.0
 
}}
 
}}
  
Line 9: Line 9:
 
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 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'''.  
+
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 a 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 ''' ZATS (ZK Application Test Suite) Mimic '''.  
  
This project is mostly 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 we appreciate very much.
+
Since the previous freshly release (please refer to [[Small Talks/2012/April/The Dawn of ZK Application Test Suite:Mimic Library]]), we have updated initialized API with more support on user operations which would wider the testing scenarios of this library.  
  
Comparing to previous freshly release (please refer [[Small Talks/2012/April/The Dawn of ZK Application Test Suite:Mimic Library]]), we change initialized API and support more user operations. These makes this library apply to wider testing scenario.
+
This project is mainly inspired by Georgi Rahnev of Telesoft Consulting GmbH, Vienna, along with a few other users and contributors.
  
= Mimic Library : No Server Test =  
+
= 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''' and use mimic library's utility class to interact components on ZUL. Then, run the test case.
+
ZATS Mimic enables developers 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''' and use Mimic's utility class to interact components on ZUL and then, run the test case.
  
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.
+
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 a agile development process.
  
 
The concept is as follows:
 
The concept is as follows:
  
[[File:Smalltalk-MimicLibraryConcept.png]]
+
[[File:Smalltalk-ZatsMimicConcept.png | center]]
  
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.'''
+
Testers write test cases to simulate user action such as clicking or typing with operation agents. Operation agent communicates with server emulator and triggers the composer's event handlers to change the component's status. Testers are able to check component's properties from the component agent to verify the result of user action. It might be a ''label'' changing its value or a ''listbox'' increased by one item. '''All behaviors that reflect on the component's properties can be verified.'''
  
 
== Limitation==
 
== Limitation==
Line 31: Line 31:
 
As this library focuses on testing the composer's logic on the server side, there are some limitations you should know:
 
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. '''
+
* '''Functions dependent on the application server won't 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.
+
*: Test cases run in simulated environment; all functions that require an application server do not work (e.g. JNDI, or JTA). If an AUT (Application Under Test) project adopts such container-provided services, it needs extra work to make them work normally out of a container, e.g. use Test Double like a fake object.
  
 
* '''Cannot test browser’s behavior.'''
 
* '''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.
+
*: In a ZK-based application, some behaviors are handled by a browser (JavaScript), e.g. popup menu or message dialog created at the client side. As server side is not aware of these behaviors, it cannot be verified.
  
 
* '''Cannot test visual effects.'''
 
* '''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.
 
*: It cannot verify any behaviors that doesn't reflect upon component's properties such as animations, or a component's visual effect.
 
  
 
== Setup ==
 
== Setup ==
Line 45: Line 44:
 
=== Maven Project ===
 
=== Maven Project ===
  
It will be available in the near future!
 
  
<!--
 
 
If your project depends on ZK '''5.0.x''', add the following dependency:
 
If your project depends on ZK '''5.0.x''', add the following dependency:
  
Line 55: Line 52:
 
       <groupId>org.zkoss.zats</groupId>
 
       <groupId>org.zkoss.zats</groupId>
 
       <artifactId>zats-mimic</artifactId>
 
       <artifactId>zats-mimic</artifactId>
       <version>1.0.0-RC</version>
+
       <version>1.0.0</version>
 
       <scope>test</scope>
 
       <scope>test</scope>
 
     </dependency>
 
     </dependency>
Line 68: Line 65:
 
       <groupId>org.zkoss.zats</groupId>
 
       <groupId>org.zkoss.zats</groupId>
 
       <artifactId>zats-mimic-ext6</artifactId>
 
       <artifactId>zats-mimic-ext6</artifactId>
       <version>1.0.0-RC</version>
+
       <version>1.0.0</version>
 
       <scope>test</scope>
 
       <scope>test</scope>
 
     </dependency>
 
     </dependency>
Line 74: Line 71:
 
</source>
 
</source>
  
Remember also add dependencies of your preferred unit test framework, e.g. JUnit.
+
Remember to also add dependencies of your preferred '''unit test framework''', e.g. JUnit or TestNG.
-->
 
  
 
=== Manually ===
 
=== Manually ===
  
Download the release zip file, add all jar under '''dist/lib''' and '''dist/lib/ext''' into your project's classpath.
+
[[Small_Talks/2012/April/Shining_ZATS_Mimic#Download |Download the released zip file]] and add all jar under '''dist/lib''' and '''dist/lib/ext''' into your project's classpath. '''Note that please do not deploy these jars to your application server'''.
 +
 
 +
Also remember to add jar of your preferred unit test framework, e.g. JUnit.
 +
 
 +
= Simple Application Under Test =
 +
 
 +
We are going to introduce some basic concepts of ZATS Mimic with a simple application. This application only has one label with no content at first and one button. It only has one function: when a user clicks the button, the label shows "Hello Mimic" as the image below.
 +
 
 +
[[File:Smalltalk-mimic-hello.png | center]]
 +
 
 +
 
 +
'''ZUL of our simple application'''
 +
 
 +
<source lang="xml">
 +
 
 +
<zk>
 +
<window title="hello" border="normal" width="300px" apply="org.zkoss.zats.example.hello.HelloComposer">
 +
<label />
 +
<button label="Hello" />
 +
</window>
 +
</zk>
 +
 
 +
</source>
 +
 
 +
'''Composer of our simple application'''
 +
 
 +
<source lang="java">
 +
 
 +
public class HelloComposer extends SelectorComposer {
 +
 +
@Wire("label")
 +
Label label;
 +
 +
@Listen("onClick = button")
 +
public void hello(){
 +
label.setValue("Hello Mimic");
 +
}
 +
}
  
Remember also add jar of your preferred unit test framework, e.g. JUnit.
+
</source>
  
== Write a Test Case ==
+
= Write a Test Case =
The steps to write a test case are as follows:
+
Steps to write a test case are as follows:
 
# Setup web application content path
 
# Setup web application content path
 
# Create a client to connect to a ZUL
 
# Create a client to connect to a ZUL
Line 91: Line 124:
 
# Verify result by checking a component’s property
 
# Verify result by checking a component’s property
 
# Tear down, stop server emulator
 
# Tear down, stop server emulator
 
  
 
= Hello Mimic Test=
 
= 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".
+
To present the basic usage of ZATS Mimic, I will demonstrate how to test a our simple application mentioned above.  
  
  
 
Before diving into the source code of a test case, let me introduce some basic classes used in a test case.
 
Before diving into the source code of a test case, let me introduce some basic classes used in a test case.
  
; <tt> Zats</tt>
+
; <code> Zats</code>
:    It contains several utility methods to initialize and clean testing environment. By default it starts server emulator '''with predefined web.xml and zk.xml ''' bundled in mimic library's jar.
+
:    It contains several utility methods to initialize and clean testing environment. By default, it starts server emulator '''with predefined web.xml and zk.xml ''' bundled in ZATS Mimic's jar.
; <tt> Client</tt>
+
; <code> Client</code>
:    It acts like a client to the server emulator and we use it to connect to a ZUL.
+
:    Acts like a client to the server emulator and we use it to connect to a ZUL.
; <tt> DesktopAgent </tt>
+
; <code> DesktopAgent </code>
:    It wraps ZK Desktop object, we usually call its <tt> query() </tt> or <tt> queryAll() </tt> to retrieve ZK components with selector syntax supported in <javadoc> org.zkoss.zk.ui.select.SelectorComposer </javadoc>
+
:    Wraps ZK Desktop object, we usually call its <code> query() </code> or <code> queryAll() </code> to retrieve ZK components with selector syntax supported in <javadoc> org.zkoss.zk.ui.select.SelectorComposer </javadoc>
 
:    For available selector syntax, please refer to javadoc or [[Small Talks/2011/January/Envisage ZK 6: An Annotation Based Composer For MVC]]
 
:    For available selector syntax, please refer to javadoc or [[Small Talks/2011/January/Envisage ZK 6: An Annotation Based Composer For MVC]]
; <tt> ComponentAgent </tt>
+
; <code> ComponentAgent </code>
:    It mimics a ZK component and determines which operation you can perform on it. We can also get ZK component property's value from it.
+
:    Mimics a ZK component and determines which operation you can perform on it. We can also get ZK component property's value from it.
: It also has <tt> query() </tt>, it means to find targets among its child components.
+
: It also has <code> query() </code> which means to find targets among its child components.
; <tt> OperationAgent (ClickAgent, TypeAgent, SelectAgent...)</tt>
+
; <code> OperationAgent (ClickAgent, InputAgent, SelectAgent...)</code>
 
:    To mimic a user operation to a ZK component.
 
:    To mimic a user operation to a ZK component.
:    We name it "Agent" because it's not really the user operation itself. It's an agent to mimic user operation on a component.
+
:    We name it "Agent" as it's not really the user operation itself, it's an agent to mimic user operation to a component.
  
 
== Hello Test Case ==
 
== Hello Test Case ==
 
We write the test case with JUnit 4 annotation, please refer to [http://www.cavdar.net/2008/07/21/junit-4-in-60-seconds JUnit 4 in 60 seconds].
 
We write the test case with JUnit 4 annotation, please refer to [http://www.cavdar.net/2008/07/21/junit-4-in-60-seconds JUnit 4 in 60 seconds].
 +
 +
The following test case will mimic a user clicking the button and verify the label's value is "Hello Mimic" or not.
  
 
'''HelloTest.java"
 
'''HelloTest.java"
  
<source lang="java" high="5,10,15,20,22,25,26,27">
+
<source lang="java" highlight="5,10,15,20,22,25,26,27">
  
 
//remove import for brevity
 
//remove import for brevity
Line 153: Line 187:
 
</source>
 
</source>
  
* Before starting a test, we have to call <tt> Zats.init() </tt> 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 initialize testing environment and starts the server emulator. (line 5)
+
* Before starting a test, we have to call <code> Zats.init() </code> 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 initializes testing environment and starts the server emulator. (line 5)
  
* Of course, we start the server emulator at <tt> @BeforeClass </tt>, we should stop it by <tt> Zats.end() </tt> at <tt>  @AfterClass </tt>. (line 10)
+
* Of course, we start the server emulator at <code> @BeforeClass </code>, we should stop it by <code> Zats.end() </code> at <code>  @AfterClass </code>. (line 10)
  
* We should call <tt> Zats.cleanup() </tt> to clear desktop before opening another ZUL. (line 15)
+
* We should call <code> Zats.cleanup() </code> to clear desktop before opening another ZUL. (line 15)
  
* The first statement of a test case is to create a client and connect to a ZUL page, like a browser visits a ZUL. The <tt> connect() </tt> returns a <tt> DesktopAgent </tt> and we usually retrieve <tt> ComponentAgent </tt> from it to perform user operation. (line 20)
+
* The first statement of a test case is to create a client and connect it to a ZUL page, like a browser visiting a ZUL. The <code> connect() </code> returns a <code> DesktopAgent </code> and we usually retrieve <code> ComponentAgent </code> from it to perform user operation. (line 20)
  
* Before we can mimic a user action to a component, we should '''retrieve a ComponentAgent'''. Empowered by selector syntax, <tt> DesktopAgent .query()</tt> is a powerful tool to retrieve it. As the ZUL contains only one button, we can query it by component name: <tt> query("button") </tt> (line 22)
+
* Before we can mimic a user action to a component, we should '''retrieve a ComponentAgent'''. Empowered by selector syntax, <code> DesktopAgent .query()</code> is a powerful tool to retrieve it. As the ZUL contains only one button, we can query it by component name: <code> query("button") </code> (line 22)
  
* 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 <tt> ComponentAgent </tt> to one of the operation agents. The conversion method <tt> as() </tt> will check for available operation for the target <tt> ComponentAgent </tt>. For example, you cannot type something in a <b>Label</b>, If you try to convert it to a non-available operation agent, you will get an exception. (line 25)
+
* As we do not 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 <code> ComponentAgent </code> to one of the operation agents. The conversion method <code> as() </code> will check for available operation for the target <code> ComponentAgent </code>. For example, you cannot type something in a <b>Label</b>, If you try to convert it to an unsupported operation agent, you will get an exception. (line 25)
  
* For convenience, <tt> ComponentAgent </tt> provides shortcut methods for commonly used operations like <tt> click() </tt>. It automatically converts for you. (line 26)
+
* For convenience, <code> ComponentAgent </code> provides shortcut methods for commonly used operations like <code> click() </code>. It automatically gets operation agent and calls it for you. (line 26)
  
* To verify test result, we also can use <tt> ComponentAgent.as() </tt> to convert it as a ZK component then get its property by getter methods. (line 27)
+
* To verify test result, we can also use <code> ComponentAgent.as() </code> to convert it as a ZK component then get its property by getter methods. (line 27)
  
 
= Operation Agent Usage =
 
= Operation Agent Usage =
In order to mimic user operation to ZK components, our library provide various operation agents. You can find all supported under package <tt> org.zkoss.zats.mimic.operation </tt> including ''' check, click, close, focus, key stroke, select, multiple select, type, select by index,''' and '''render '''. We support those commonly-used operation first and will keep adding more operations.
+
In order to mimic user operation to ZK components, our library provides various operation agents. You can find all supported operations under package <code> org.zkoss.zats.mimic.operation </code> including ''' check, click, close, focus, key stroke, select, multiple select, type, select by index,''' and '''render '''. At this stage. we support operations that are more commonly used and plan to keep adding more operations in the future.
 +
 
 +
All source code of following examples can be found in the example project. [[Small_Talks/2012/April/Shining_ZATS_Mimic#Download| Download It]]
  
== Click ==
+
== ClickAgent ==
<tt> ClickAgent </tt> helps us to mimic clicking a component in general intention. It could trigger onClick, onDoubleClick, or onRightClick event. Because most of user action are done by clicking, but they might have different intentions. To click on a listitem means selecting it, and to click on a checkbox represents checking it. Therefore, to avoid mixing several actions into clicking operation, specific action has corresponding specific operation agent. For example, if you want to select a listitem, use <tt> SelectAgent </tt>. For checkbox, use <tt> CheckAgent </tt>. It depends on your intention.
+
<code> ClickAgent </code> helps us to mimic the clicking of a component for general intention; it is able to trigger <b>onClick</b>, <b>onDoubleClick</b>, or <b>onRightClick</b> events. Most user actions are done by clicking, but they might have different intentions. For example, clicking a <b>listitem</b> would represent selecting it, by clicking on a <b>checkbox</b> would represents checking the box and so on. Therefore, to avoid mixing several actions into clicking operations, specific actions has different corresponding operation agents. For example, if you wanted to select a <b>listitem</b>, use <code> SelectAgent </code>,for checkbox, use <code> CheckAgent </code>. Which operation agent you choose to use would depend on the intention.
  
According to [http://books.zkoss.org/wiki/ZK_Component_Reference/Base_Components/HtmlBasedComponent ZK Component Referenece], all components that inherit <tt> HtmlBasedComponent </tt> support click, double click, and right click.
+
According to [http://books.zkoss.org/wiki/ZK_Component_Reference/Base_Components/HtmlBasedComponent ZK Component Referenece], all components that inherit <code> HtmlBasedComponent </code> support click, double click, and right click.
  
 
'''ClickTest.java'''
 
'''ClickTest.java'''
  
<source lang="java" high="12,15,18">
+
<source lang="java" highlight="12,15,18">
 
public class ClickTest {
 
public class ClickTest {
  
Line 203: Line 239:
 
</source>
 
</source>
  
* As we mentioned in previous section, it's a shortcut method for convenience. (line 12)
+
* As mentioned in the previous section, it's a shortcut method for convenience. (line 12)
 +
 
 +
* If you want to perform double click or right click, you have get <code> ClickAgent </code> first from <code> ComponentAgent </code>. (line 15,18)
  
* If you want to perform double click or right click, you have get <tt> ClickAgent </tt> first from <tt> ComponentAgent </tt>. (line 15,18)
+
== InputAgent ==
  
== Type ==
+
We use a todo list application to demonstrate <code> InputAgent </code> usage. Here is the application's UI:
We use a todo list application to demonstrate <tt> TypeAgent usage </tt>. Here is the application's UI:
 
  
[[File:Smalltalk-MimicLibrary-todolist.png]]
+
[[File:Smalltalk-MimicLibrary-todolist.png | center]]
  
The following test case verifies "Add" function, we enter values into 3 field: <b>item name</b>, <b>priority</b>, and <b>date</b>, and click "Add" button. Then we inspect each ''listcell'' of a ''listitem'' to verify the result.
+
The following test case verifies "Add" function, we enter values into 3 fields: <b>item name</b>, <b>priority</b>, and <b>date</b>, and click "Add" button. Then we inspect each ''listcell'' of a ''listitem'' to verify the result.
  
 
'''TodoTest.java'''
 
'''TodoTest.java'''
  
<source lang="java" high="14,15,16,17,18,22">
+
<source lang="java" highlight="14,15,16,17,18,22">
  
 
public class TodoTest {
 
public class TodoTest {
Line 231: Line 268:
  
 
//add
 
//add
//itemName.as(TypeAgent.class).type("one-item");
+
//itemName.as(InputAgent.class).type("one-item");
 
itemName.type("one-item");
 
itemName.type("one-item");
 
priority.type("3");
 
priority.type("3");
Line 247: Line 284:
  
 
</source>
 
</source>
* The formal usage of <tt> TypeAgent </tt> is to retrieve from a <tt> ComponentAgent </tt>. (line 14)
+
* The formal usage of <code> InputAgent </code> is to retrieve from a <code> ComponentAgent </code>. (line 14)
 
* As seen in the previous example, this is also a shortcut method. (line 15)
 
* As seen in the previous example, this is also a shortcut method. (line 15)
* Although <tt> priority </tt> is an ''intbox'', we still provide a String as the parameter. The string will be parsed to an integer internally, if failed we'll get a exception. (line 16)
+
* Although <code> priority </code> is an ''intbox'', we still provide a String as the parameter. The string will be parsed to an integer internally, if failed we'll get an exception. (line 16)
* When typing in a Datebox, use the date format that you have specified in Datebox's "format" attribute. The same rule applies to ''timebox''.  (line 17)
+
* When typing in a <b>Datebox</b>, use the date format that you have specified in Datebox's "format" attribute. The same rule applies to ''timebox''.  (line 17)
 
* The query syntax means "retrieve a button whose label is 'Add'". (line 18)
 
* The query syntax means "retrieve a button whose label is 'Add'". (line 18)
* If we call <tt> ComponentAgent.query() </tt>, it'll only query the ComponentAgent's child components. Here, we find listitem to get listcell. (line 22)
+
* If we call <code> ComponentAgent.query() </code>, it'll only query the <b>ComponentAgent</b>'s child components. Here, we find <b>listitem</b> to get <b>listcell</b>. (line 22)
  
== Select ==
+
== SelectAgent ==
We keep using todo list application to demonstrate <tt> SelectAgent </tt> usage. To select a ''listitem'', we must to retrieve it first. The same rule applies to ''grid'' and ''combobox''. We should retrieve a ''row'' or ''comboitem'' before selecting them.
+
We'll keep using the todo list application to demonstrate <code> SelectAgent </code> usage. In this application, when selecting a ''listitem'', its value will be loaded to three different input fields for modification. The following test case steps verifies whether or not <b>listitem</b>'s data are correctly loaded into three input fields.
  
When selecting a listitem, its value will be loaded to three different input fields for modification. The following test case steps verify listitem's data is correctly loaded into 3 input fields.
+
To '''single''' select a ''listitem'', we must retrieve it first. The same rule applies to ''grid'' and ''combobox''. We should retrieve a ''row'' or ''comboitem'' before selecting them.  
  
 
'''TodoTest.java'''
 
'''TodoTest.java'''
  
<source lang="java" high="7">
+
<source lang="java" highlight="7">
  
 
public class TodoTest {
 
public class TodoTest {
Line 278: Line 315:
 
</source>
 
</source>
  
* Retrieve a ''listitem'' and use <tt> SelectAgent </tt> to select it.
+
* Retrieve a ''listitem'' and use <code> SelectAgent </code> to select it.
 +
 
 +
== RenderAgent ==
 +
 
 +
When a ''listbox'' (or ''grid'') uses a live data and is not in paging mold, it '''does not load all items at first''', It just pre-loads first few items. Until a user scrolls the scroll bar down, it loads and renders subsequent items. If we retrieve those un-rendered ''listitems'' in a test case, we will find its ''listcell'' containing no child components. This is one of ZK's optimizing behavior; to avoid loading large amount of data which might not be viewed by users.
 +
 
 +
Hence, before we retrieve components inside a ''listcell'', we should use Render operation to force ''listbox'' to load its content.
 +
 
 +
We use a simple case to demonstrate how to use this agent. This application contains a ''lisbox'' with 1000 ''listitems'' and has a simple logic: when a user selects an item, the label at the bottom will display the item's content. Each ''listcell'' has one child component ''label''.
 +
 
 +
[[File:Smalltalk-mimic-render.png | center]]
 +
 
 +
'''RenderTest.java'''
 +
 
 +
<source lang="java" highlight="9,10,13,14,16,17,19, 21,22,23,24">
 +
 
 +
@Test
 +
public void testRendererAgent() {
 +
DesktopAgent desktop = Zats.newClient().connect("/render.zul");
 +
 
 +
ComponentAgent listbox = desktop.query("#listbox");
 +
Label itemData = desktop.query("#itemData").as(Label.class);
 +
 
 +
//selecting first item works correctly
 +
listbox.getChild(1).as(SelectAgent.class).select();
 +
Assert.assertEquals("item0", itemData.getValue());
 +
 +
//select a non-rendered item
 +
listbox.getChild(1000).as(SelectAgent.class).select();
 +
Assert.assertEquals("", itemData.getValue());
 +
//get a non-rendered listcell, check it has no child.
 +
Listcell listcell = listbox.getChild(1000).getChild(0).as(Listcell.class);
 +
Assert.assertTrue(listcell.getChildren().size()==0);
 +
 
 +
listbox.as(RenderAgent.class).render(999, 999);
 +
 
 +
listbox.getChild(1000).as(SelectAgent.class).select();
 +
Assert.assertTrue("item999".equals(itemData.getValue()));
 +
listcell = listbox.getChild(1000).getChild(0).as(Listcell.class);
 +
Assert.assertTrue(listcell.getChildren().size()>0);
 +
}
 +
 
 +
</source>
 +
* Selecting the first item, item data label displays correctly. (line 9,10)
 +
 
 +
* Before using <code> RenderAgent </code> to render, label displays nothing when selecting the last item. (line 13,14)
 +
* We can also find that ''listcell'' has no children. (line 16,17)
 +
* <code> render() </code> could accept a range of items as parameters, and the index starts from zero. The last index of 1000 ''listitem'' is 999 but the last listitem's index of the ''listbox'' is 1000, index zero is ''listhead''. (line 19)
 +
* After rendering the last item, selecting it now works properly as expected while ''listcell'' now contains child components. Remember that '''you should retrieve the ''listcell'' again after rendering''' because original ''listitem'' component is already detached replaced with a new ''listitem''. (line 21-24)
 +
 
 +
== MultipleSelectAgent ==
 +
 
 +
The reason that ZATS Mimic provides a <code> MultipleSelectAgent </code> instead of combining it with the <code> SelectAgent </code> is to avoid confusion making it clearer to distinguish each action. As it is possible for an user to perform '''single selection''' in a ''listbox'' with attribute "multiple=true", If we only have one agent to select, it is hard to tell which kind of selecting a tester is referring to. Therefore, we designed two separate agents to reflect a tester's intention more precisely. With <code> MultipleSelectAgent </code>, you can <code> select() </code> and <code> deselect() </code>.
 +
 
 +
 
 +
The application we use to demonstrate this usage contains a ''listbox'' with attribute "multiple=true" at first and there is also a ''checkbox'' that can switch the ''listbox'' between single and multiple selection mode. When we select one or more items, the label at the bottom will display all selected item's label.
 +
 
 +
[[File:Smalltalk-mimic-multipleselect.png | center]]
 +
 
 +
 
 +
In the test case below, we perform three different tests;
 +
<ul>
 +
<li>'''multiple''' select in '''multiple''' selection mode</li>
 +
<li>'''single''' select in '''multiple''' selection mode</li>
 +
<li>'''multiple''' select in '''single''' selection mode</li>
 +
</ul>
 +
 
 +
'''MultipleSelectTest'''
 +
 
 +
<source lang="java" highlight="13,19,26,33,40">
 +
 
 +
@Test
 +
public void testAgent() {
 +
DesktopAgent desktopAgent = Zats.newClient().connect("/multiple-select.zul");
 +
 
 +
Label msg = desktopAgent.query("#msg").as(Label.class);
 +
Assert.assertEquals("", msg.getValue());
 +
 
 +
ComponentAgent listbox = desktopAgent.query("#lb");
 +
Assert.assertEquals(4, listbox.as(Listbox.class).getChildren().size()); // include header
 +
List<ComponentAgent> items = listbox.queryAll("listitem");
 +
 
 +
// listbox multiple selection
 +
items.get(0).as(MultipleSelectAgent.class).select();
 +
items.get(1).as(MultipleSelectAgent.class).select();
 +
items.get(2).as(MultipleSelectAgent.class).select();
 +
Assert.assertEquals("[i0, i1, i2]", msg.getValue());
 +
Assert.assertEquals(3, listbox.as(Listbox.class).getSelectedCount());
 +
 
 +
items.get(1).as(MultipleSelectAgent.class).deselect();
 +
Assert.assertEquals("[i0, i2]", msg.getValue());
 +
Assert.assertEquals(2, listbox.as(Listbox.class).getSelectedCount());
 +
items.get(0).as(MultipleSelectAgent.class).deselect();
 +
Assert.assertEquals("[i2]", msg.getValue());
 +
Assert.assertEquals(1, listbox.as(Listbox.class).getSelectedCount());
 +
 
 +
items.get(0).as(MultipleSelectAgent.class).deselect(); // should happen nothing
 +
Assert.assertEquals("[i2]", msg.getValue());
 +
Assert.assertEquals(1, listbox.as(Listbox.class).getSelectedCount());
 +
 
 +
//single select in multiple selection mode
 +
String[] values = { "[i0]", "[i1]", "[i2]" };
 +
for (int i = 0; i < 3; ++i) {
 +
items.get(i).as(SelectAgent.class).select();
 +
Assert.assertEquals(values[i], msg.getValue());
 +
}
 +
 +
//multiple select in single selection mode, throw an exception
 +
desktopAgent.query("#multipleMode").as(CheckAgent.class).check(false);
 +
try {
 +
items.get(0).as(MultipleSelectAgent.class).select();
 +
Assert.fail();
 +
}catch(RuntimeException e){
 +
System.out.println(e.getMessage());
 +
}
 +
}
 +
 
 +
</source>
 +
* With <code> MultipleSelectAgent </code>, we can mimic multiple selecting behavior. (line 13, 19)
 +
 
 +
* Deselect a non-selected item won't cause any error. (line 26)
  
== Render ==
+
* Under multiple selection mode, you can still use <code> SelectAgent </code> to perform single selecting. (line 33)
  
== Multiple Select ==
+
* But if you want to do multiple selecting under single selecting mode, you'll get a run-time exception. (line 40)
  
== Open ==
+
== OpenAgent ==
 
<!-- open a tree item -->
 
<!-- open a tree item -->
 +
<code> OpenAgent </code> is used to expand a ''treeitem'', ''listgroup'', ''detail'', ''bandbox'', ''groupbox'' or ''combobutton'' etc.
 +
 +
 +
Here we use a ''tree'' with binary structure to demonstrate this agent's usage. Each 'treeitem'' has two children.
 +
 +
[[File:Smalltalk-mimic-open.png | center]]
 +
 +
 +
In the test case below, we expand the first ''treeitem'' (node1) and its first child ''treeitem'' (node3), then first treeitem's visible item count should be 5. Before expanding, its visible item count is 1(only itself).
 +
 +
'''OpenTest.java'''
 +
 +
<source lang="java" highlight="3,6,10,11,14">
 +
 +
@Test
 +
public void testAgent() {
 +
DesktopAgent desktop = Zats.newClient().connect("/open-tree.zul");
 +
 +
ComponentAgent firstItem = desktop.query("#tree").query("treeitem");
 +
Assert.assertEquals(1, firstItem.as(Treeitem.class).getVisibleItemCount());
 +
 +
//open first item
 +
firstItem.as(OpenAgent.class).open(true);
 +
firstItem.query("treechildren").query("treeitem").as(OpenAgent.class).open(true);
 +
Assert.assertEquals(5, firstItem.as(Treeitem.class).getVisibleItemCount());
 +
 +
//collapse first item
 +
firstItem.as(OpenAgent.class).open(false);
 +
Assert.assertEquals(1, firstItem.as(Treeitem.class).getVisibleItemCount());
 +
}
 +
 +
</source>
 +
* Although there are two ''treeitem'', <code> query() </code> only returns the first one. (line 3)
 +
* Before expanding, its visible item count is one(only itself). (line 6)
 +
* After we expand the first ''treeitem'' and its first child item, its visible item count should now be five as shown in the image above. (line 8-9)
 +
* Call <code> open(false) </code> to collapse first ''treeitem'', and its visible item count comes back to one. (line 12)
 +
 +
== CloseAgent ==
 +
 +
Due to ZK component's specifications, only ''' ''panel'', ''tab'', and ''window'' '''supports this operation. Notice that when you close any of these components, the closed component is consequently detached from desktop by default. Of course, you can write event handler to override this behavior, for example to hide a component instead of detaching it.
 +
 +
[[File:Smalltalk-mimic-close.png | center]]
 +
 +
 +
The test below is quite simple, close each component and check if they are detached one by one.
 +
 +
<source lang="java" highlight="6,10,14,15">
 +
 +
@Test
 +
public void testAgent() {
 +
DesktopAgent desktopAgent = Zats.newClient().connect("/close.zul");
 +
 +
ComponentAgent panel = desktopAgent.query("panel[title='closable']");
 +
panel.as(CloseAgent.class).close();
 +
Assert.assertNull(((Component)panel.getDelegatee()).getPage());
 +
 +
ComponentAgent window = desktopAgent.query("window[title='closable']");
 +
window.as(CloseAgent.class).close();
 +
Assert.assertNull(((Component)window.getDelegatee()).getPage());
 +
 +
ComponentAgent tab = desktopAgent.query("tab[label='closable']");
 +
tab.as(CloseAgent.class).close();
 +
Assert.assertNull(desktopAgent.query("tab[label='closable']"));
 +
}
 +
 +
</source>
 +
* Close a closeable component with <code> CloseAgent.close() </code> When the component is detached, do not reuse the variables that references them. (line 6,10,14)
 +
 +
* After "tab" is detached,  you cannot retrieve from desktop. (line 15)
 +
 +
== More Agents ==
  
== Close ==
+
For more agents that are not covered in this small talk, please refer to [https://github.com/zkoss/zats/wiki/Operation-Agent-Usage Project wiki-Operation Agent Usage]
  
 
= Summary =
 
= Summary =
  
 +
ZATS Mimic 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. ZATS Mimic loads ZUL pages and runs test cases 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.
 +
 +
In the future, we plan to increase more user actions such as mouse over or next page and integrate with other frameworks in order to expand the usability of ZATS Mimic to fit more testing scenarios. We also welcome any feedback, please leave any comments here.
  
 
= Download =
 
= Download =
[? Example Project].
+
[http://www.zkoss.org/download/zats Download ZATS Mimic 1.0.0 Release]
 +
 
 +
[https://github.com/downloads/zkoss/zats/zats-example.20120516.zip Download Example Project]
 +
 
 +
[http://github.com/zkoss/zats/wiki Project wiki]
  
  

Latest revision as of 04:19, 20 January 2022

Shining ZATS Mimic

Author
Hawk Chen, Engineer, Potix Corporation
Date
April 25, 2012
Version
1.0.0

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 a 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 ZATS (ZK Application Test Suite) Mimic .

Since the previous freshly release (please refer to Small Talks/2012/April/The Dawn of ZK Application Test Suite:Mimic Library), we have updated initialized API with more support on user operations which would wider the testing scenarios of this library.

This project is mainly inspired by Georgi Rahnev of Telesoft Consulting GmbH, Vienna, along with a few other users and contributors.

No Server Test

ZATS Mimic enables developers 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 and use Mimic's utility class to interact components on ZUL and then, run the test case.

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 a agile development process.

The concept is as follows:

Smalltalk-ZatsMimicConcept.png

Testers write test cases to simulate user action such as clicking or typing with operation agents. Operation agent communicates with server emulator and triggers the composer's event handlers to change the component's status. Testers are able to check component's properties from the component agent to verify the result of user action. It might be a label changing its value or a listbox increased by one item. All 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 dependent on the application server won't work.
    Test cases run in simulated environment; all functions that require an application server do not work (e.g. JNDI, or JTA). If an AUT (Application Under Test) project adopts such container-provided services, it needs extra work to make them work normally out of a container, e.g. use Test Double like a fake object.
  • Cannot test browser’s behavior.
    In a ZK-based application, some behaviors are handled by a browser (JavaScript), e.g. popup menu or message dialog created at the client side. As server side is not aware of these behaviors, it cannot be verified.
  • 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.

Setup

Maven Project

If your project depends on ZK 5.0.x, add the following dependency:

    <dependency>
      <groupId>org.zkoss.zats</groupId>
      <artifactId>zats-mimic</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>

If your project depends on ZK 6.0.0, add the following dependency:

    <dependency>
      <groupId>org.zkoss.zats</groupId>
      <artifactId>zats-mimic-ext6</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>

Remember to also add dependencies of your preferred unit test framework, e.g. JUnit or TestNG.

Manually

Download the released zip file and add all jar under dist/lib and dist/lib/ext into your project's classpath. Note that please do not deploy these jars to your application server.

Also remember to add jar of your preferred unit test framework, e.g. JUnit.

Simple Application Under Test

We are going to introduce some basic concepts of ZATS Mimic with a simple application. This application only has one label with no content at first and one button. It only has one function: when a user clicks the button, the label shows "Hello Mimic" as the image below.

Smalltalk-mimic-hello.png


ZUL of our simple application

<zk>
	<window title="hello" border="normal" width="300px" apply="org.zkoss.zats.example.hello.HelloComposer">
		<label />
		<button label="Hello" />
	</window>
</zk>

Composer of our simple application

public class HelloComposer extends SelectorComposer {
	
	@Wire("label")
	Label label;
	
	@Listen("onClick = button")
	public void hello(){
		label.setValue("Hello Mimic");
	}
}

Write a Test Case

Steps to write a test case are as follows:

  1. Setup web application content path
  2. Create a client to connect to a ZUL
  3. Query a component
  4. Perform an operation on a component
  5. Verify result by checking a component’s property
  6. Tear down, stop server emulator

Hello Mimic Test

To present the basic usage of ZATS Mimic, I will demonstrate how to test a our simple application mentioned above.


Before diving into the source code of a test case, let me introduce some basic classes used in a test case.

Zats
It contains several utility methods to initialize and clean testing environment. By default, it starts server emulator with predefined web.xml and zk.xml bundled in ZATS Mimic's jar.
Client
Acts like a client to the server emulator and we use it to connect to a ZUL.
DesktopAgent
Wraps ZK Desktop object, we usually call its query() or queryAll() 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
ComponentAgent
Mimics a ZK component and determines which operation you can perform on it. We can also get ZK component property's value from it.
It also has query() which means to find targets among its child components.
OperationAgent (ClickAgent, InputAgent, SelectAgent...)
To mimic a user operation to a ZK component.
We name it "Agent" as it's not really the user operation itself, it's an agent to mimic user operation to a component.

Hello Test Case

We write the test case with JUnit 4 annotation, please refer to JUnit 4 in 60 seconds.

The following test case will mimic a user clicking the button and verify the label's value is "Hello Mimic" or not.

HelloTest.java"

//remove import for brevity
public class HelloTest {
	@BeforeClass
	public static void init() {
		Zats.init("./src/main/webapp"); 
	}

	@AfterClass
	public static void end() {
		Zats.end();
	}

	@After
	public void after() {
		Zats.cleanup();
	}

	@Test
	public void test() {
		DesktopAgent desktop = Zats.newClient().connect("/hello.zul");

		ComponentAgent button = desktop.query("button");
		ComponentAgent label = desktop.query("label");
		
		//button.as(ClickAgent.class).click();
		button.click();
		assertEquals("Hello Mimic", label.as(Label.class).getValue());
	}
}
  • Before starting a test, we have to call Zats.init() 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 initializes testing environment and starts the server emulator. (line 5)
  • Of course, we start the server emulator at @BeforeClass , we should stop it by Zats.end() at @AfterClass . (line 10)
  • We should call Zats.cleanup() to clear desktop before opening another ZUL. (line 15)
  • The first statement of a test case is to create a client and connect it to a ZUL page, like a browser visiting a ZUL. The connect() returns a DesktopAgent and we usually retrieve ComponentAgent from it to perform user operation. (line 20)
  • Before we can mimic a user action to a component, we should retrieve a ComponentAgent. Empowered by selector syntax, DesktopAgent .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 22)
  • As we do not 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 method as() will check for available operation for the target ComponentAgent . For example, you cannot type something in a Label, If you try to convert it to an unsupported operation agent, you will get an exception. (line 25)
  • For convenience, ComponentAgent provides shortcut methods for commonly used operations like click() . It automatically gets operation agent and calls it for you. (line 26)
  • To verify test result, we can also use ComponentAgent.as() to convert it as a ZK component then get its property by getter methods. (line 27)

Operation Agent Usage

In order to mimic user operation to ZK components, our library provides various operation agents. You can find all supported operations under package org.zkoss.zats.mimic.operation including check, click, close, focus, key stroke, select, multiple select, type, select by index, and render . At this stage. we support operations that are more commonly used and plan to keep adding more operations in the future.

All source code of following examples can be found in the example project. Download It

ClickAgent

ClickAgent helps us to mimic the clicking of a component for general intention; it is able to trigger onClick, onDoubleClick, or onRightClick events. Most user actions are done by clicking, but they might have different intentions. For example, clicking a listitem would represent selecting it, by clicking on a checkbox would represents checking the box and so on. Therefore, to avoid mixing several actions into clicking operations, specific actions has different corresponding operation agents. For example, if you wanted to select a listitem, use SelectAgent ,for checkbox, use CheckAgent . Which operation agent you choose to use would depend on the intention.

According to ZK Component Referenece, all components that inherit HtmlBasedComponent support click, double click, and right click.

ClickTest.java

public class ClickTest {

	//remove other methods for brevity

	@Test
	public void test() {
		DesktopAgent desktop = Zats.newClient().connect("/click.zul");

		ComponentAgent label = desktop.query("#mylabel");
		ComponentAgent eventName = desktop.query("#eventName");
		
		label.click();
		assertEquals("onClick", eventName.as(Label.class).getValue());
		
		label.as(ClickAgent.class).doubleClick();
		assertEquals("onDoubleClick", eventName.as(Label.class).getValue());
		
		label.as(ClickAgent.class).rightClick();
		assertEquals("onRightClick", eventName.as(Label.class).getValue());
	}
}
  • As mentioned in the previous section, it's a shortcut method for convenience. (line 12)
  • If you want to perform double click or right click, you have get ClickAgent first from ComponentAgent . (line 15,18)

InputAgent

We use a todo list application to demonstrate InputAgent usage. Here is the application's UI:

Smalltalk-MimicLibrary-todolist.png

The following test case verifies "Add" function, we enter values into 3 fields: item name, priority, and date, and click "Add" button. Then we inspect each listcell of a listitem to verify the result.

TodoTest.java

public class TodoTest {

	@Test
	public void test() {
		//visit the target page
		DesktopAgent desktop = Zats.newClient().connect("/todo.zul");

		//find components
		ComponentAgent itemName = desktop.query("textbox");
		ComponentAgent priority = desktop.query("intbox");
		ComponentAgent date = desktop.query("datebox");

		//add
		//itemName.as(InputAgent.class).type("one-item");
		itemName.type("one-item");
		priority.type("3");
		date.type("2012-03-16");
		desktop.query("button[label='Add']").click();
		
		//verify each listcell's label
		ComponentAgent listbox = desktop.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());
	}
}
  • The formal usage of InputAgent is to retrieve from a ComponentAgent . (line 14)
  • As seen in the previous example, this is also a shortcut method. (line 15)
  • Although priority is an intbox, we still provide a String as the parameter. The string will be parsed to an integer internally, if failed we'll get an exception. (line 16)
  • When typing in a Datebox, use the date format that you have specified in Datebox's "format" attribute. The same rule applies to timebox. (line 17)
  • The query syntax means "retrieve a button whose label is 'Add'". (line 18)
  • If we call ComponentAgent.query() , it'll only query the ComponentAgent's child components. Here, we find listitem to get listcell. (line 22)

SelectAgent

We'll keep using the todo list application to demonstrate SelectAgent usage. In this application, when selecting a listitem, its value will be loaded to three different input fields for modification. The following test case steps verifies whether or not listitem's data are correctly loaded into three input fields.

To single select a listitem, we must retrieve it first. The same rule applies to grid and combobox. We should retrieve a row or comboitem before selecting them.

TodoTest.java

public class TodoTest {
	@Test
	public void test() {
		//remove irrelevant code for brevity

		//update
		desktop.queryAll("listbox > listitem").get(0).as(SelectAgent.class).select();
		//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());
	}
  • Retrieve a listitem and use SelectAgent to select it.

RenderAgent

When a listbox (or grid) uses a live data and is not in paging mold, it does not load all items at first, It just pre-loads first few items. Until a user scrolls the scroll bar down, it loads and renders subsequent items. If we retrieve those un-rendered listitems in a test case, we will find its listcell containing no child components. This is one of ZK's optimizing behavior; to avoid loading large amount of data which might not be viewed by users.

Hence, before we retrieve components inside a listcell, we should use Render operation to force listbox to load its content.

We use a simple case to demonstrate how to use this agent. This application contains a lisbox with 1000 listitems and has a simple logic: when a user selects an item, the label at the bottom will display the item's content. Each listcell has one child component label.

Smalltalk-mimic-render.png

RenderTest.java

	@Test
	public void testRendererAgent() {
		DesktopAgent desktop = Zats.newClient().connect("/render.zul");

		ComponentAgent listbox = desktop.query("#listbox");
		Label itemData = desktop.query("#itemData").as(Label.class);

		//selecting first item works correctly 
		listbox.getChild(1).as(SelectAgent.class).select();
		Assert.assertEquals("item0", itemData.getValue());
		
		//select a non-rendered item
		listbox.getChild(1000).as(SelectAgent.class).select();
		Assert.assertEquals("", itemData.getValue());
		//get a non-rendered listcell, check it has no child.
		Listcell listcell = listbox.getChild(1000).getChild(0).as(Listcell.class);
		Assert.assertTrue(listcell.getChildren().size()==0);

		listbox.as(RenderAgent.class).render(999, 999);

		listbox.getChild(1000).as(SelectAgent.class).select();
		Assert.assertTrue("item999".equals(itemData.getValue()));
		listcell = listbox.getChild(1000).getChild(0).as(Listcell.class);
		Assert.assertTrue(listcell.getChildren().size()>0);
	}
  • Selecting the first item, item data label displays correctly. (line 9,10)
  • Before using RenderAgent to render, label displays nothing when selecting the last item. (line 13,14)
  • We can also find that listcell has no children. (line 16,17)
  • render() could accept a range of items as parameters, and the index starts from zero. The last index of 1000 listitem is 999 but the last listitem's index of the listbox is 1000, index zero is listhead. (line 19)
  • After rendering the last item, selecting it now works properly as expected while listcell now contains child components. Remember that you should retrieve the listcell again after rendering because original listitem component is already detached replaced with a new listitem. (line 21-24)

MultipleSelectAgent

The reason that ZATS Mimic provides a MultipleSelectAgent instead of combining it with the SelectAgent is to avoid confusion making it clearer to distinguish each action. As it is possible for an user to perform single selection in a listbox with attribute "multiple=true", If we only have one agent to select, it is hard to tell which kind of selecting a tester is referring to. Therefore, we designed two separate agents to reflect a tester's intention more precisely. With MultipleSelectAgent , you can select() and deselect() .


The application we use to demonstrate this usage contains a listbox with attribute "multiple=true" at first and there is also a checkbox that can switch the listbox between single and multiple selection mode. When we select one or more items, the label at the bottom will display all selected item's label.

Smalltalk-mimic-multipleselect.png


In the test case below, we perform three different tests;

  • multiple select in multiple selection mode
  • single select in multiple selection mode
  • multiple select in single selection mode

MultipleSelectTest

	@Test
	public void testAgent() {
		DesktopAgent desktopAgent = Zats.newClient().connect("/multiple-select.zul");

		Label msg = desktopAgent.query("#msg").as(Label.class);
		Assert.assertEquals("", msg.getValue());

		ComponentAgent listbox = desktopAgent.query("#lb");
		Assert.assertEquals(4, listbox.as(Listbox.class).getChildren().size()); // include header
		List<ComponentAgent> items = listbox.queryAll("listitem");

		// listbox multiple selection
		items.get(0).as(MultipleSelectAgent.class).select();
		items.get(1).as(MultipleSelectAgent.class).select();
		items.get(2).as(MultipleSelectAgent.class).select();
		Assert.assertEquals("[i0, i1, i2]", msg.getValue());
		Assert.assertEquals(3, listbox.as(Listbox.class).getSelectedCount());

		items.get(1).as(MultipleSelectAgent.class).deselect();
		Assert.assertEquals("[i0, i2]", msg.getValue());
		Assert.assertEquals(2, listbox.as(Listbox.class).getSelectedCount());
		items.get(0).as(MultipleSelectAgent.class).deselect();
		Assert.assertEquals("[i2]", msg.getValue());
		Assert.assertEquals(1, listbox.as(Listbox.class).getSelectedCount());

		items.get(0).as(MultipleSelectAgent.class).deselect(); // should happen nothing
		Assert.assertEquals("[i2]", msg.getValue());
		Assert.assertEquals(1, listbox.as(Listbox.class).getSelectedCount());

		//single select in multiple selection mode
		String[] values = { "[i0]", "[i1]", "[i2]" };
		for (int i = 0; i < 3; ++i) {
			items.get(i).as(SelectAgent.class).select();
			Assert.assertEquals(values[i], msg.getValue());
		}
		
		//multiple select in single selection mode, throw an exception
		desktopAgent.query("#multipleMode").as(CheckAgent.class).check(false);
		try {
			items.get(0).as(MultipleSelectAgent.class).select();
			Assert.fail();
		}catch(RuntimeException e){
			System.out.println(e.getMessage());
		}
	}
  • With MultipleSelectAgent , we can mimic multiple selecting behavior. (line 13, 19)
  • Deselect a non-selected item won't cause any error. (line 26)
  • Under multiple selection mode, you can still use SelectAgent to perform single selecting. (line 33)
  • But if you want to do multiple selecting under single selecting mode, you'll get a run-time exception. (line 40)

OpenAgent

OpenAgent is used to expand a treeitem, listgroup, detail, bandbox, groupbox or combobutton etc.


Here we use a tree with binary structure to demonstrate this agent's usage. Each 'treeitem has two children.

Smalltalk-mimic-open.png


In the test case below, we expand the first treeitem (node1) and its first child treeitem (node3), then first treeitem's visible item count should be 5. Before expanding, its visible item count is 1(only itself).

OpenTest.java

	@Test
	public void testAgent() {
		DesktopAgent desktop = Zats.newClient().connect("/open-tree.zul");

		ComponentAgent firstItem = desktop.query("#tree").query("treeitem");
		Assert.assertEquals(1, firstItem.as(Treeitem.class).getVisibleItemCount());
		
		//open first item
		firstItem.as(OpenAgent.class).open(true);
		firstItem.query("treechildren").query("treeitem").as(OpenAgent.class).open(true);
		Assert.assertEquals(5, firstItem.as(Treeitem.class).getVisibleItemCount());
		
		//collapse first item
		firstItem.as(OpenAgent.class).open(false);
		Assert.assertEquals(1, firstItem.as(Treeitem.class).getVisibleItemCount());
	}
  • Although there are two treeitem, query() only returns the first one. (line 3)
  • Before expanding, its visible item count is one(only itself). (line 6)
  • After we expand the first treeitem and its first child item, its visible item count should now be five as shown in the image above. (line 8-9)
  • Call open(false) to collapse first treeitem, and its visible item count comes back to one. (line 12)

CloseAgent

Due to ZK component's specifications, only panel, tab, and window supports this operation. Notice that when you close any of these components, the closed component is consequently detached from desktop by default. Of course, you can write event handler to override this behavior, for example to hide a component instead of detaching it.

Smalltalk-mimic-close.png


The test below is quite simple, close each component and check if they are detached one by one.

	@Test
	public void testAgent() {
		DesktopAgent desktopAgent = Zats.newClient().connect("/close.zul");
		
		ComponentAgent panel = desktopAgent.query("panel[title='closable']");
		panel.as(CloseAgent.class).close();
		Assert.assertNull(((Component)panel.getDelegatee()).getPage());
		
		ComponentAgent window = desktopAgent.query("window[title='closable']");
		window.as(CloseAgent.class).close();
		Assert.assertNull(((Component)window.getDelegatee()).getPage());
		
		ComponentAgent tab = desktopAgent.query("tab[label='closable']");
		tab.as(CloseAgent.class).close();
		Assert.assertNull(desktopAgent.query("tab[label='closable']"));
	}
  • Close a closeable component with CloseAgent.close() When the component is detached, do not reuse the variables that references them. (line 6,10,14)
  • After "tab" is detached, you cannot retrieve from desktop. (line 15)

More Agents

For more agents that are not covered in this small talk, please refer to Project wiki-Operation Agent Usage

Summary

ZATS Mimic 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. ZATS Mimic loads ZUL pages and runs test cases 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.

In the future, we plan to increase more user actions such as mouse over or next page and integrate with other frameworks in order to expand the usability of ZATS Mimic to fit more testing scenarios. We also welcome any feedback, please leave any comments here.

Download

Download ZATS Mimic 1.0.0 Release

Download Example Project

Project wiki



Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.