Test system changes
Submitted by alex on Tue, 03/06/2007 - 01:03.
Dojo 0.9 jettisons JUM (the JSUnit Test Manager), originally written by Mark Anderson for Burstlib and later adopted by Dojo as the basis of our command-line unit test suite. In its place is a new unit test harness, D.O.H., which borrows code and ideas from JUM while bringing the system into line with the goals of 0.9; namely removal of magical interfaces and automagic naming.
While designed to run in both browser and command line environments, JUM was not aware of the Dojo package system and therefore was unable to be run without the assistance of a pre-generated testRunner.js file or the Dojo build system. D.O.H. assumes the availability of the Dojo package system, thereby allowing tests to be run without need of the build tools at all. This should simplify the process of running tests for many contributors.
D.O.H. also includes a browser-based test runner which can execute all of the command-line tests as well as browser-specific tests. No special logic is needed by test authors other than a new requirement that tests be registered with their parent module. HTML test files must be registered through their browser-specific JS files and must adhere to a boilerplate format that allows for easy reporting to the hosting test harness.
Examples of this file layout are available in the core
The tests.register() method accepts the function signatures of any of the other registration functions and determines the correct underlying function to dispatch registration to.
The contents of a typical, command-line-only, test file might look something like:
tests/ directory.
Test Registration Changes
Files containing tests are no longer automatically discovered by file name and functions which implement tests are no longer discovered by function name. Instead, the following APIs are being introduced to register tests:doh.registerTest(group, testFuncOrObj); doh.registerTests(group, testFuncOrObjArr); doh.registerTestNs(nsObj, objName); doh.registerTestUrl(url); doh.register(...);
// file located in core at:
// tests/moduleToBeTested.js
dojo.provide("tests.moduleToBeTested");
dojo.require("doh.runner");
doh.register("tests.moduleToBeTested",
[
// single test, no test fixture
function assertTrueTest(){
doh.assertTrue(true);
doh.assertTrue(1);
doh.assertTrue(!false);
},
// a test fixture
{
name: "thingerTest",
setUp: function(){
this.thingerToTest = new Thinger();
this.thingerToTest.doStuffToInit();
},
runTest: function(){
doh.assertEqual("blah", this.thingerToTest.blahProp);
doh.assertFalse(this.thingerToTest.falseProp);
// ...
},
tearDown: function(){
}
},
// ...
]
);
In this example, we see a variant of the test system that uses the doh.addTests() style of add() and registers both independent tests and fixture-driven tests. Note that we give the functions that are registered names even though we could easily provide anonymous functions. This allows the system to more correctly report on what went wrong and where, allowing you to talk more intelligently about your tests passing or failing. The fixture-based example uses the "name" property to provide the same information.
Assertion Function Changes
JUM exposed the following APIs (and permutations thereof) for determining success or failure within any given test:D.O.H. exposes an analgous set of APIs, but each API only has one calling mode:jum.assertEquals(expected, actual); jum.assertEquals(testName, expected, actual); jum.assertTrue(condition); jum.assertTrue(testName, condition); jum.assertFalse(condition); jum.assertFalse(testName, condition);
In general, the new function names should clarify test files.doh.assertEqual(expected, actual); // aliased to: doh.is(e, a) doh.assertTrue(condition); // aliased to: doh.t(condition) doh.assertFalse(condition); // aliased to: doh.f(condition)
Fake DOM Removal
The Dojo 0.9 test manager no longer provides the JsFakeDom implementation. If you need to test a DOM implementation, please run the tests in an environment which can provide one and use the new support for fixtures to provide a DOM for your test group(s).Asynchronous Tests
Unlike the previous test tool, D.O.H. provides direct support for asynchronous test cases. Writing asynchronous tests depends on a script context that "knows about" asynchronous execution (aka, a browser but not Rhino) and a slightly modified test authoring syntax:
doh.register("tests.moduleToBeTested",
// the async text fixture
{
name: "thingerTest",
timeout: 2000, // 2 seconds, defaults to half a second
setUp: function(){
this.thingerToTest = new Thinger();
this.thingerToTest.doStuffToInit();
},
runTest: function(){
var testCondition = true;
var d = new doh.Deferred();
setTimeout(function(){
try{
if(testCondition){
d.callback(true);
}else{
d.errback(new Error("we got a failure"));
}
}catch(e){
d.errback(e);
}
}, 100);
return d;
}
}
);
Note that in the above example, the runTest function explicitly returns a doh.Deferred object. This is how the system knows that you are going to be testing potentially asynchronous conditions. Also, in our delayed call (see the setTimeout), we explicitly catch errors and pass them to the Deferred's errback() function. It is expected that your code will do this if you are testing asynchronous conditions. Lastly, you may specify a timeout in milliseconds as part of the fixture object.
As with the previous examples, you can specify an anonymous or single function in place of a the full fixture used here to handle asynchronous cases. The only caveat is that it must return a Deferred object in order to be treated as an async test. We can also simplify the above by using the tests.Deferred classes getTestCallback() method. Here's a simplified async test case:
doh.register("tests.moduleToBeTested", function simplerAsyncTest(){
var testCondtition = true;
var d = new doh.Deferred();
var checkCondition = function(){
doh.assertTrue(testCondition);
};
setTimeout(d.getTestCallback(checkCondition), 100);
return d;
});
While the test case still needs to pass back a Deferred object, the use of the getTestCallback() to wrap the success or failure test function allows us to stop manually handling exceptions that might be thrown in the callback function, specifically from assertTrue(), assertEqual(), or assertFalse().
Group Registration
Many times, it's advantageous to register an entire group of tests at once. D.O.H. provides a method for doing this as well as for registering group-levelsetUp and tearDown methods. The tests.registerGroup(name, tests, setUp, tearDown) method lets you handle this in a single call:
// file located in core at:
// tests/fullGroupTest.js
dojo.provide("tests.fullGroupTest");
dojo.require("tests.runner");
doh.registerGroup("tests.fullGroupTest",
[
// single test, no test fixture
function assertTrueTest(t){ t.t(true); },
// string variant of the same:
"doh.t(true);",
// test that uses variable set up by group
function assertTrueTest(t){
t.t(tests.fullGroupTest._localVariable);
},
// ...
],
function(){ // setUp
tests.fullGroupTest._localVariable = true;
},
function(){ // tearDown
tests.fullGroupTest._localVariable = false;
}
);
Note that when using registerGroup, setUp and tearDown replace existing group-level handlers, but the registered tests are additive to any pre-existing tests registered for the group.
The above example also introduces yet another shorthand for writing tests, the string-only test. This style of test authoring is particularly terse. Tests written this way do not provide explicit fixture names and so the test code itself is used as the test name in reporting. In these tests, there is also always a variable t which is an alias to the global tests variable. This allows for very compact tests to be written in the form:
[
"doh.t(true);",
"doh.f(!true);",
"doh.is('thinger', 'thing'+'er')",
// ...
]
With group registration, this style of test authoring requires very little typing. Just mind your string quotes!
URL-based Testing
Being developed explicitly to test JavaScript applications, D.O.H. includes features for browser-based test harnesses to load sub-documents which may run a set of tests explicitly on a browser-provided DOM. This lets you automate UI testing and isolate browser-specific bugs by writing tests once and quickly running them through the unified test harness UI. To support this, browser runtimes for D.O.H. provide an implementation fortests.registerUrl(groupName, url). On other environments, this may be a no-op.
A real example from Dojo Core:
doh.registerUrl("tests._base.NodeList", dojo.moduleUrl("tests", "_base/NodeList.html"));
This example uses Dojo to normalize the tested URL with relationship to the loading code, but you can just as easily specify a full URL manually. Just be aware that in order for D.O.H. to be able to record the results of tests from this page, it must be hosted on the same domain as the hosting test harness.
But what does the page itself look like? Here's a snapshot of the page referenced above:
<html>
<head>
<title>testing dojo.NodeList</title>
<script type="text/javascript" src="../../dojo.js"
djConfig="isDebug: true"></script>
<script type="text/javascript">
dojo.require("doh.runner");
dojo.addOnLoad(function(){
doh.register("t",
[
function ctor(){
var nl = new dojo.NodeList();
nl.push(dojo.byId("c1"));
doh.assertEqual(1, nl.length);
},
// ...
]
);
doh.run();
});
</script>
</head>
<body>
<h1>testing dojo.NodeList</h1>
<div id="t">
<span id="c1">c1</span>
</div>
</body>
</html>
The above code has several important features (in bold). First, we ensure that the test system itself is loaded into the tested page. Without this, the tests won't run. D.O.H. is smart enough to know if it's being loaded into a child frame or as a parent document. If you load this file into a normal browser window, the tests will still run, but you won't get the pretty D.O.H. chrome or audio feedback. Instead, the results of only the tests from this page will be sent to whatever console facility is available.
The second important feature of our tested URL is that it manually calls tests.run(), in this case after the page has been loaded and tests have been registered (a good time to do it). There are Dojo-isms in the test page, but they don't affect the important bits of the system. You can still load the test system with <script> tags and hard-wired URLs and this file would participate in the larger test group correctly.
Since testing on loaded pages may take a long time (relatively), a default timeout of 10 seconds per URL is provided. If your tested page requires more (or less) time, you can pass an explicit timeout parameter to the tests.registerUrl method:
doh.registerUrl("tests._base.NodeList",
dojo.moduleUrl("tests", "_base/NodeList.html"),
5000); // 5000ms, or 5 seconds
Running the tests in Rhino
Here are some instructions for running the tests in Rhino. Ideally they should run in any Rhino version 1.6R4 or later, however it has only be verified to work with the custom_rhino.jar in Dojo's util repository (it is in util/buildscripts/lib). Steps:> svn svn co http://svn.dojotoolkit.org/dojo/view/anon/all/trunk dojo_0.9 > cd dojo_0.9 > cd util/doh > java -jar ../buildscripts/lib/custom_rhino.jar runner.js
- Printer-friendly version
- Login or register to post comments
- Subscribe post
