Test Unit
Stride groups similar test cases into a single runnable package called a Test Unit (also known as a Test Suite). These tests are written in C and C++ and are compiled and linked with your software and run, when called, on your target platform. They are suitable for both developer unit testing as well as end-to-end integration testing. An external Stride Runner is provided which controls the execution of Test Units and publishes test results to the local file system and optionally to Testspace. The following is an example of the structure of a Test Unit:
Declare tests
#include <srtest.h>
class MyTest : public stride::srTest {
public:
void CheckFoo();
void CheckBoo();
};
#pragma scl_test_class(MyTest)
Write tests
#include <mytest.h>
void MyTest::CheckFoo() {
..
srEXPECT_EQ(foo(2 + 2), 4);
}
void MyTest::CheckBoo() {
..
srEXPECT_GT(boo(3 * 3), 7);
}
Test Unit
A single Test Unit is a set of Test Methods that always run together as an executable unit. Test Methods are the test cases that comprise a Test Unit. Each Test Method by default maps to a single Test Case in the results. When relying on this default behavior of Test Methods, we refer to these methods as static Test Cases. A less common technique is to create and add a Test Case dynamically at runtime to an existing Suite via the Test Services. We refer to these as dynamic Test Cases.
Individual test cases are implemented as test methods or functions which follow a four-phase testing pattern:
- Setting up a test fixture (optional)
- Exercising the Software Under Test (SUT)
- Verifying that the expected outcome has occurred
- Tearing down the test fixture (optional)
Test fixturing refers to the Setup and Teardown phases of the testing.
In the Setup phase, we put all of the things into place that are required in order to run a our test and expect a particular outcome. This includes things like:
- Acquiring resources such as memory, hardware, etc.
- Setting up required states such as input files in place, memory filled with a pattern, dependencies initialized, etc.
In the Tear down phase, we clean up the fixturing we did in the Setup phase, leaving the system in a state that is ready to be used by the next test.
Packaging
Individual functions or methods, which typically implement a single test case are grouped into one or more Test Units which are executed as atomic entities.
Grouping of individual tests into a Test Unit can be accomplished in any of three ways:
- A Test Unit can be comprised of the member functions of a C++ class,
- A Test Unit can be comprised of a set of C functions,
- A Test Unit can be comprised of C functions pointed to by members of a C struct
The following table compares the three packaging variants.
Test Unit Type | Advantages | Disadvantages | scl pragma |
---|---|---|---|
C++ class
|
|
|
scl_test_class |
C class |
|
|
scl_test_cclass |
C functions |
|
|
scl_test_flist |
The best choice is usually the C++ class since it offers the best mix of features and ease-of-use. (You can test code written in C or C++ using the C++ class test units.) However, compiling C++ is not always possible, in this case one of the C-based test unit packaging options must be used.
You can freely mix different deployment methods across a project if desired, the format of the results is consistent across all test unit packaging options.
Test Method Return Values
Test Method return values must conform to certain return types as summarizing in the following table.
Return Type | Description | How Return Value is Interpreted | Default Status |
void | Most common return type | Since there is no return, PASS/FAIL status is set in the body of the method using a
or a runtime call |
|
integer type | Integer types include:
may include signed/unsigned/const qualifiers |
|
|
bool | c++ only |
|
|
Examples
Following are a few short examples. In each example, a single test unit with the name "MyTest" is identified to the Stride compiler via a Test Pragmas.
C++ Class
MyTest.h
#include <srtest.h>
class MyTest : public stride::srTest
{
public:
void ExpectPass()
{
srNOTE_INFO("this test should pass");
srEXPECT_EQ(2 + 2, 4);
}
void ExpectFail()
{
srNOTE_INFO("this test should fail");
srEXPECT_GT(2 * 3, 7);
}
};
#ifdef _SCL
// this pragma identifies MyTest as a test class to the STRIDE compiler
#pragma scl_test_class(MyTest)
#endif
C Class
MyTest.h
#include <srtest.h>
typedef struct MyTest
{
void (*ExpectPass)(struct MyTest* self);
void (*ExpectFail)(struct MyTest* self);
} MyTest;
void MyTest_Init(MyTest* self);
#ifdef _SCL
// This pragma identifies MyTest as a test c class to the STRIDE compiler.
// Extra instrumentation code will be generated to call MyTest_Init() before
// tests are run.
#pragma scl_test_cclass(MyTest, MyTest_Init)
#endif
MyTest.c
#include "MyTest.h"
static void ExpectPass(MyTest* self)
{
srNOTE_INFO("this test should pass");
srEXPECT_EQ(2 + 2, 4);
}
static void ExpectFail(MyTest* self)
{
srNOTE_INFO("this test should fail");
srEXPECT_GT(2 * 3, 7);
}
void MyTest_Init(MyTest* self)
{
self->ExpectPass = ExpectPass;
self->ExpectFail = ExpectFail;
}
Free Functions
MyTest.h
#include <srtest.h>
void ExpectPass();
void ExpectFail();
#ifdef _SCL
// this pragma identifies MyTest as a test unit to the STRIDE compiler, specifying
// the four functions as members of the test unit
#pragma scl_test_flist("MyTest", ExpectPass, ExpectFail, ChangeMyName, ChangeMyDescription)
#endif
MyTest.c
#include "MyTest.h"
void ExpectPass()
{
srNOTE_INFO("this test should pass");
srEXPECT_EQ(2 + 2, 4);
}
void ExpectFail()
{
srNOTE_INFO("this test should fail");
srEXPECT_GT(2 * 3, 7);
}
C++ Only Features
Use Operator << to Augment Test Macros
In C++ test code Test Point, Test Log and Test Macros macros support adding to the annotation using the << operator. For example:
srEXPECT_TRUE(a != b) << "My custom message" << " with more data " << 1234;
As delivered, the macros will support stream input annotations for:
- all numeric types,
- C string (char* or wchar_t*), and
- types allowing implicit cast to numeric type or "C" string.
Overloading the << operator
You can also overload the << operator in order to annotate reports using your own custom type. An example is below.
The following will compile and execute successfully given that the << operator is overloaded as shown:
#include <srtest.h>
// MyCustomClass implementation
class MyCustomClass
{
public:
MyCustomClass(int i) : m_int(i) {}
private:
int m_int;
friend stride::Message& operator<<(stride::Message& ss, const MyCustomClass& obj);
};
stride::Message& operator<<(stride::Message& ss, const MyCustomClass& obj)
{
ss << obj.m_int;
return ss;
}
void test()
{
MyCustomClass custom(34);
srEXPECT_FALSE(true) << custom;
}