TinyEmbeddedTest Framework Overview

This page provides a quick overview of the TinyEmbeddedTest framework. The framework follows the same design principles as the CppUTest and GoogleTest frameworks, and should be very easy to use if you have experience with these frameworks.

Test Groups and Test Cases

Each unit test based on the TinyEmbeddedTest framework is a separate function that can be tracked and launched independently. To make test selection easier, each test must belong to a test group. You can define the test groups as shown below:

TEST_GROUP(DemoTestGroup)
{
};

Once a test group is defined, you can define tests using the TEST() macro:

TEST(DemoTestGroup, UnitTest1)
{
    printf("Hello from UnitTest1");
}
 
TEST(DemoTestGroup, UnitTest2)
{
    printf("Hello from UnitTest2");
}

Once you build the project defining the tests, they will appear in the Test Explorer window, organized by test groups:
You can use the Test Explorer window to select and run any arbitrary subset of tests.

If you are running tests from command line, use the /testregex argument to filter the tests that are being run. E.g. the following command line will run UnitTest1 from both TestGroup1 and TestGroup2:

VisualGDB.exe /runtests TestDemo.vgdbtestcontainer "/testregex:(TestGroup1|TestGroup2)::UnitTest1"

Setup and Teardown Functions

Test groups can define setup and teardown methods as shown below:

TEST_GROUP(DemoTestGroup)
{
    void setup()
    {
        //...
    }
    void teardown()
    {
        //...
    }
    void TestSetup(TestInstance *)
    {
        //...
    }
    void TestTeardown(TestInstance *)
    {
        //...
    }
};

If you select one of more tests from a test group, the following will happen:

  • The setup() callback will be called once per group before running the first test in the group
  • The teardown() callback will be called once per group after running the last test in the group
  • The TestSetup() and TestTeardown() methods will be called before/after each test

Assertion Macros

TinyEmbeddedTest provides several assertion macros inspired by the CppUTest framework. E.g. CHECK(), CHECK_EQUAL(), etc. The demo test project created by the VisualGDB wizard includes a quick demonstration of all supported assertion macros:

TEST(DemoTestGroup, SuccessfulTest1)
{
    CHECK(true);
    CHECK_FALSE(false);
    CHECK_EQUAL(1, 1);
    STRCMP_EQUAL("abc", "abc");
    STRNCMP_EQUAL("abc1", "abc2", 3);
    STRCMP_NOCASE_EQUAL("abc", "ABC");
    STRCMP_CONTAINS("123", "test123");
 
    LONGS_EQUAL(-2, -2);
    UNSIGNED_LONGS_EQUAL(3, 3);
    UNSIGNED_LONGS_EQUAL_WITHIN(10, 11, 2);
 
    BYTES_EQUAL(-1, 255);
    POINTERS_EQUAL(0, 0);
    MEMCMP_EQUAL("string", "string", 6);
 
    DOUBLES_EQUAL(1.0, -1.0, 2);
    DOUBLES_NOT_EQUAL(1.0, -1.0, 1);
 
    FLOATS_EQUAL(1.0F, -1.0F, 2);   
    FLOATS_NOT_EQUAL(1.0F, -1.0F, 1);
 
    BITS_EQUAL(0x050, 0xF5F, 0x0F0);
}

You can define custom assertion macros as well. If the assertion fails, the macro should call the printf-like ReportTestFailure() function. The message passed to that function will be displayed in the test failure summary in Solution Explorer.

Recovering from Assertion Failures

The default TinyEmbeddedTest behavior when a unit test fails is to continue executing the test method, assuming that it will handle this properly. You can change this behavior via VisualGDB Project Properties -> Unit Tests:TinyEmbeddedTest supports 3 different ways of handling a failed test assertion:

Mode Pros Cons
Test continues running Minimum overhead. Requires extra care when writing tests (e.g. check for null pointers).
Test ends via setjmp() No need to handle ‘failed’ case. A failed assertion will automatically abort the rest of the test. Any allocated memory will leak until the end of test session.
Test ends via C++ exceptions Resources allocated via RAII will get freed when aborting the test (i.e. C++ destructors will get called). Exception support for embedded projects has a considerable memory footprint.

You can find various other settings (e.g. whether to redirect printf() output to the test output window) on the same page of VisualGDB Project Properties.

Test Attributes

TinyEmbeddedTest allows assigning attributes to tests to simplify selecting them programmatically. You can declare test attributes as follows:

TEST_WITH_ATTRIBUTES(TestGroupName, TestName, Attribute1, Attribute2, ...)

In order for test attributes to be discovered correctly, each attribute must be a valid C++ class name (it doesn’t need to have a definition), e.g.

class MyTestAttribute;
 
TEST_WITH_ATTRIBUTES(DemoTestGroup, SuccessfulTest, MyTestAttribute)
{
    printf("Hello from test #1\n");
}

You can then run all tests with the MyTestAttribute attribute using the following command line:

VisualGDB.exe /runtests <container file> /attrregex:MyTestAttribute

Checking for Memory Leaks

You can use the CHECK_FOR_MEMORY_LEAKS() macro at the beginning of the test to automatically fail it if any of the memory allocated during the test was not freed:

TEST(DemoTestGroup, MemoryLeakTest)
{
	CHECK_FOR_MEMORY_LEAKS();
	void *p = malloc(123);
}

This can also be done for individual blocks:

TEST(DemoTestGroup, MemoryLeakTest)
{
    InitializeSomething();
 
    {
        CHECK_FOR_MEMORY_LEAKS_NAMED("First block");
        void *p = malloc(123);
        free(p);
    }
 
    {
        CHECK_FOR_MEMORY_LEAKS_NAMED("Second block");
        void *p = malloc(123);
    }
}

Troubleshooting Common Problems

This section outlines common problems that occur in TinyEmbeddedTest-based projects and describes workarounds for them.

Multiple definition of _write

This error would happen if you simultaneously enable the “Redirect printf() to Test Output window” option in TinyEmbeddedTest and one of the following options:

  • “Redirect printf() to fast semihosting” in the Fast Semihosting and Profiler framework settings.
  • “Implementations for _sbrk(), etc.” => “Support semihosting” in device/toolchain settings.

It can also happen if your device (e.g. Nordic nRF5x) provides its own implementation of the _write() function. In either case, removing the other implementations of _write(), either by disabling the related settings, or by setting the “excluded from build” flag on the file providing them, will resolve the problem.

Tests not running for a newly created project

If you have created a TinyEmbeddedTest-based project manually, make sure you call RunAllTests() from main(). See our Unit Testing tutorial for more details.