Sitemap | Contact

Company Evaluations Purchase Support What´s new

Testwell CTA++

C++ Test Aider

Information in this document corresponds to CTA++ version 3.0.3

1. INTRODUCTION

CTA++ is a tool for unit testing C++ classes, libraries and subsystems. CTA++ is simple to use and provides very powerful features helping the tester to build the testing environments and running the tests on C++ code. The testing process becomes efficient, visible and organized - as required in a professional testing environment.

The Object Oriented (OO) paradigm of C++, while being efficient to use, also introduces engaging technical challenges. CTA++ addresses those challenges. Also, the CTA++ technical implementation uses C++ OO features in a novel way, resulting in very powerful capabilities that are astonishingly simple to use. The test code developed for the base class can be reused in testing derived classes.

CTA++ is an ideal tool to build and execute flexible, yet powerful test suites on C++ code. Also C code can be tested.

Here are some quick links to some of the CTA++ capabilities:

On all supported platforms CTA++ can be used as a command line based tool. On Windows platform CTA++ can additionally be used straight from the Visual Studio GUI, see more from CTA++ integration to Visual Studio.

CTA++ is used for building testing environments, test beds, for the code under test (C++ classes, libraries, subsystems, APIs) and then executing the tests with the test bed. The CTA++ architecture can be illustrated as follows:

    CTA++ architecture
Figure: CTA++ architecture

Tests are executed with the test bed program. It consists of a user-written test driver (+ stub units as needed), of the code under test, and of the CTA++ run-time support library. When constructing the test driver and the stubs the powerful test-oriented services of the CTA++ run-time support library are utilized. On Windows platform portions of the test driver and the stub files can be generated by CTA++/VSI, only the essential "test-logic" needs to be written by the user.

On the one hand, full C++ power can be used as the scripting language. Thus, even though the code under test has whatever C++ constructs, like templates, they can be fully tested with CTA++. On the other hand, thanks to the services of the CTA++ run-time support library, the tester is saved from developing much of the auxiliary test harness code, like implementing the notion of a test case, making the test runs visible, assertion mechanism,  actual and expected value reporting in assertion failures, unhandled exceptions catching and reporting, PASS/FAIL bookkeeping, feeding test data to the test bed, stub behavior control, multithread testing support, etc. In writing the test driver the tester can concentrate on the essential, on the testing of his or her code with easy-to-use high level test-oriented constructs. The very powerful  and readily usable test execution infrastructure comes from CTA++.

There are separate utilities for generating CTA++ stubs from header files (ctastub) and for converting textual test bed execution trace files to convenient HTML format (cta2html).

2. CTA++ FEATURES

Here we study some of the CTA++ key features in more detail.

2.1. C++ as the Scripting Language

Full power of C++ can be used in writing the test main program (test script). The services of CTA++ run-time library (introduced via the cta.h file) are utilized in writing the test script. The CTA++ services are a collection of macros, classes, utility functions and models for arranging the testing, all helping tremendously the test main program writing.

2.2. The CTA++ Model of Testing Arrangements

Testing with CTA++ is based on test-case functions, driven by a test-driver object.

The test driver is a supervisor to the test-case functions. It is created in the test main program and the test-case functions are registered in it. The test driver executes the test cases and keeps track of the events taking place in the tests.

2.3. Assertions

Assertions are a means for checking that the execution of a test had the effect that the tester expected it to have. CTA++ has powerful support for assertions, for example the following can be asserted
  • A boolean expression has the value true
  • Two comparable values (expressions), designating the actual and the expected value, are in specified a relation with each other (==, !=, >, >=, etc.)
  • An actual value is within an expected range or near enough to an expected value
  • Two strings or memory areas are equal
  • Execution of a code block or a single expression throws a specified exception, or executes without throwing any exception
  • Specified stubs get called exactly in the given order
A failed assertion is reported in the trace file. Then, automatically, also the actual and expected values are displayed. Here is an example how three failed assertions are shown in the trace file.
255: CTA_ASSERT_EQ(pool2.nbr_of_items(), 20)
 *** assertion failure, line 255 in file myscript.cc
     actual  : 0
     expected: 20

256: CTA_ASSERT_EXCEPTION(pool2.add(itm2), Pool::overfill_exception)

 *** assertion failure, line 256 in file myscript.cc
     actual  : no exception
     expected: exception Pool::overfill_exception

266 CTA_ASSERT_STUBORDER(foo_stub << foo_stub << bar_stub)
     ... // some test script execution, and later
 *** assertion failure, line 34 in file myscript.cc
     actual  : stub call to bar_stub
     expected: stub call to foo_stub

CTA++ keeps a record of the executed assertions. A failed assertion or an unhanded exception turns the test case to FAIL state.

2.4. Trace of Test Runs, Visibility of Testing

The purpose of testing is to make experiments with the code under test with the intent of finding errors. In CTA++ assertions are the primary mechanism for automatically revealing potential errors.

It is also desirable to get the testing visible and automatically documented. A track of the following is interesting: date and time when the tests were executed, which test script was used, which test cases were executed, which test calls and assertions were done in the test cases, did the test cases PASS or FAIL, etc. CTA++ produces this information to the trace file.

In many cases the application uses symbolic names (either by #define or enum) for various integral values. CTA++ can display integral values (for example in assertion failures) using symbolic names, thus making the reporting more useful (vs. 'magic' integral constants would be displayed).

Not always a full trace is wanted. Sometimes a more compact view of the test results is more appropriate. CTA++ supports this by tester-controllable verbosity modes on how the trace file is to be written:

  • Verbose mode. All the information that a script requests to be written out is written to the trace file. Also the test case begins and ends together with the test case PASS/FAIL result is reported.
  • Brief mode. Only test case begins and ends together with test case PASS/FAIL result is written to the trace file. Possible assertion failures and unhandled exceptions are reported.
  • Summary mode. Only one line per test case is written to the trace file. The line contains the test case identification and PASS/FAIL outcome.
  • Silent mode. No trace file is written. However, a program return code is returned to the operating system level revealing if the whole test run was a PASS or FAIL.

With the cta2html utility the trace file can be converted to an HTML browsable form. In that representation and with only a few mouse clicks you can view the test session in summary level, zoom-in/zoom-out some specific test case for seeing the detailed trace level, view the actual C++ script file of the test case, and view the test data file that provided the input values to the test case. The HTML representation uses color-coding for highlighting the test failures and for identifying the trace output coming from different threads.

2.5. Command-Line Parameterization of Test Runs

When a CTA++ test bed program is invoked from the operating system command level, it recognizes a few command-line parameters by which the test bed run can be further parameterized. The following three important CTA++ command line capabilities are discussed here:

Running test cases selectively:

When test cases are registered for running they are assigned an id. By default all registered test cases are run. Based on test case ids you can request that only the specified test cases will be run at this time. It is also possible to request a single test case to be run multiple times.

Overriding test case parameters:

A test case function can have a parameter. The default value for this parameter is determined when the test case is registered into the test driver. However, along with running the test cases selectively from the command line, you can override the parameter values for the test cases, if needed. This can be useful when interactively experimenting how the code under test behaves with different data values.

Running the test session in different verbosity modes:

The trace file verbosity mode can be specified when the test bed is invoked.

Now, putting all these features together the following use scenario becomes possible:  First run all the registered test cases. Take Summary mode trace. See which test cases failed. Then run the test bed again, but now selecting the failed test cases only and take the trace in Verbose mode. Study the detailed trace file for figuring out the problems. Finally, further tests can be made with the problematic test cases by giving them modified parameter values from the command line. Of course, the test bed can be run under a debugger, too.

2.6. Test Data, Test Data File

CTA++ System provides many useful means for writing the test main program (test script). One such means is the CTA_TestData type. It is a class, which encapsulates the notion of test data in a very flexible and easy to use manner. CTA++'s test data could be (somewhat simplifyingly) viewed as a struct that can have any number of data members of any type. In the test script the test data items can be conveniently extracted from the CTA_TestData object with the >> operator.

One of the test case function parameter variants is of the type CTA_TestData. When such a test case is explicitly invoked from the command line, the CTA_TestData aggregate can also be given.

A test case function (having a CTA_TestData parameter) can also be registered so that it will read its parameter from a textual Test Data file. Here's an example:

    ...
    TESTDATA "55" {
       ("Hello", 5, "Hello"),
       ("How are you?", 12, "I'm fine"),
       ("How are you?", 3, "Don't understand")
    }
    ...
This is an excerpt of a test data file. The above section contains three test data sets (CTA_TestData aggregates) for the test case with id "55". When the registered test case "55" comes to execution, the associated test case function is actually called three times. For each call the CTA_TestData aggregate has been evaluated from the Test Data file for the function parameter. As a matter of fact, the test case "55" is three separate test cases. The trace file shows with which test data set the test case was executed on each of the calls.

The capability to override test case parameters from the command line, and especially the CTA++'s test data concept, manifest the important capability where the test data can be off-loaded from the hard-coded test main program logic. The test data file can be easily modified and extended without needing to recompile the test main program and link the test bed. The test data can be in a separate, simple and compact text file. It need not be scattered in the test bed code (the "test execution engine"). All this mechanism and much more is automatically provided to you by the CTA++ system.

2.7. Stubs

Stubs are simulated functions whose behavior is controlled by the tester and which are called by the actual code under test. Typical needs for using stubs are the following:
  • Simulation of target hardware dependent functions, which can not be run in the host test environment.
  • Simulation of the functions, which have not yet been implemented.
  • Arranging hard-to-produce error return codes for the called functions for ensuring thorough testing of the actual code under test.
  • When the testing process rules require "testing in isolation".
CTA++ has powerful support for stubs. Defining a stub and setting an intelligent behavior on it is very easy. Here is a simple example to show the idea:

In a test script in the global scope you might define

    CTA_STUB (void*, my_alloc(size_t sz), my_alloc_stub)
       CTA_BODY(1) {
          CTA_PUT(sz)
          CTA_ASSERT_IN(sz, 8, 256)
          return malloc(sz);
       }
       CTA_BODY(2) {return malloc(sz);}
    CTA_ENDSTUB
In some test case function, then, there could be
    my_alloc_stub.actionList() << repeat(5) << body(1) << repeat() << body(2);
which means that during the next 5 my_alloc calls it will behave according to CTA_BODY(1), and thereafter it will indefinitely behave according to CTA_BODY(2).

Later you might want to test how the code under test manages the end-of-heap condition and set on the stub

    my_alloc_stub << 0;
which means that the stub returns 0 (null pointer) each time it is called from now on.

CTA++ has a utility (ctastub), which reads a header file and generates null-behaving CTA++ stub definitions for the standalone and member functions that the header file contains. Later it is easy for you to edit such intelligence to the stub as the testing situation needs. The "infrastructure" to use the stubs is provided by CTA++.

2.8. Testing Multithreaded Code

CTA++ is implemented to cope with testing of multithreaded code. Firstly, the code under test may have been divided into threads and each of them may issue calls to the same global stub functions.

Secondly, this being perhaps a more interesting case, the test main program may itself divide into threads. Each of those threads can set up their own test drivers, which run in parallel. Thus it is possible to make a testing arrangement where the code under test is being called in parallel from multiple threads. Where needed, the tester may set up some synchronizing mechanisms between the threads and obtain controlled call ordering from the threads.

2.9. Access to Object Private Members

CTA++ has  a straightforward mechanism on how the object private members can be accessed at the time of testing (in the test main program and in the test-case functions).

2.10. Usage with Code Coverage Tools

From the operating system's point of view a CTA++ test bed is a normal program. A coverage tool can be applied on the code under test for measuring whether all code is exercised. For example, Testwell's CTC++, Test Coverage Analyzer for C/C++, can be used.

2.11. Usage with Run-Time Error Detecting Tools

Run-time error detecting tools reveal memory leaks, bad usage of pointers, overwrite of memory buffers, bad operating system calls, etc.

Firstly, any run-time error detecting tool can be applied, because the CTA++ test bed is a normal program. The tool findings are per whole test bed run.

Secondly, if the error detecting tool has an API, a step further can be taken. CTA++ supports a novel way how the error detecting tool can be integrated to the CTA++ model of test driver and test cases. As a result the tester can read from the CTA++ trace file in which test case the memory leaked or some other anomaly occurred as detected by the tool.

2.12. Reusing of test code

Assume a situation where a class has been developed (class A) and it has been tested by CTA++. So, for the class A there is a CTA++ test suite. More concretely , there exists a collection of test case functions, each testing some functional properties of class A type objects.

Then assume that later class B is derived from class A and the developer is faced with the task of testing this new class B. Assuming that class B must still satisfy the class A properties, here it would be handy to be able to reuse the test code (test cases) that there are already for testing the class A.

CTA++ facilitates a straightforward means to arrange the testing so that the test case functions for class A type of objects can also be used to test class B (or any inherited class thereof) type of objects. For testing class B only such test case functions need to be developed that test the new functionality of class B. The existing test case functions for class A can be reused in testing class B.

3. USE OF CTA++/EXAMPLE

3.1. The code under test

We have the interface of the code under test in the file list.h. It contains a couple of class declarations. In this example we test the operations of the class List. The implementation file is in list_bug.cpp. The list_bug.cpp file calls onwards a couple of functions, whose interface is declared in the file memory.h. In this example we use stubs for the functions declared in the memory.h file.

3.2. What needs to be done for the test bed

All CTA++ test beds need a test main program. It contains the the test bed main() function, the test case functions, etc., see CTA_vsList_drv.cpp. For the stubs we have constructed CTA_memory.h_stb.inc file. The CTA_vsList_drv.cpp file #includes the stub definition file. In this example we use also a data file to feed test data to the test bed, see CTA_vsList_drv.dat. All these three files here are actually initially generated by CTA++/VSI (that's why their naming and certain "coding conventions"), but they could have been constructed also directly by the user.

3.3. Command line mode use

The above CTA++ test bed could be compiled like any C++ program, for example as follows:
    cl -Febed.exe list_bug.cpp CTA_vsList_drv.cpp cta.lib
The resultant bed.exe program recognizes some set of command line parameters (test bed run verbosity, where to write the trace, to run only some selected test cases, to give other parameters to the test bed run). For example, it could be run as follows (all test cases in it):
    bed -o trace.txt
Next, the trace file could be converted to HTML format as follows:
    cta2html -i trace.txt
And you can view it with your default browser as follows:
    start CTAHTML\index.html
See the complete HTML report here (the test bed was constructed, compiled and run via CTA++/VSI, but effectively the same contents could have been achieved also in command-line mode).

3.4. CTA++ Visual Studio Integration

On Windows the CTA++ usage can be done fully in the Visual Studio IDE framework. See a more detailed description of CTA++/VSI.

4. SUMMARY OF BENEFITS

CTA++ benefits can be summarized as follows:
  • Automated test execution
  • Repeatable tests, regression testing
  • Visibility and documentation of test executions
  • HTML representation of test results showing summary and detailed views
  • Reporting in 'application domain' (displaying in #define/enum symbolic names)
  • Reusing of test cases saves work in derived class testing
  • Early unit testing
  • Testing in isolation (use of stubs)
  • More testing possible in the host environment (use of stubs)
  • Stub generation from header files
  • On a Windows platform CTA++ integration to Visual Studio
CTA++ test beds can be used together with the Testwell CTC++, Test Coverage Analyzer for C/C++ (see  CTC++ description ). When used together CTA++ gives the black-box (behavioral) testing approach and CTC++ gives the white-box (structural) testing approach. CTA++ test beds are "the test execution engine" with strong automation. CTC++ is used to measure that all code was exercised during the tests.

5. OPERATING ENVIRONMENTS

CTA++ is available in the following machine / operating system / C++ compiler environments:  CTA++ availability.
To frontpage