Subnavigation

QSTestLib revisited

Last year I posted a simple library for unit testing with Qt Script, called QSTestLib. Since then I didn't really do much with it. Until recently, that is, when I got some feedback and suggestions from people actually using it, and this inspired me to make some simple but hopefully useful improvements.

The first one was to make QSTestLib work with SpiderMonkey (i.e. Firefox), which is a step towards making it a general-purpose auto-test library for JavaScript. Admittedly this was very low-hanging fruit, since the only Qt Script-specific code is the parsing of the (non-standard) "stack" property of error objects to figure out the location of test failures. (Unfortunately JavaScriptCore doesn't provide a similar "stack" property, so QSTestLib currently can't provide failure locations when run in WebKit-based browsers.)

Another change was needed to make QSTestLib work with browsers; the log functions used the built-in print() function to generate output, the critical assumption being that print() is a function that writes its arguments to standard output. This assumption doesn't hold if you call print() in a browser (guess what happens). ;) So the log functions now use QTest.writeln, which you can redefine to be any function. This is quite powerful since you can use it to redirect the output to wherever:

QTest.writeln = function(text) { output += text; output += 'n'; };
output = "";
QTest.exec( {
name: "Simple test",
add: function() { qCompare(1+1, 3); },
subtract: function() { qCompare(2-1, 1); }
});

After which the contents of the global variable output will be the string

********* Start testing of Simple test *********
FAIL! : add : Compared values are not the same:
Actual: 2
Expected: 3
Loc: sillytest.js(5)
PASS : subtract
Totals: 1 passed, 1 failed, 0 skipped
********* Finished testing of Simple test *********

The next improvement is the addition of a test logger that produces XML in the same format as the C++ QTestLib (with QTestLib you get XML output by passing -xml or -lightxml when running the auto-test). Example:

QTest.logger = QTest.xmlLogger;
QTest.exec( {
name: "Simple test",
add: function() { qCompare(1+1, 3); }
});

which produces

<?xml version="1.0" encoding="ISO-8859-1"?>
<testcase name="Simple test">
<testfunction name="add">
<incident type="fail" file="sillytest.js" line="4">
<description>< ![CDATA[Compared values are not the same:
Actual: 2
Expected: 3]]></description>
</incident>
</testfunction>
</testcase>

This means that you (or an automated test system) can readily process the results of script-based tests and C++-based tests in the same way.

The next improvement is the addition of a default HTML generator. Combined with the addition of QTest.writeln, a test can be run in a webpage:

<html>
<head><title>My test</title></head>
<body>
<script src="qstestlib.js"></script>
<script src="mytest.js"></script> <!-- the test case (defines qTestcase object) -->
<script>
QTest.logger = QTest.htmlLogger;
QTest.logger.light = true; // don't produce <html> tag etc.

QTest.writeln = function(text) { document.write(text); document.write("n"); };

QTest.exec(qTestcase);
</script>
</body>
</html>

You can run the above test yourself by clicking here. That test runs against the latest version of QSTestLib. Which brings me to the next "improvement": QSTestLib is now hosted on Qt Labs. There's also a project for it on Google Code where you can report issues and create suggestions.

Speaking of suggestions; the next improvement was suggested by a user (thanks Nathan!), and is the ability to provide qCompare() with a custom compare function. This allows you to define the semantics of a comparison yourself, i.e. where the standard qCompare() behavior is not appropriate. Example:

QTest.exec( {
name: "Simple test",
add: function() { qCompare(1+1, 3, function(a, b) { return (a+1 == b); }); }
});

With our own twisted comparison rule in place, the test will now pass. Neat.

By the way, in case you're not happy with any of the built-in loggers, here's the beginning of a custom logger:

QTest.logger = {
compareFail: function(actual, expected, testFunction) {
QTest.writeln(testFunction + " failed, which means that everything is NOT peachy!");
}
};

Now when you execute the testcase, you'll get something like

add failed, which means that everything is NOT peachy!

Sometimes that's all the information you really need. Anyway, have fun writing those auto-tests!


Blog Topics:

Comments