Central Classes

To understand how Scrunitizer works, you need to know how the most important interfaces and classes interact.

Tests

The test is the fundamental concept in unit testing. A test is something that handles unit testing. Two basic subtypes of a test have to be distinguished:
  1. Test cases
  2. Test suites
Test cases are entities that actually execute a single unit test. They test if a member function works as excepted, they check if a function returns the right result or they check that an object behaves correctly in extreme cases.

Test suite group together test cases. If some test cases check functionality that is closely related, they could be put in the same test suite. Beside the grouping functionality, it is also important that test suites are simply containers for other tests, regardless if they are test cases or other test suites. It is important to know that when a test is added to a test suite, the suite owns the test. That means that the suite also deletes the test when the suite is destroyed.
Test suites are also a must because many classes in Scrunitizer simply operate on tests. So if more than one test instance should be handled, they must be combined in a test suite. (See also the Composite Pattern.)

Test Manager

A test manager is an entity that
  1. conducts the execution of tests and that
  2. informs test observers about the progress of a test session.
This already highlights a test managers main responsibilities. On one hand, it ensures that all tests are executed in a well defined way, makes sure that exceptions get caught and properly handled and collects tests results. On the other hand, it notifies test observers about test outcomes and the progress of the test session - the act of executing all tests that belongs directly or indirectly to the root test suite.

Test Observers

Test observers are the third import family of classes of Scrunitizer. A test observer - as the name suggests - observes test outcomes and the progress of test session. A test observer is informed by the test manager if something substantial has happened. This covers the begin and end of a test session and a single test (test cases and test suites) as well as the occurrence of exceptions. To be able to notify an observer, the test manager must know about an observer. So observer must be registered (added) to the test manager before a test session is started.

Test Execution

To write proper test cases, it is necessary to understand how test are executed and how this is done via the Test interface.

Test Phases

A test is spitted up in three phases:
  1. Test set-up. In this phase, everything is done to ensure that the actual test could run. That could mean that variables are initialised with object references, input files are opened or OS resources are acquired.
    After execution of this step, the "test fixture" should be prepared so that the test could be run without problems that are not related to the test itself. This is an important point. A test that should add values that are stored in a file could fails at least for tow reasons. First, the test produces the wrong result and second the file which contains the values could not be opened. So both cases need to be distinguished! If the purpose of the test is to check if the correct result is produced, the problem of opening the value file should not go in the test itself. This task must be done in the set-up phase.
  2. Test execution. Here, the real test action should happen. Typically this involves testing results with assertion macros or throwing test failures if some mandatory post condition is not fulfilled. In case the test does not produce the expected outcome, an test failure is thrown (a special exception class).
  3. Test tear-down. Here the test fixture that was set up in the first phase is torn down. That means that all resources acquired for the test are released and the fixture environment is clean (i.e. files opened for testing are now closed).
Each of those three phases is seen as executed successfully if they simply return. If a problem occurs, a test exception (test failure or test error) must be thrown.

Control Flow

The control flow is depicted below:

    First, the test set-up is executed.

      1: If the set-up fails (an exception was thrown), the test comes immediately to the end. Neither the test action nor the tear-down is executed.

      2: If the set-up was successful, the test is executed. Also here two possibilities, failure or success. But because setting up the test fixture was already successfully executed, the tear-down function is always invoked.

        a: If also the test execution was successful, a normal tear down is done.

        b: If the test has failed, a premature test tear-down is executed. At test interface level no distinction is made between normal and premature tear-down. Only the test phase is called differently.

Test Phase Isolation

It is absolutely essential to know that the TestManager isolates each test phase completely from other test phases (of the same test and other tests). So if an exception is thrown during execution of a test phase, only this phase is stopped immediately. Other test phases are unaffected by this exception; they are executed due to the test execution schema described above.

Test Life Cycle

Note that executing test this way is quite similar to the life cycle of C++ objects. First, the constructor is called (set-up). If that fails (because an exception was thrown), the object is not created and the destructor is not called. If construction succeeds, the object can be used (test) and the destructor is called (tear-down) when the object is destroyed, regardless if using the object was successful or not.

It is also important to remember that this execution logic applies to test cases and test suites! That means that if the set-up of a suite fails, none of its contained tests are executed. But it also means that if one or more of the contained tests fail, the suite is properly torn down anyway.

Test Construction and Destruction

Note that this test execution schema has also serious impact on the usage of constructors and destructors. Because test set-up and tear-down is done in dedicated functions, constructors and destructors of test objects should not do anything that could fail! Test constructors and destructors are not called under the control of the test manager. They are normally called when the test case and test suite objects are created and destroyed. But at that point in time, the test manager does not have the control over test execution. So the exceptions are not caught and handled properly. In effect, that means that test objects have quite often empty constrictors and destructors.

The Scrunitizer C++ Unit Test Framework
by Bernd Linowski

[Scrunitizer]  [Overview]  [Cookbook]  [Download]  [Index]  [Linosphere]

Page generated: 1 Nov 2000
(C) by Bernd Linowski
scrunitizer@linosphere.de