SDK Development


Discuss and develop the mbed SDK.

Mbed SDK cpputest usage

Mbed OS 2 and Mbed OS 5

This is a guide for Mbed OS 2. If you’re working with Mbed OS 5, please see the Testing guide in the new handbook.

Introduction - CppUTest in Mbed SDK testing

CppUTest library

CppUTest (http://cpputest.github.io/) is a C /C++ based unit xUnit test framework for unit testing and for test-driving your code. It is written in C++ but is used in C and C++ projects and frequently used in embedded systems but it works for any C/C++ project.

Mbed SDK test suite supports writing tests using CppUTest. All you need to do it to provide CppUTest sources and includes with Mbed SDK port. This is already done for you so all you need to do it to get proper sources in your project directory.

CppUTest’s core design principles are:

  1. Simple in design and simple in use.
  2. Portable to old and new platforms.
  3. Build with Test-driven Development in mind

From where you can get more help about CppUTest library and unit testing

How to add CppUTest to your current Mbed SDK installation

Do I need CppUTest port for Mbed SDK?

Yes, you do. If you want to use CppUTest with Mbed SDK you need to have CppUTest version with ARMCC compiler (only ARM flavor for now) port and Mbed SDK console port (if you want to have output on serial port). All is already prepared by Mbed engineers and you can get it for example here: http://mbed.org/users/rgrover1/code/CppUTest/

Prerequisites

  1. Installed git client e.g. http://git-scm.com/downloads
  2. Installed Mercurial client e.g. http://tortoisehg.bitbucket.org/

How to install

We want to create directory structure similar to one below:

Your Project
│
├───cpputest
│   ├───include
│   └───src
└───mbed
    ├───libraries
    ├───travis
    └───workspace_tools

Please go to directory with your project. For example it could be c:\Projects\Project.

cd c:\Projects\Project

If your project directory already has your Mbed SDK repository included just execute below command. It should download CppUTest with Mbed SDK port.

hg clone https://mbed.org/users/rgrover1/code/cpputest/

You should see something like this after you execute Mercurial clone command:

c:\Projects\Project>hg clone https://mbed.org/users/rgrover1/code/cpputest/
destination directory: cpputest
requesting all changes
adding changesets
adding manifests
adding file changes
added 3 changesets with 69 changes to 42 files
updating to branch default
41 files updated, 0 files merged, 0 files removed, 0 files unresolved

Confirm your project structure. It should look more or less like this:

c:\Projects\Project>ls
cpputest  mbed

From now on CppUTest is in correct path. Each time you want to compile unit tests for CppUTest build script will always look for CppUTest library in the same directory where mbed library is.

New off-line Mbed SDK project with CppUTest support

If you are creating new Mbed SDK project and you want to use CppUTest with it you need to download bot Mbed SDK and CppUTest with mbed port to the same directory. You can do it like this:

cd c:\Projects\Project

git clone https://github.com/mbedmicro/mbed.git

hg clone https://mbed.org/users/rgrover1/code/cpputest/

It should look more or less like on screen below: /media/uploads/PrzemekWirkus/setup_cpputest.png

After above three steps you should have proper directory structure. All you need to do now is to configure your private_settings.py in mbed/workspace_tools/ directory. Please refer to Mbed SDK build script environment's Configure workspace tools to work with your compilers Wiki page chapter for details about how to configure mbed SDK so it's aware of your supported toolchains.

CppUTest port

To make sure you actualy have CppUTest library with Mbed SDK port you can go to cpputest ARMCC platform directory:

cd c:\Projects\Project\cpputest\src\Platforms\armcc\

And open file UtestPlatform.cpp.

You should find part of code responsible for porting console on default serial port of the mbed device:

UtestPlatform.cpp

#include "Serial.h"
using namespace mbed;

int PlatformSpecificPutchar(int c)
{
    /* Please modify this block for test results to be reported as
     * console output. The following is a sample implementation using a
     * Serial object connected to the console. */
#define NEED_TEST_REPORT_AS_CONSOLE_OUTPUT 1
#if NEED_TEST_REPORT_AS_CONSOLE_OUTPUT
    extern Serial console;

    #define NEED_LINE_FEED_IN_ADDITION_TO_NEWLINE 1
    #if NEED_LINE_FEED_IN_ADDITION_TO_NEWLINE
    /* CppUTest emits \n line terminators in its reports; some terminals
     * need the linefeed (\r) in addition. */
    if (c == '\n') {
        console.putc('\r');
    }
    #endif /* #if NEED_LINE_FEED_IN_ADDITION_TO_NEWLINE */

    return (console.putc(c));
#else /* NEED_TEST_REPORT_AS_CONSOLE_OUTPUT */
    return (0);
#endif /* NEED_TEST_REPORT_AS_CONSOLE_OUTPUT */
}

You can find cpputest UT test runner main function in mbed sources;

c:\Projects\Project\mbed\libraries\tests\utest\testrunner\testrunner.cpp

Test runner code only defined console object and executes all unit tests.

testrunner.cpp

#include "CppUTest\CommandLineTestRunner.h"
#include <stdio.h>
#include "mbed.h"
#include "testrunner.h"
#include "test_env.h"

/**
Object 'console' is used to show prints on console.
It is declared in \cpputest\src\Platforms\armcc\UtestPlatform.cpp
*/
Serial console(STDIO_UART_TX, STDIO_UART_RX);

int main(int ac, char** av)
{
    unsigned failureCount = 0;
    {
        // Some compilers may not pass ac, av so we need to supply them ourselves
        int ac = 2;
        char* av[] = {__FILE__, "-v"};
        failureCount = CommandLineTestRunner::RunAllTests(ac, av);
    }

    notify_completion(failureCount == 0);
    return failureCount;
}

Unit test location

Unit tests' source code is located in below directory:

c:\Projects\Project\mbed\libraries\tests\utest

Each sub directory except testrunner contains compilable unit test source file(s) with test groups and test cases. You can see utest structure below. Please note this is just example and in the future this directory will contain many sub directories with unit tests.

c:\Projects\Project\mbed\libraries\tests\utest>tree
utest
├───basic
├───semihost_fs
└───testrunner

Define unit tests in test suite

All tests defined in test suite are described in mbed/workspace_tools/tests.py file. This file stores data structure TESTS which is a list of simple structures describing each test. Below you can find example of TESTS structure which is configuring one of the unit tests (defines which ).

mbed\\workspace_tools\tests.py

    {
        "id": "UT_2", "description": "Semihost file system",
        "source_dir": join(TEST_DIR, "utest", "file"),
        "dependencies": [MBED_LIBRARIES, TEST_MBED_LIB, CPPUTEST_LIBRARY],
        "automated": False,
        "mcu": ["LPC1768", "LPC2368", "LPC11U24"]
    },

Note: In dependency section we've added library CPPUTEST_LIBRARY which is pointing build script to CppUTest library with mbed port. This is a must for unit tests to be compiled with CppUTest library.

Tests are now divided into two types:

'Hello world' tests

First we call 'hello world' tests. They do not dependent on CppUTest library and are monolithic programs usually composed of one main function. Yo can find this tests in below example directories:

  • mbed/libraries/tests/mbed/
  • mbed/libraries/tests/net/
  • mbed/libraries/tests/rtos/
  • mbed/libraries/tests/usb/

Unit test tests

Second group of tests are unit tests. They are using CppUTest library and require you to write TEST_GROUPs and TESTs in your test files. Test suite will add test runner sources to your test automatically so you can concentrate on writing tests.

Example of unit test

mbed\libraries\tests\utest\semihost_fs\semihost_fs.cpp

#include "CppUTest/TestHarness.h"
#include "mbed.h"
#include "semihost_api.h"
#include <stdio.h>

#define FILENAME      "/local/out.txt"
#define TEST_STRING   "Hello World!"

TEST_GROUP(FirstTestGroup)
{

    FILE *test_open(const char *mode) {
        FILE *f = fopen(FILENAME, mode);
        return f;
    }

    bool test_write(FILE *f, char *str, int str_len) {
        int n = fprintf(f, str);
        return (n == str_len) ? true : false;
    }

    bool test_read(FILE *f, char *str, int str_len) {
        int n = fread(str, sizeof(unsigned char), str_len, f);
        return (n == str_len) ? true : false;
    }

    bool test_close(FILE *f) {
        int rc = fclose(f);
        return rc ? true : false;
    }

};

TEST(FirstTestGroup, FirstTest)
{
    CHECK_TEXT(semihost_connected(), "Semihost not connected")

    LocalFileSystem local("local");

    char *str = TEST_STRING;
    char *buffer = (char *)malloc(sizeof(unsigned char) * strlen(TEST_STRING));
    int str_len = strlen(TEST_STRING);

    CHECK_TEXT(buffer != NULL, "Buffer allocation failed");
    CHECK_TEXT(str_len > 0, "Test string is empty (len <= 0)");

    {
        // Perform write / read tests
        FILE *f = NULL;
        // Write
        f = test_open("w");
        CHECK_TEXT(f != NULL, "Error opening file for writing")
        CHECK_TEXT(test_write(f, str, str_len), "Error writing file");
        CHECK_TEXT(test_close(f) != EOF, "Error closing file after write");

        // Read
        f = test_open("r");
        CHECK_TEXT(f != NULL, "Error opening file for reading")
        CHECK_TEXT(test_read(f, buffer, str_len), "Error reading file");
        CHECK_TEXT(test_close(f) != EOF, "Error closing file after read");
    }
    CHECK(strncmp(buffer, str, str_len) == 0);
}

Example of 'hello world' test

mbed\libraries\tests\mbed\i2c_MMA8451Q\main.cpp

#include "mbed.h"
#include "MMA8451Q.h"
#include "test_env.h"

#ifdef TARGET_KL05Z
#define SDA     PTB4
#define SCL     PTB3
#else
#define SDA     PTE25
#define SCL     PTE24
#endif

namespace {
    const int MMA8451_I2C_ADDRESS = 0x1D << 1;          // I2C bus address
    const float MMA8451_DIGITAL_SENSITIVITY = 4096.0;   // Counts/g
}

float calc_3d_vector_len(float x, float y, float z) {
    return sqrt(x*x + y*y + z*z);
}

#define TEST_ITERATIONS                 25
#define TEST_ITERATIONS_SKIP            5
#define MEASURE_DEVIATION_TOLERANCE     0.025   // 2.5%

int main(void) {
    DigitalOut led(LED_GREEN);
    MMA8451Q acc(SDA, SCL, MMA8451_I2C_ADDRESS);
    bool result = true;
    printf("WHO AM I: 0x%2X\r\n\n", acc.getWhoAmI());

    for (int i = 0; i < TEST_ITERATIONS; i++) {
        if (i < TEST_ITERATIONS_SKIP) {
            // Skip first 5 measurements
            continue;
        }
        const float g_vect_len = calc_3d_vector_len(acc.getAccX(), acc.getAccY(), acc.getAccZ()) / MMA8451_DIGITAL_SENSITIVITY;
        const float deviation = fabs(g_vect_len - 1.0);
        const char *succes_str = deviation <= MEASURE_DEVIATION_TOLERANCE ? "[OK]" : "[FAIL]";
        result = result && (deviation <= MEASURE_DEVIATION_TOLERANCE);
        printf("X:% 6d Y:% 6d Z:% 5d GF:%0.3fg, dev:%0.3f ... %s\r\n", acc.getAccX(), acc.getAccY(), acc.getAccZ(), g_vect_len, deviation, succes_str);
        wait(0.5);
        led = !led;
    }
    notify_completion(result);
}

Example

In below example we will run two example unit tests that are now available. tests UT_1 and UT_2 are unit tests used for now only to check if mbed SDK works with CppUTest library and if tests are being executed. In future number of unit tests will increase, nothing is also should stop you from writing and executing your own unit tests!

Example configuration

By default unit tests UT_1 and UT_2 are not automated - simply test suite will ignore them. Also we do not want to create dependency to CppUTest library each time someone executes automation.

Note: To compile UT_1 and UT_2 tests CppUTest library described above installation is needed and not all users wish to add UT libs to their project. Also new to mbed users may find it difficult. This is why unit testing is an extra feature active only after you deliberately install and enable needed components.

Bellow snippet shows how to modify 'automated' flag so test suite will consider unit tests UT_1 and UT_2 as part of "automated test portfolio". Just change flag 'automated' from False to True.

workspace_tools\tests.py

    # CPPUTEST Library provides Unit testing Framework
    #
    # To write TESTs and TEST_GROUPs please add CPPUTEST_LIBRARY to 'dependencies'
    #
    # This will also include:
    # 1. test runner - main function with call to CommandLineTestRunner::RunAllTests(ac, av)
    # 2. Serial console object to print test result on serial port console
    #

    # Unit testing with cpputest library
    {
        "id": "UT_1", "description": "Basic",
        "source_dir": join(TEST_DIR, "utest", "basic"),
        "dependencies": [MBED_LIBRARIES, TEST_MBED_LIB, CPPUTEST_LIBRARY],
        "automated": True,
    },
    {
        "id": "UT_2", "description": "Semihost file system",
        "source_dir": join(TEST_DIR, "utest", "semihost_fs"),
        "dependencies": [MBED_LIBRARIES, TEST_MBED_LIB, CPPUTEST_LIBRARY],
        "automated": True,
        "mcu": ["LPC1768", "LPC2368", "LPC11U24"]
    },

Execute tests

In my test I will use common NXP LPC1768 mbed enabled board because unit test UT_2 is checking semi-host functionality which is available on this board and handful of others.

Configure your test_spec.json and muts_all.json files and set mbed disk and serial port.

singletest.py -i test_spec.json -M muts_all.json -n UT_1,UT_2 -V

Building library CMSIS (LPC1768, ARM)
Building library MBED (LPC1768, ARM)
Building library CPPUTEST (LPC1768, ARM)
Building project BASIC (LPC1768, ARM)
Executing 'python host_test.py -p COM77 -d E:\ -t 10'
Test::Output::Start
Host test instrumentation on port: "COM77" and disk: "E:\"
TEST(FirstTestGroup, FirstTest) - 0 ms

OK (1 tests, 1 ran, 3 checks, 0 ignored, 0 filtered out, 3 ms)

{{success}}
{{end}}
Test::Output::Finish
TargetTest::LPC1768::ARM::UT_1::Basic [OK] in 2.43 of 10 sec
Building library CPPUTEST (LPC1768, ARM)
Building project SEMIHOST_FS (LPC1768, ARM)
Executing 'python host_test.py -p COM77 -d E:\ -t 10'
Test::Output::Start
Host test instrumentation on port: "COM77" and disk: "E:\"
TEST(FirstTestGroup, FirstTest) - 9 ms

OK (1 tests, 1 ran, 10 checks, 0 ignored, 0 filtered out, 10 ms)

{{success}}
{{end}}
Test::Output::Finish
TargetTest::LPC1768::ARM::UT_2::Semihost file system [OK] in 2.43 of 10 sec
Test summary:
+--------+---------+-----------+---------+----------------------+--------------------+---------------+-------+
| Result | Target  | Toolchain | Test ID | Test Description     | Elapsed Time (sec) | Timeout (sec) | Loops |
+--------+---------+-----------+---------+----------------------+--------------------+---------------+-------+
| OK     | LPC1768 | ARM       | UT_1    | Basic                |        2.43        |       10      |  1/1  |
| OK     | LPC1768 | ARM       | UT_2    | Semihost file system |        2.43        |       10      |  1/1  |
+--------+---------+-----------+---------+----------------------+--------------------+---------------+-------+
Result: 2 OK

Completed in 12.02 sec

You can compile unit tests using various number of supported compilers, below just few examples with working solutions:

Test summary:
+--------+---------+-----------+---------+----------------------+--------------------+---------------+-------+
| Result | Target  | Toolchain | Test ID | Test Description     | Elapsed Time (sec) | Timeout (sec) | Loops |
+--------+---------+-----------+---------+----------------------+--------------------+---------------+-------+
| OK     | LPC1768 | ARM       | UT_1    | Basic                |        2.43        |       10      |  1/1  |
| OK     | LPC1768 | ARM       | UT_2    | Semihost file system |        2.43        |       10      |  1/1  |
| OK     | LPC1768 | uARM      | UT_1    | Basic                |        2.43        |       10      |  1/1  |
| OK     | LPC1768 | uARM      | UT_2    | Semihost file system |        2.43        |       10      |  1/1  |
| OK     | LPC1768 | GCC_ARM   | UT_1    | Basic                |        2.43        |       10      |  1/1  |
| OK     | LPC1768 | GCC_ARM   | UT_2    | Semihost file system |        2.43        |       10      |  1/1  |
| OK     | LPC1768 | GCC_CR    | UT_1    | Basic                |        3.44        |       10      |  1/1  |
| OK     | LPC1768 | GCC_CR    | UT_2    | Semihost file system |        3.43        |       10      |  1/1  |
+--------+---------+-----------+---------+----------------------+--------------------+---------------+-------+
Result: 8 OK

Completed in 55.85 sec

All wikipages