Overview
gaia-email-libs-and-more currently uses a largely custom testing framework right now that was built for the deuxdrop project based on experiences trying to test Thunderbird.
Concepts
Asynchronous Steps
Each test is organized into one or more asynchronous steps. Each step runs until all of the asynchronous operations in the step are completed. Each step has a timeout; if the timeout fires before the asynchronous operations complete, the step fails and the test case fails and the test driver framework just tries to run the cleanup code for a test.
There are a few different types of test step; the delineation was just made to try and provide meta-data that could help automated tooling or those reading the tests. The TestContext class defines the following methods:
- setup(): A test step that is just trying to set up the test; these kinds of steps should usually pass unless something is very broken in the system.
- convenienceSetup(): A setup step that is automatically generated by testing support code rather than explicitly written to be part of the test. For example, when our test involves talking to an IMAP server, setting up the account happens automatically.
- action(): A test step that's trying to do something. Currently there is no concept of a 'convenienceAction', although action steps can be marked as "boring" so the UI displayed them with less contrast.
- check(): A test step that's just trying to check the state of the system and not perform any actions. Currently there is no concept of a 'convenienceCheck', although check steps can be marked as "boring".
- cleanup(): Performing shutdown operations where we either care to make sure that the shutdown happens for correctness reasons or where we are trying to clean-up after resources that won't be automatically destroyed. These steps currently will be performed if we abort the test to provide them a chance to perform necessary cleanup, but we might want to stop doing that.
- convenienceCleanup(): like convenienceSetup, a cleanup step that is automatically generated.
- convenienceDeferredCleanup(): a convenienceCleanup that is added to the list of steps after the defining function finishes running. Usually a function that calls convenienceSetup() will also call convenienceDeferredCleanup() to ensure its cleanup function is invoked at the right time.
Test steps are defined/declared as 'test definition' time. Let's look at the boilerplate for a test:
// (this is global code that is run when the JS file is evaluated; but we use
// AMD-style modules, so the body of our module is not actually evaluated
// until our module is require()d)
define(['rdcommon/testcontext', './resources/th_main',
'exports'],
function($tc, $th_main, exports) {
// This is module-level code that is run when the module is evaluated
var TD = exports.TD = $tc.defineTestsFor(
{ id: 'test_blah' }, null, [$th_main.TESTHELPER], ['app']);
TD.commonCase('I am a test', function(RT) {
// This is test case definition code, it is run when we want to set up our
// test. Our calls to T.check/T.action/etc. produce test steps as a
// byproduct of our being run.
T.action('action 1', function() {
// I run asynchronously. I will not run until after the defining function
// has fully completed running.
});
T.action('action 2', function() {
// I do not run until 'action 1' has completed in its entirety, including
// any asynchronous operations it was waiting for.
});
// actions can also be created in a loop, or by other functions. The calls
// to T.action() just need to complete before this function returns.
}); // (end 'I am a test')
}); // (end module 'define' call)
Code is run like so:
- The 'I am a test' function runs, defining one or more test steps. In this case, it defines 'action 1' and 'action 2'.
- 'action 1' is run. We do not advance to the next test step until all tracked asynchronous operations complete or we time out.
- 'action 2' runs after 'action 1' and all of its asynchronous operations have completed.
- the test is over!