Unit tests are as important to a firmware/program (“application”) as the production source code. As with the regular code, you need to give careful thought to where the tests reside, both physically and logically, in relation to the code under test. If you put unit tests in the wrong place, the tests you have written may not be run.
Similarly, if you do not devise ways to reuse parts of your tests and use test hierarchy, you will end up with test code that is either unmaintainable or hard to understand. So, what makes a good test?
Tests should be independent and repeatable. It is a pain to debug a test that succeeds or fails as a result of other tests. PlatformIO isolates the tests by running each of them as a separate application. When a test fails, PlatformIO allows you to run it in isolation for quick debugging.
Tests should not depend on the main application source code.
In PlatformIO, each test is an independent application and should contain its
own main()
function (setup() / loop()
for Arduino,
app_main()
for Espressif IoT Development Framework).
Linking the main application source code from src_dir
with a test suite code will lead to multiple compilation errors.
Hence, the Shared Code is disabled by default.
Tests should be well organized and reflect the structure of the tested code.
PlatformIO lets you organize tests using nested folders. The only folder with
a name prefixed by test_
is nominated for unit testing and is an independent
test/application. See Test Hierarchy.
New in version 6.0.
PlatformIO looks for the tests in the project test_dir.
The only folder with a name prefixed by test_
is nominated for unit testing
and should be an independent test/application with its own main()
function
(setup() / loop()
for Arduino, app_main()
for Espressif IoT Development Framework). Nested folders are supported and will help you
to organize your tests.
The root test_dir and a folder of the active test are
automatically added to the CPPPATH
scope (C Preprocessor Search Path).
Also, C/C++ files located in the root of test_dir will
be compiled together with the active test source files. The root
test_dir is useful for placing configuration
and extra C/C++ files related to the Testing Frameworks.
Example of Pizza Project
Let’s demystify how PlatformIO handles unit tests using a virtual “Pizza Project” having the following structure:
project_dir
├── include
│ └── pizza_config.h
├── lib
│ ├── Cheese
│ │ ├── include
│ │ │ └── cheese.h
│ │ └── src
│ │ └── cheese.cpp
│ ├── Dough
│ │ ├── include
│ │ │ └── dough.h
│ │ └── src
│ │ └── dough.cpp
│ └── Sauce
│ ├── include
│ │ └── sauce.h
│ └── src
│ └── sauce.cpp
├── platformio.ini
├── src
│ └── baking.cpp
└── test
├── embedded
│ ├── components
│ │ └── sauce
│ │ └── test_tomatos
│ │ └── prepare.cpp
│ ├── stove
│ │ ├── test_humidity
│ │ │ ├── measure.cpp
│ │ │ └── sensor.cpp
│ │ └── test_temperature
│ │ ├── measure.cpp
│ │ └── sensor
│ │ ├── sensor.cpp
│ │ └── sensor.h
│ ├── unity_config.cpp
│ └── unity_config.h
└── test_ingredients
├── include
│ ├── cheese.h
│ ├── vegetables.h
│ ├── water.h
│ ├── wheat.h
│ └── yeast.h
└── weighing.cpp
The main source code (“pizza baking”) is located in the src
folder.
This is a production code. A cooking process consists of multiple subprocesses
and depends on the components located in the lib
folder. Each pizza’s component
can be tested independently using unit testing.
The Pizza Project consists of 4 independent tests:
embedded/components/sauce/test_tomatos
embedded/stove/test_humidity
embedded/stove/test_temperature
test_ingredients
PlatformIO treats each test as an independent micro project with its own source
files and subfolders. You can include local header files using the relative paths.
For example, the test_ingredients/weighing.cpp
source file includes
cheese.h
as #include <include/cheese.h>
.
The unity_config.h
and unity_config.cpp
files are located in the
embedded
folder and are common for the embedded/components/sauce/test_tomatos
,
embedded/stove/test_humidity
, and embedded/stove/test_temperature
tests.
This allows you to run a group of tests only on the embedded target and route
a test result output to the custom Serial/UART interface.
On the other hand, the test_ingredients
test uses the default Unity configuration
provided by PlatformIO. For more details, please check the documentation for the
Unity testing framework.