Table of Contents
LzUnit
is OpenLaszlo's implementation of the xUnit testing framework. LzUnit enables automated unit-testing of Laszlo
applications and libraries.
The functionality provided by the LzUnit framework is essentially comprised of two public classes —
TestCase
and TestSuite
. Each TestSuite
contains one or more children that are instances of TestCase
. An LZX program that
consists of a <TestSuite>
will, when loaded, automatically run all of its child TestCase
s and report the number of test cases run, the number of failures, and the number of runtime errors. If any
failures occur, an obvious error message is presented.
The unit testing code is not a part of the Openlaszlo Runtime Library; you must explicitly include it using <include href="lzunit"/>.
The <lzunit>
library has a <debug
y="500"/> in it. If you would like to see the debugger elsewhere (as in the examples below), assign it a different "y" value.
Put the <debug> before the include statement. (The compiler ignores all but the first occurence when it
sees two debug tags.)
An LzUnit test case is generally defined as an instance of TestCase
or its subclass.
LzUnit test cases contain one or more logical tests that are represented by corresponding methods on the
<TestCase>
. In order for a test to be recognized by and added to the enclosing
<TestSuite>
, its name must start with "test
". Within each test, you use the standard
assert
methods of the xUnit API to define the checkpoints that will form the basis of your test.
Below is an example of typical usage of the LzUnit framework that shows a success, a failure, and a runtime error.
XXX
()
Example 51.1. LzUnit example
<canvas debug="true" height="600"> <debug y="500"/> <include href="lzunit"/> <TestSuite> <TestCase> <attribute name="prop1" value="'foo'" when="once"/> <text name="mytext" width="200" bgcolor="blue" text="LzUnit example"/> <method name="testsuccess"> assertEquals(prop1, 'foo') </method> <method name="testfailure"> assertTrue(mytext.multiline, "This is not a multiline text field") </method> <method name="testerror"> callFoo() </method> </TestCase> </TestSuite> </canvas>
In this example, as is the case with any tests that include visual elements, you do not see the text field because it was positioned below the test status panel by the layout which controls them.
Note | |
---|---|
You must explicitly tell the compiler to include the LzUnit library in your code by using the
|
Also, when writing a test suite, remember to enable the debugger, as is done in the example above, in order for runtime
errors to be detected. If you would like to see debugger warnings, but do not want them to affect results of the test run,
you should set a global flag catchErrors
to false
in a <script>
tag.
By default, the order of execution of individual tests within a test case is not guaranteed. That is, every test
is executed at the next idle event (see the LzIdle
documentation for details), and thus
tests that take longer to execute may finish after those tests that were started later but do not need as much time to complete.
If it is important that tests are run in the order they are written, set the global flag asynchronousTests
to false
,
as in the following example.
Example 51.2. Sequential execution of tests
<canvas debug="true" height="400"> <debug y="80"/> <include href="lzunit"/> <script> asynchronousTests = false </script> <class name="syncrun" extends="TestCase"> <attribute name="counter" value="1" when="once"/> <method name="testfirst"> assertEquals(1, counter++) </method> <method name="testsecond"> assertEquals(2, counter++) </method> <method name="testthird"> assertEquals(3, counter++) </method> </class> <TestSuite> <syncrun/> </TestSuite> </canvas>
In many cases, you will encounter that the objects that your test case incorporates are not ready for inspection by the time it runs. Some of the common examples of this scenario are tests that include animation, loading of remote data or media, and instantiation of classes declared with deferred initialization stages. The approach illustrated by the example below involves waiting for the event that signals completion of a specific action, and putting the checkpoint in the event handler method.
Example 51.3. Deferred execution of tests
<canvas debug="true" height="800"> <debug x="100" y="280"/> <include href="lzunit"/> <TestSuite> <TestCase> <view name="redbox" bgcolor="red" width="50" height="50"> <animator name="anm" attribute="y" from="200" to="-400" duration="1000" start="false" relative="false" onstop="parent.parent.checkStatus()"/> </view> <method name="testanim"> redbox.anm.doStart() </method> <method name="checkStatus"> assertEquals(-400, redbox.y) </method> </TestCase> </TestSuite> </canvas>
Sometimes it is necessary to reset execution environment to its initial state before running every test in a single
test case. The way to do so is to override the setUp()
and, optionally,
tearDown()
methods of TestCase
(the default
implementation of these methods is a no-op). These methods are called before and after each of the test methods are
invoked. Here's an example of their use.
Example 51.4. setUp and tearDown
<canvas debug="true" height="800"> <debug y="500" /> <include href="lzunit"/> <dataset name="places"> <country name="U.S."> <state code="CA" capitol="Sacramento"> <city>San Francisco</city> <city>Los Angeles</city> <city>San Diego</city> </state> <state code="NJ" capitol="Trenton"> <city>Newark</city> </state> <state code="NY" capitol="Albany"> <city>Buffalo</city> <city>New York City</city> </state> </country> </dataset> <TestSuite> <TestCase> <attribute name="start" value="'places:/country[1]'"/> <datapointer name="nav" xpath="$once{parent.start}"/> <method name="setUp"> // Move pointer to first state in selected country nav.selectChild() </method> <method name="tearDown"> // Reset pointer to starting position nav.setXPath(start) </method> <method name="testsettext"> assertTrue(nav.selectChild()) assertTrue(nav.selectNext()) var oldText = nav.getNodeText() nav.setNodeText('Oakland') assertEquals('Oakland', nav.xpathQuery('text()')) nav.setNodeText(oldText) assertEquals(oldText, nav.getNodeText()) </method> <method name="testBumpChild"> assertTrue(nav.selectNext(2)) assertEquals(2, nav.getNodeCount()) assertTrue(nav.selectChild()) nav.deleteNode() assertFalse('Buffalo', nav.getNodeText()) assertTrue(nav.selectParent()) assertEquals(1, nav.getNodeCount()) </method> </TestCase> </TestSuite> </canvas>
Consider the case of a testSuite that has two tests for animation. The test will report that it hasn't failed *before* the
test is complete. This is to be expected, since the animator's doStart()
returns right away, and the test progress bar fills up as soon as the test method returns. But that does not invalidate the
test results, since failures will still be clearly reported. To see that this is so, try inserting assertTrue(false)
after the first assertion and see what happens.
If you are testing animation you need to start your second test from the callback of the first. Results are often different when you run animations sequentially or simultaneously.
If you declare a sequential animatorgroup around the animators, you can chain the checkpoints, without having to start the individual animators, as shown below.
Example 51.5. Chaining tests
<canvas height ="300" width="400" debug="true"> <debug y="80"/> <include href="lzunit"/> <view> <view id="bluebox" bgcolor="blue" width="30" height="30"> <animatorgroup name="AG1" attribute="x" process="sequential" start="false" relative="false"> <animator from="0" to="100" duration="2000" onstop="canvas.animatorsuite.animatorcase.checkSimpleXAnim()"/> <animator from="0" to="200" duration="3000" onstop="canvas.animatorsuite.animatorcase.checkSimpleXAnim2()"/> </animatorgroup> </view> </view> <TestSuite name="animatorsuite"> <TestCase name="animatorcase"> <method name="testSimpleXAnim"> bluebox.AG1.doStart() </method> <method name="checkSimpleXAnim"> assertEquals(100, bluebox.x); </method> <method name="checkSimpleXAnim2"> assertEquals(200, bluebox.x); Debug.write('--- test complete ---'); </method> </TestCase> </TestSuite> </canvas>
Of course, running both animators simultaneously will superimpose the effect of each on the other and the tests will fail; this is true of any simultaneous animators applied to the same attribute of the same object.
Copyright © 2002-2007 Laszlo Systems, Inc. All Rights Reserved. Unauthorized use, duplication or distribution is strictly prohibited. This is the proprietary information of Laszlo Systems, Inc. Use is subject to license terms.