PIO Unit Testing

New in version 3.0: (PlatformIO Plus)

Unit Testing (wiki) is a software testing method by which individual units of source code, sets of one or more MCU program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use. Unit testing finds problems early in the development cycle.

Tip

QUESTIONS? Ask us! Chat button is located in the bottom right corner

Test Types

Desktop

PlatformIO wraps a test and a main program (from src_dir) with PIO Unit Testing Framework and builds the final program using Native development platform. Finally, PlatformIO runs tests on the host machine (desktop or Continuous Integration VM instance).

Note

PlatformIO does not install any toolchains automatically for Native and requires GCC toolchain to be installed on your host machine. Please open Terminal and check that the gcc command is installed.

Embedded

PlatformIO wraps a test and main firmware (from src_dir) with PIO Unit Testing Framework and builds special firmware for a target device and deploy it. Then, PlatformIO connects to the embedded device (board) using configured Serial test_port and communicate via test_transport. Finally, it runs tests on embedded side, collects results, analyzes them and provides a summary on host machine side (desktop).

Note

Please note that the PIO Unit Testing Framework uses the first available Serial/UART implementation (depending on a framework) as a communication interface between the PIO Unit Testing Engine and target device. If you use Serial in your project, please wrap/hide Serial-based blocks with #ifndef UNIT_TEST macro.

Also, you can create custom test_transport and implement base interface.

Test Runner

Test Runner allows you to process specific environments or ignore tests using “Glob patterns”. You can also ignore tests for specific environments using a test_ignore option from Project Configuration File platformio.ini.

Local

Allows you to run tests on a host machine or on target devices (boards) that are directly connected to the host machine. In this case, need to use the platformio test command.

Remote

Allows you to run tests on a remote machine or remote target devices (boards) without having to depend on OS software, extra software, SSH, VPN or opening network ports. Remote Unit Testing works in pair with PIO Remote™. In this case, you need to use the special command platformio remote test.

PlatformIO supports multiple Continuous Integration systems where you can run unit tests on each integration stage. See real PlatformIO Remote Unit Testing Example.

Design

The PIO Unit Testing Engine design is based on a few isolated components:

  1. Main Program. Contains the independent modules, procedures, functions or methods that will be the target candidates (TC) for testing.
  2. Unit Test. A small independent program that is intended to re-use TC from the main program and apply tests to them.
  3. Test Processor. The set of approaches and tools that will be used to apply tests for the environments from Project Configuration File platformio.ini.

Workflow

  1. Create PlatformIO project using the platformio init command. For Desktop Unit Testing (on the host machine), you need to use Native.

    ; PlatformIO Project Configuration File
    ;
    ;   Build options: build flags, source filter, extra scripting
    ;   Upload options: custom port, speed and extra flags
    ;   Library options: dependencies, extra library storages
    ;
    ; Please visit documentation for the other options and examples
    ; http://docs.platformio.org/en/stable/projectconf.html
    
    ;
    ; Embedded platforms
    ;
    
    [env:uno]
    platform = atmelavr
    framework = arduino
    board = uno
    
    [env:nodemcu]
    platform = espressif8266
    framework = arduino
    board = nodemcuv2
    
    ;
    ; Desktop platforms (Win, Mac, Linux, Raspberry Pi, etc)
    ; See http://platformio.org/platforms/native
    ;
    
    [env:native]
    platform = native
    
  2. Place source code for the main program in the src directory.

  3. Wrap main() or setup()/loop() methods in the main program with a UNIT_TEST guard:

    /**
    * Arduino Wiring-based Framework
    */
    #ifndef UNIT_TEST
    #include <Arduino.h>
    void setup () {
      // some code...
    }
    
    void loop () {
      // some code...
    }
    #endif
    
    /**
    * Generic C/C++
    */
    #ifndef UNIT_TEST
    int main(int argc, char **argv) {
      // setup code...
    
      while (1) {
          // loop code...
      }
      return 0
    }
    #endif
    
  4. Create a test directory in the root of your project. See test_dir.

  5. Write the test using API. Each test is a small independent program with its own main() or setup()/loop() methods. Tests should start with UNITY_BEGIN() and finish with UNITY_END().

    Warning

    If your board does not support software reset via Serial.DTR/RTS, you should add >2 seconds delay before UNITY_BEGIN()`. That time is needed to establish a ``Serial communication between the host machine and target device.

    delay(2000); // for Arduino framework
    wait(2);     // for ARM mbed framework
    UNITY_BEGIN();
    
  6. Place the test in the test directory. If you have more than one test, split them into sub-folders. For example, test/test_1/*.[c,cpp,h], test_N/*.[c,cpp,h], etc. If there is no such directory in the test``folder, then |PIOUTE| will treat the source code of ``test folder as SINGLE test.

  7. Run tests using the platformio test command.

API

Summary of the Unity Test API:

  • Running Tests
    • RUN_TEST(func, linenum)
  • Ignoring Tests
    • TEST_IGNORE()
    • TEST_IGNORE_MESSAGE (message)
  • Aborting Tests
    • TEST_PROTECT()
    • TEST_ABORT()
  • Basic Validity Tests
    • TEST_ASSERT_TRUE(condition)
    • TEST_ASSERT_FALSE(condition)
    • TEST_ASSERT(condition)
    • TEST_ASSERT_UNLESS(condition)
    • TEST_FAIL()
    • TEST_FAIL_MESSAGE(message)
  • Numerical Assertions: Integers
    • TEST_ASSERT_EQUAL_INT(expected, actual)
    • TEST_ASSERT_EQUAL_INT8(expected, actual)
    • TEST_ASSERT_EQUAL_INT16(expected, actual)
    • TEST_ASSERT_EQUAL_INT32(expected, actual)
    • TEST_ASSERT_EQUAL_INT64(expected, actual)
    • TEST_ASSERT_EQUAL_UINT(expected, actual)
    • TEST_ASSERT_EQUAL_UINT8(expected, actual)
    • TEST_ASSERT_EQUAL_UINT16(expected, actual)
    • TEST_ASSERT_EQUAL_UINT32(expected, actual)
    • TEST_ASSERT_EQUAL_UINT64(expected, actual)
    • TEST_ASSERT_EQUAL_HEX(expected, actual)
    • TEST_ASSERT_EQUAL_HEX8(expected, actual)
    • TEST_ASSERT_EQUAL_HEX16(expected, actual)
    • TEST_ASSERT_EQUAL_HEX32(expected, actual)
    • TEST_ASSERT_EQUAL_HEX64(expected, actual)
    • TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, elements)
    • TEST_ASSERT_EQUAL(expected, actual)
    • TEST_ASSERT_INT_WITHIN(delta, expected, actual)
  • Numerical Assertions: Bitwise
    • TEST_ASSERT_BITS(mask, expected, actual)
    • TEST_ASSERT_BITS_HIGH(mask, actual)
    • TEST_ASSERT_BITS_LOW(mask, actual)
    • TEST_ASSERT_BIT_HIGH(mask, actual)
    • TEST_ASSERT_BIT_LOW(mask, actual)
  • Numerical Assertions: Floats
    • TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual)
    • TEST_ASSERT_EQUAL_FLOAT(expected, actual)
    • TEST_ASSERT_EQUAL_DOUBLE(expected, actual)
  • String Assertions
    • TEST_ASSERT_EQUAL_STRING(expected, actual)
    • TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len)
    • TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message)
    • TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message)
  • Pointer Assertions
    • TEST_ASSERT_NULL(pointer)
    • TEST_ASSERT_NOT_NULL(pointer)
  • Memory Assertions
    • TEST_ASSERT_EQUAL_MEMORY(expected, actual, len)