Monday, February 26, 2007

JavaScript unit tests


Athena has had Nit for some time now. Nit comes in handy when code which interacts in complex ways with the runtime needs to be tested (that is, when you really want Firefox running your code, to make sure your code works with Firefox, because Firefox does something ridiculously insane that you need to account for). This isn't really the common case for unit testing, though. In the common case, you want nice, cleanly factored units, each with a well defined test suite with nice, simple tests which don't require a web browser to run.




A little while ago jml threw together an xUnit implementation for JavaScript to include in Nevow with Athena. Here's a sample of the result:


// Copyright (c) 2006 Divmod.
// See LICENSE for details.

/**
* Tests for L{Mantissa.ScrollTable.PlaceholderModel}
*/

// import Divmod.UnitTest
// import Mantissa.ScrollTable

Mantissa.Test.TestPlaceholder.PlaceholderTests = Divmod.UnitTest.TestCase.subclass(
'Mantissa.Test.TestPlaceholder.PlaceholderTests');
Mantissa.Test.TestPlaceholder.PlaceholderTests.methods(
/**
* Set up a placeholder model with an initial placeholder which extends as far
* as C{totalRows}.
*
* @type totalRows: integer
* @rtype: L{Mantissa.ScrollTable.PlaceholderModel}
*/
function setUp(self) {
var totalRows = 5;
self.model = Mantissa.ScrollTable.PlaceholderModel();
self.model.registerInitialPlaceholder(totalRows, null);
},


/**
* Set up a placeholder model with as many placeholders are there are entries
* in C{ranges}, each start and stop indices equal to the first and second
* entries in the corresponding pair.
*
* @param ranges: pairs of [start index, stop index]
* @type ranges: sorted C{Array} of C{Array}
*
* @rtype: L{Mantissa.ScrollTable.PlaceholderModel}
*/
function createPlaceholderLayout(self, ranges) {
var model = Mantissa.ScrollTable.PlaceholderModel();
for (var i = 0; i < ranges.length; i++) {
model._placeholderRanges.push(
model.createPlaceholder(ranges[i][0], ranges[i][1], null));
}
return model;
},

/**
* Test L{Mantissa.ScrollTable.PlaceholderModel.createPlaceholder}
*/
function test_createPlaceholder(self) {
var p = self.model.createPlaceholder(0, 1, null);

self.assertIdentical(p.start, 0, "expected a start of 0");
self.assertIdentical(p.stop, 1, "expected a stop of 1");
self.assertIdentical(p.node, null, "expected a null node");

p = self.model.createPlaceholder(5, 11, 6);

self.assertIdentical(p.start, 5, "expected a start of 5");
self.assertIdentical(p.stop, 11, "expected a stop of 11");
self.assertIdentical(p.node, 6, "expected a node of 6");
},

// ...
);


These can be run with trial, although the integration still has a couple obvious defects:


exarkun@boson:~$ trial xmantissa.test.test_javascript
Running 1 tests.
subunit
RemotedTestCase
test_createPlaceholder ... [OK]
test_dividePlaceholder ... [OK]
test_findFirstPlaceholderIndexAfterRowIndexMultiplePlaceholders ... [OK]
test_findFirstPlaceholderIndexAfterRowIndexMultiplePlaceholders2 ... [OK]
test_findFirstPlaceholderIndexAfterRowIndexMultiplePlaceholders3 ... [OK]
test_findFirstPlaceholderIndexAfterRowIndexMultiplePlaceholders4 ... [OK]
test_findFirstPlaceholderIndexAfterRowIndexMultiplePlaceholders5 ... [OK]
test_findFirstPlaceholderIndexAfterRowIndexOnePlaceholderNeg ... [OK]
test_findFirstPlaceholderIndexAfterRowIndexOnePlaceholderNeg2 ... [OK]
test_findPlaceholderIndexForRowIndexMultiplePlaceholders ... [OK]
test_findPlaceholderIndexForRowIndexMultiplePlaceholdersNeg ... [OK]
test_findPlaceholderIndexForRowIndexOnePlaceholder ... [OK]
test_registerInitialPlaceholder ... [OK]
test_removedRow ... [OK]
test_removedRowNeg ... [OK]
test_replacePlaceholder ... [OK]

-------------------------------------------------------------------------------
Ran 16 tests in 2.435s

PASSED (successes=16)
exarkun@boson:~$



This kind of testing means faster development cycles, better code factoring, and a more reliable final product.