Table of Contents
LzUnit
is OpenLaszlo's implementation of the xUnit testing framework, enabling automated unit testing of OpenLaszlo 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
includes a TestCase
will run all of its child TestCase
s, then report the number of test cases run, the number of failures (plus error messages), and the number of runtime errors.
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 occurrence of <debug>.
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>
, the test method must be registered to the test case via a call to addTest
. You must place all the calls to addTest
in a method named addTests
. The addTests
method is called by the TestSuite
when it runs the TestCase. 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 52.1. LzUnit simple case
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<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
">
assertFalse(mytext.multiline, "This is not a multiline text field")
</method
>
<method
name
="addTests
">
this.addTest("testsuccess");
this.addTest("testfailure");
this.addTest("testerror");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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 lz.Idle
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 52.2. Sequential execution of tests
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<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
>
<method
name
="addTests
">
this.addTest("testfirst");
this.addTest("testsecond");
this.addTest("testthird");
</method
>
</class
>
<TestSuite
>
<syncrun
/>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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 52.3. Deferred execution of tests
<canvas
debug
="true
" width
="100%
">
<debug
x
="100
" y
="150
"/>
<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
>
<method
name
="addTests
">
this.addTest("testanim");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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 52.4. setUp and tearDown
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<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.setAttribute("xpath", 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
>
<method
name
="addTests
">
this.addTest("testsettext");
this.addTest("testBumpChild");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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 52.5. Chaining tests
<canvas
height
="300
" width
="100%
" 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.debug('--- test complete ---');
</method
>
<method
name
="addTests
">
this.addTest("testSimpleXAnim");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2007, 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
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.
In the late '80s, the Talking Moose on my Macintosh SE recited at startup the "waterfall" development model taught in a first-year computer science class: "Problem statement. Analysis. Algorithm. Implementation. Testing." From that model, I learned the frustration of perpetual coding and debugging, and returned to journalism school for good.
Somehow I ended up at a software company as a tester (which I still think is odder than fiction). I often wonder when my lack of skill and experience will catch up with me, but a co-worker suggested some reading that changed my thinking, helped me become a more useful colleague, and even served to get me out of bed earlier in the morning: Test-Driven Development by Example by Kent Beck, in which Beck teaches this development cycle:
Write a failing automated test before writing any code
Pass the test by any means necessary
Remove duplication
According to Beck, test-driven development gives a programmer courage. When the end of a difficult programming task is nowhere in sight, bringing one failing or "red" test to "green" signifies one concrete step forward.
Test-driven development demands thoughtful design. In the "waterfall" model, testing is the last phase, so programmers stumble through the implementation phase, not certain that the code will fulfill the requirements because it is untested. In test-first development, the programmer must be accurate and specific about what the code is meant to accomplish, and design a test for that before going further.
The strongest chess players play their best moves at the end of the game. The players who study the opening find that they drift into fearful territory, while the players proficient at endgames grow in confidence. Those endgame-savvy chess players are like "test-infected" developers who worked on the last phase first: they always know where they're headed.
A similar analogy: A well-trained chess student should be coached to play moves that are foolish at the start -- as practice for difficult situations in the future. Beck instructs that at the start of the TDD cycle to write a test that fails. Red in the opening, green in the endgame.
The computer science instructors who stress the "Big Design Up Front" methodology would probably require that I understand TDD at a professional level before I write this. The test-driven methodology, on the other hand, says that if each code example takes a tiny step while adhering to test-driven principles, I can write with confidence.
The aim of this work is fourfold:
To demonstrate test-driven development through the construction of simple OpenLaszlo applications;
To build upon the OpenLaszlo developers' guide section about LzUnit, the XUnit framework for OpenLaszlo;
To complete the LzUnit-related documentation tasks assigned to me in the OpenLaszlo bug reporting database, so I can think of this as actual work;
To learn.
Many folks seem to be certain that a writer's life is mystical and arcane, but Beck, a software engineer, understands it completely. I knew Beck was speaking my language when he wrote in JUnit Pocket Guide: "Writers write. Testers test." In Test-Driven Development, Beck cut through the knot that has buried my programming education since the beginning. "Take teeny tiny steps," he said.
By taking the smallest steps possible in program development, it is easier to step backward if necessary. Experienced developers, said Beck, benefit from taking small steps because they can always increase their size, but if they began with large steps, they wouldn't know if smaller steps were appropriate.
The smallest step possible in OpenLaszlo is initializing the canvas
, the view
at the foundation of every OpenLaszlo application. Many programming tutorials start by demonstrating a stub application that
compiles and runs successfully, but doesn't actually do anything. In OpenLaszlo, that would be:
However, that stub is too simple to break, so it can't be a useful example in the test-driven development model.
Red -- Write a test that doesn't compile (the LzUnit console runs red);
Green -- Make the test green by any means, no matter how inelegant or distasteful (Beck recommends faking it, if necessary);
Refactor -- Bring the test to respectability by removing duplication.
I tried a beginning programming problem in a test-driven manner: 1. Display series of numbers in an infinite loop. The program should quit if someone hits a specific key.
We need two things: a while loop to display numbers in the debugger, and a button to terminate the loop. If we make the infinite loop first, we'll have to pull the plug to stop it, so we should make the button first.
Stop!
Before writing any code, write a failing test.
XUnit is a testing framework that Beck created in 1994, which evolved first into SUnit for Smalltalk. He and Erich Gamma (author of Design Patterns) modified XUnit for Java while sharing a flight to a developers' conference three years later. JUnit became the best known of the XUnit family; there are XUnit frameworks for C++, C#, Python, Fortran, Perl, Visual Basic, and others, including OpenLaszlo.
The lightweight XUnit contains three classes and 12 methods. "Never in the field of software development was so much owed by so many to so few lines of code," said object-oriented design authority Martin Fowler.
In test-driven development, we devise a successful test case first, and then we fail it (because we wrote the test first). We want the first button click to change "stop" to "go", and the second click to "stop". The easiest solution, I think, is to give the button a "go" attribute which is a Boolean, where its initial state is "false".
Sometimes I think I most often revisit the OpenLaszlo developers' guide Chapter 29, Methods, Events, Handlers, and Attributes. This attribute is a simple one, though: we'll instantiate an instance of the button class, and assign it an attribute named "go", of the type boolean, with two values: "go" or "stop" according to the boolean expression "true" or "false".
The first test is to check for the button's initial state. I am a lazy bum; every programming task I do starts with this LzUnit test template (I even use the antiquated trick of putting a space at the start of its filename so it shows up at the top of the Open... dialog):
Example 52.8. Test stub
<canvas debug="true"> <debug y="150"/> <include href="lzunit"/> <simplelayout axis="y" spacing="10"/> <TestSuite> <TestCase> <method name="test"> </method> <method name="addTests"> this.addTest("test"); </method> </TestCase> </TestSuite> </canvas>
This is an empty canvas (it will compile and run, resulting in a blank canvas plus the debugger window and the LzUnit output
console; the simplelayout
tag separates the LzUnit console from the visual objects). The script's inclusion of the LzUnit directory enables us to create
instances of the TestSuite
class, which binds any number of instances of TestCase
. TestCases include the Assert
classes, which are the basis for unit testing in the XUnit framework. The TestCase method that makes the assertions must
have "test" at the start of its name, so I make that part of the template.
Example 52.9. testGoButtonTrue
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- Copyright 2001-2008 Laszlo Systems, Inc. -->
When we compile and run that, it's red, of course, because there's no code to test, but the test-driven cycle of red-green-remove_duplication becomes an addiction; the programmer's confidence and courage is bolstered every time each small step goes from red to green.
The TestCase method name must be descriptive. If a test applies to a numbered bug in the Laszlo bug database, I'll name the file LPP-nnnn accordingly, but the TestCase method should always describe the basis of the test.
AssertTrue(goButton.go)
asks, "Is the 'go' attribute of the 'goButton' instance true?". We could also assertEquals(true, goButton.go)
, which asks the same question, but here I want to stress the boolean nature of goButton.go.
The button code:
Example 52.10. testGoButtonTrue
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<button
name
="goButton
" width
="100
" text
="Go
">
<attribute
name
="go
" type
="boolean
" value
="true
"/>
</button
>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- Copyright 2001-2008 Laszlo Systems, Inc. -->
When this compiles, the test runs green, and that is an accomplishment.
Our button is set to "go". Now we need an onclick
method for "stop" -- that is, to set the "go" attribute to false. The LzUnit testing framework doesn't enable us to test
the mouseclick; integration testing tests functionality, which comes after the unit testing phase.
On a whim, I thought to enable the button to stop and start the loop. Without an LzUnit option to test the button's function, I tested the button method in the debugger window:
Example 52.11. testGoButtonTrueFalse
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<button
name
="goButton
" width
="100
" text
="Go
">
<attribute
name
="go
" type
="boolean
" value
="true
"/>
<handler
name
="onclick
">
if (goButton.go==true) {
this.setAttribute('text', 'Stop');
this.setAttribute('go', false);
Debug.debug("%w should be 'false'", this.go);
}
else {
this.setAttribute('text', 'Go');
this.setAttribute('go', true);
Debug.debug("%w should be 'true'", this.go);
}
</handler
>
</button
>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
A while statement attached to (goButton.go==true) would loop infinitely, or until a buttonclick set the "go" attribute to
false, but how to test for infinity? Maybe it's something the developer has to take on faith, but JavaScript does have its
limit: Number.MAX_VALUE
is the largest number JavaScript can represent. The while statement is while (goButton.counter < Number.MAX_VALUE)
.
I am not sure if it is good style to declare "counter" as a button attribute, but I think that must be better than initializing
the counter variable on the canvas (with the script <handler name="oninit">var goButton.counter=1;</handler>
). Before adding the code for the while loop and the code for the button attribute, there's a test to write: Does the goButton
have a attribute "displayed" that equals 1?
Example 52.12. testGoButtonCounter
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<button
name
="goButton
" width
="100
" text
="Go
">
<attribute
name
="go
" type
="boolean
" value
="true
"/>
<handler
name
="onclick
">
if (goButton.go==true) {
this.setAttribute('text', 'Stop');
this.setAttribute('go', false);
}
else {
this.setAttribute('text', 'Go');
this.setAttribute('go', true);
}
</handler
>
</button
>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertEquals(1, goButton.counter);
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
The test runs red because the counter attribute hasn't been written. (Also, the button's test for true/false was removed because it served its purpose).
Example 52.13. testGoButtonCounter
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<button
name
="goButton
" width
="100
" text
="Go
">
<attribute
name
="go
" type
="boolean
" value
="true
"/>
<attribute
name
="counter
" type
="number
" value
="1
" when
="once
"/>
<handler
name
="onclick
">
if (goButton.go==true) {
this.setAttribute('text', 'Stop');
this.setAttribute('go', false);
}
else {
this.setAttribute('text', 'Go');
this.setAttribute('go', true);
}
</handler
>
</button
>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertEquals(1, goButton.counter);
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
That runs green. Here's the while loop:
Example 52.14. testGoButtonWhile
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<button
name
="goButton
" width
="100
" text
="Go
">
<attribute
name
="go
" type
="boolean
" value
="true
"/>
<attribute
name
="counter
" type
="number
" value
="1
" when
="once
"/>
<handler
name
="onclick
"><![CDATA[
if (goButton.go==true) {
this.setAttribute('text', 'Stop');
this.setAttribute('go', false);
while (goButton.counter<Number.MAX_VALUE) {
Debug.debug("%w", goButton.counter);
counter=counter+1;
}
}
else {
this.setAttribute('text', 'Go');
this.setAttribute('go', true);
}
]]>
</handler
>
</button
>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertEquals(1, goButton.counter);
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
One of the maxims of test-driven development is "do the simplest thing that works". This LZX script satisfies the programming
problem "display numbers infinitely", but the compiled application does not work! An infinitely looping Debug.debug
statement fills up memory, and results in the browser choking. We need the script to Debug.debug
, pause for breath, Debug.debug
, pause for breath, and so on. The answer is in the global object lz.Idle
, which I have never used. The problem now presents a challenge to learn something new about the language!
How do you test for the idle state? Doesn't the testing itself mean the universe isn't idle?
Writing the test first, my best guess is that we're checking for this.idle
:
Example 52.15. testIdle
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<TestSuite
>
<TestCase
>
<method
name
="testIdle
">
assertTrue(this.idle);
</method
>
<method
name
="addTests
">
this.addTest("testIdle");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- Copyright 2001-2008 Laszlo Systems, Inc. -->
We expect a newly-written test to fail, but in this instance, I don't know if it'll ever pass, or how to make it pass in the red-green-refactor cycle.
I copied some code out the developers' guide Chapter 17, Layout and Design from the example Building a 'floating view'
:
Example 52.16. startDraggingFloater method
<method name="startDraggingFloater"> this.d = new LzDelegate(this, "adjustFloaterPosition", lz.Idle, "onidle"); this.gm = new LzDelegate(this, "cancelFloater", lz.GlobalMouse, "onmouseup"); </method> <method name="adjustFloaterPosition"> this.f.setX(canvas.getMouse("x")-this.x_offset); this.f.setY(canvas.getMouse("y")-this.y_offset); </method>
Then modified it for simplicity and our purpose:
Example 52.17. testIdle
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<handler
name
="oninit
">
this.wake();
</handler
>
<method
name
="wake
">
foo = new LzDelegate(this, "sleep", lz.Idle, "onidle");
</method
>
<method
name
="sleep
" args
="v
">
Debug.debug("Sleeping");
</method
>
<TestSuite
>
<TestCase
>
<method
name
="testIdle
">
assertTrue(this.idle);
</method
>
<method
name
="addTests
">
this.addTest("testIdle");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- Copyright 2001-2008 Laszlo Systems, Inc. -->
I wasn't surprised to find that the assertion failed, but I was delighted to discover that after the TestSuite ran, the universe
went to an idle state, causing the sleep
method to write "Sleeping" repeatedly. The idle state itself is an "infinite loop"!
Here's a problem. The test-driven development routine says "no new code without a new test", but I still don't know how to
test for the idle state, and since the wake
and sleep
methods will be folded into goButton's onclick method, that still falls under the integration testing umbrella.
If we break the rules and plow ahead, the while loop is removed (because the idle state replaces it) and the && goButton.counter<Number.MAX_VALUE
condition moves into the if
statement:
Example 52.18. testGoButtonCount
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<button
name
="goButton
" width
="100
" text
="Go
">
<attribute
name
="go
" type
="boolean
" value
="true
"/>
<attribute
name
="counter
" type
="number
" value
="1
" when
="once
"/>
<handler
name
="onclick
"><![CDATA[
if (goButton.go==true) {
if (goButton.counter<Number.MAX_VALUE) {
foo = new LzDelegate(this, "count", lz.Idle, "onidle");
this.setAttribute('text', 'Stop');
this.setAttribute('go', false);
}
}
else {
Debug.debug("Paused");
this.setAttribute('text', 'Go');
this.setAttribute('go', true);
}
]]>
</handler
>
<method
name
="count
" args
="v
">
Debug.debug("%w", counter);
counter=counter+1;
</method
>
</button
>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertEquals(1, goButton.counter);
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
It almost works! The problem now is that even though the "Stop" button sets goButton.go to false, the lz.Idle function is
still active, and triggers the count
method.
The solution is also found in the Layout and Design example. Idling calls the method startDraggingFloater
, and its terminating condition is cancelFloater
:
Example 52.19. cancelFloater method
<method name="cancelFloater"> this.gm.unregisterAll(); this.d.unregisterAll(); this.f.destroy(); </method>
The cancelFloater method demonstrates how to unregister the lz.Idle delegate with unregisterAll()
. In our application, we will unregister the idle method when goButton is set to false:
Example 52.20. testGoButtonCount
<canvas
debug
="true
" width
="100%
">
<debug
y
="150
"/>
<include
href
="lzunit
"/>
<simplelayout
axis
="y
" spacing
="10
"/>
<button
name
="goButton
" width
="100
" text
="Go
">
<attribute
name
="go
" type
="boolean
" value
="true
"/>
<attribute
name
="counter
" type
="number
" value
="1
" when
="once
"/>
<handler
name
="onclick
"><![CDATA[
if (goButton.go==true) {
if (goButton.counter<Number.MAX_VALUE) {
foo = new LzDelegate(this, "count", lz.Idle, "onidle");
this.setAttribute('text', 'Stop');
this.setAttribute('go', false);
}
}
else {
foo.unregisterAll();
Debug.debug("Paused");
this.setAttribute('text', 'Go');
this.setAttribute('go', true);
}
]]>
</handler
>
<method
name
="count
" args
="v
">
Debug.debug("%w", counter);
counter=counter+1;
</method
>
</button
>
<TestSuite
>
<TestCase
>
<method
name
="testGoButtonTrue
">
assertEquals(1, goButton.counter);
assertTrue(goButton.go);
</method
>
<method
name
="addTests
">
this.addTest("testGoButtonTrue");
</method
>
</TestCase
>
</TestSuite
>
</canvas
>
<!-- * X_LZ_COPYRIGHT_BEGIN ***************************************************
* Copyright 2008 Laszlo Systems, Inc. All Rights Reserved. *
* Use is subject to license terms. *
* X_LZ_COPYRIGHT_END ****************************************************** -->
The application works! I won't call this a complete success, though, because the development was not wholly test-driven. Perhaps the next example will be.
Copyright © 2002-2010 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.