Terms:
TDD - Test Driven Development: Development cycle starting with writing a specification, then writing a unit test, having the test fail, writing enough code for having the unit test pass, then writing the next unit test, carrying on this cycle until specification is met.
Unit Testing: The act of testing core modules of code independently and rigorously with a pass or fail state returned.
Unit Testing with ESP-IDF.
Firstly, exploring the ESP-IDF examples includes a prebuilt sample "Unit Test" template. This can be built into any directory using the idf.py menuconfig tool. Or via the VSCode addin. This will build the framework for a main project with a component or submodule for testing.
ESP-IDF uses the Unity framework as the primary unit testing framework. Unity is a C specific unit testing framework, originally derived from JUnit (Java testing framework). Unity key features are that it only requires a single header file, simple to use and portable from 8 bit micro controllers to 64 bit systems.
More details on Unity's official website here.
F1 brings up main menu on VSCode, with this you can select the function of finding examples from the ESP-IDF addin.
Finding the relevant example project and by clicking the create project it will make a unit test sample project in a directory of your choosing which includes all make files and can be configured and built from this stage.
Project folder layout.
For embedded C Projects there is normally a singular defined main, this is where the compiler starts to build a project from.
However to run a unit test we need a separate main to run the tests. This needs to be built separately than the production code main. This should be under the main directory as a sub folder "test". I do suggest however you name the test running main to show distinction from the production code main.
Suggested _main.c naming convention.
Getting Started: Test Driven Development
Start by planning a few basic tests that you want to perform, consider what we are building and how you would know if it was working.
In this example i will use a 16 bit variable to mock setting 16 different Leds.
First stage is to just write a first test, this test won't compile but thats ok.
We write the first test inside component->test cases. (Refer to folder layout if confused).
This test cases file should be a .c file which starts with the word test. I use test_led_driver.
#include "unity.h"
TEST_CASE("test_name","test_tag")
{
TEST_FAIL_MESSAGE("FAILING");
}
The header will include all the unit testing context for the TEST_CASE function and fail message.
To compile we need to do something unexpected, we need to open in a separate instance the "test" subfolder and build from here.This isn't the best setup and Espresif are working on a better solution, for now this is the only way to have sub modules within a single project.
Test runner: All we really need is a main.c under test directory, which includes the ESP-IDF specified app_main(). that ESP need for a compile, to run the tests with unity we need to invoke a setup function UNITY_BEGIN(); , then call the suite of tests we want to run, this can be a single test, a group or all tests. As we only have one i will invoke all tests to be run using unity_run_all_tests(); Followed by the teardown function UNITY_END();.
#include <stdio.h>
#include <string.h>
#include "unity.h"
static void print_banner(const char* text);
void app_main(void)
{
print_banner("Running all the registered tests");
UNITY_BEGIN();
unity_run_all_tests();
UNITY_END();
}
static void print_banner(const char* text)
{
printf("\n#### %s #####\n\n", text);
}
This test should compile if you using the sample. If you flash to hardware or mock instance,it will give a test display which should similar to this.
#### Running all the registered tests #####
Running test_name...
.../test/test_led_driver.c:41:test_name:FAIL:Function test_tag. FAILING
-----------------------
1 Tests 1 Failures 0 Ignored
FAIL
If you are having the sample project compile but without giving any test info.
What is likely happening is you are building using the production code main rather than the test runner main. To correct this you need to open the "test" folder in a separate instance of VSCode or terminal and run build from this folder.
First test failing is an essential part of Test driven development.
If renaming any files during this tutorial, please check the corresponding MAKEFILE and CMakeList.txt files are also updated. If the names don't correspond to each other then CMake the build tool cannot compile the bin.
For basic build, flash monitor commands, there is a toolbar on VSCode with functions also callable from F1->ESP-IDF-> ...
Ok, simplest test failed. Great. Moving on.
I switched my first test into a test with a chance of passing one day.
TEST_CASE("led_off_after_creation","led_driver")
{
uint16_t virtual_leds = 0xffff;
led_driver_create(&virtual_leds);
TEST_ASSERT_EQUAL_HEX16(0,virtual_leds);
}
Broken down, this test is setting all my leds off and checking they are off, in reality it sets the bits of a variable virtual leds to 0.
Attempting to compile this will fail, depending on compiler it will spit out different errors, however expect something along the lines of... what is led_driver_create?
We have yet to write this function so it is no surprise the build doesn't work. If it does compile check again you are compiling the test subdirectory.
Work In Progress