,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
!"#$%#"$!&' !$)*+
Unit testing embedded code with cmocka 26 Sep 2016
Sam Lewis !"#$ %
Unit testing testing is is a great software testing practice that gets a lot of love in web and application development but unfortunately isn't practiced as often in embedded/firmware development. This is a shame as the impacts of having a bug in firmware can be considerably more catastrophic than having a bug in a webapp. I've written this article in the hope of demonstrating some of the benefits that come when writing unit tests for embedded software and have also given a small toy example of how it's possible to unit test firmware using the cmocka framework.
Why unit test embedded software? System level embedded software testing is notoriously difficult, mainly because embedded software runs on bare metal hardware. While you can never completely get away from testing embedded software on real hardware, testing the logic in the embedded software through unit testing is very valuable and gets you a lot of the way there. If you still need convincing, some of the things I really like about having unit tests for my embedded code: Allows you to develop your application layer logic without the need for any hardware, saving lots of time Gives you a greater appreciation and understanding of your hardware periphereals, as you need to understand their interfaces to write tests against them Less 'wtf' bugs that are impossible impossible to track down in development/production Allows you to test your code against edge cases that otherwise hard to reproduce (eg: testing how your firmware reacts when your temperature sensor is used in negative temperatures) Testable code tends to be cleaner, modular and Testable reusable code Takes some amount of pressure off your systems level testing 9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
?;20 ! 7@ A
,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
!"#$%#"$!&' !$)*+
Stops regressions, particularly if you integrate the tests into your build There are a lot of unit testing frameworks for C but for embedded testing, the framework I like best is cmocka cmocka. I've looked at others in the past, such as cmock cmock and cutest cutest but cmocka stands out as having all testing & mocking functionality I need while having minimal dependencies. On caveat worth mentioning is that to properly use these frameworks, you'll most likely need to compiler your code with a different compiler to what your compile with for your hardware - both so that the framework can run and so that you can execute the tests on your workstation. However, this shouldn't present a major problem as long as you're not relying on non-standard or undefined compiler behaviour. Logic errors in your code will still be logic errors no matter what compiler you use to compile the code.
An example of unit testing C in an embedded context To give a concrete example of how you could make use of cmocka in an embedded context, I'll show the process of unit testing code for a temperature sensor. I've chosen the TI TMP101 TMP101 as the temperature sensor, which is a temperature sensor that works over I2C. The complete listing of code is checked in on my github github, the below text gives a rundown of how it all comes together. The first step as normal, is to write some code for the tmp101 sensor that can fetch a temperature. You can find the full code listing on my github github. It's also reproduced below. #include "tmp101.h" static const float TMP_BIT_RESOLUTION = 0.0625; float tmp101_get_temperature(void) { // Need to set the TMP101 pointer register to point to the temp register uint8_t pointer_address = 0; i2c_transmit_blocking(TMP101_ADDRESS, 0, &pointer_address, 1); // The TMP101 stores 12 bit samples that are retrieved in two byte blocks uint8_t data[2]; i2c_read_blocking(TMP101_ADDRESS, 0, &data[0], 2); 9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
?;20 " 7@ A
,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
!"#$%#"$!&' !$)*+
// the 1st byte is bits 12 to 4 of the sample and the 2nd byte is bits 4 to 0 // see page 16 of the TMP_101 datasheet uint16_t temperature_bits = (data[0] << 4) | (data[1] >> 4); // The 12 bit sample is represented using 2s complement, for simplicity // (and because there's no 12 bit int representation), scale up the sample // to 16 bits and adjust the bit resolution when converting later int16_t temperature = temperature_bits << 4; // shift the sample back down and convert by the TMP_101 bit resolution return ((temperature / 16) * 0.0625f); }
This function isn't the most complicated function but there's enough bit manipulation to make me nervous and unsure if it'll do exactly what I want all the time. Sure, we could run it on my hardware platform and see how it responds but that'd only prove that it works as whatever temperature it is now. Instead, lets write some unit tests for this code. Conveniently, for this particular temperature sensor, TI are kind enough to provide a table of example temperatures with their corresponding 12 bit digital outputs. These prove a nice place to start in testing our get_temperature function.
In essence, we want to control the data that i2c_read_blocking returns to our tmp101_get_temperature function so that we can check against our function works as it should. A simple (but somewhat ugly) way to this might be to have the function look something like: void i2c_read_blocking(uint8_t address, uint8_t offset, uint8_t* pData, uint8_t data_size) { 9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
?;20 H 7@ A
,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
!"#$%#"$!&' !$)*+
#ifdef TESTING return DUMMY_VALUE #endif //normal i2c logic here }
But mixing test code with production code is ugly and an antipattern that should be avoided if possible. A much better way that allows us to both completely separate our testing and source code is to use the cmocka framework to help us (dynamically!) control what our i2c_read_blocking function does. The first step of using cmocka is to define mocks for the functions you want to mock out. The two mocked functions are shown below. void __wrap_i2c_transmit_blocking(uint8_t address, uint8_t offset, uint8_t* data, uint8_t data_size) { // allows the calling test to check if the supplied parameters are as expected check_expected(address); check_expected(offset); } void __wrap_i2c_read_blocking(uint8_t address, uint8_t offset, uint8_t* pData, uint8_t data_size) { // allow the calling test to specify the data it wants back // and copy it back out for(int i=0; i < data_size; i++) { pData[i] = mock_type(uint8_t); } }
cmocka cleverly uses the linker to swap out the real function
calls for the mocked ones. To allow for this, mocked out functions are prefixed with __wrap_. The linker is then provided with the arguments --wrap=i2c_read_blocking -Wl,--wrap=i2c_transmit_blocking which allows these functions to be mocked out. For a complete example of compiling/linking with cmocka, see the makefile makefile in my example project. With the mocks in place we can now write some tests! A complete look at the tests in my example project can be found on my github github but, as an example, here's a single test: static void test_negative_temperature(void **state) 9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
?;20 * 7@ A
,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
!"#$%#"$!&' !$)*+
{ will_return(__wrap_i2c_read_blocking, 0b11100111); will_return(__wrap_i2c_read_blocking, 0b00000000); assert_true(tmp101_get_temperature() == -25); }
The two calls to will_return set what the i2c_read_blocking function writes into the pData array. As per the datasheet, the 12 bit sample is returned over two bytes so these values correspond (from the above TI table) to -25 degrees celsius, which we check against. Much easier than having to stick the temperature sensor in the freezer! Running all of the tests through a Makefile gives this cool print out:
Although this is just a simple example, it hopefully demonstrates the utility that having unit tests can provide. Having a suite of unit tests for a project gives developers a lot of confidence - I find unit tests especially useful in developing application layer protocols on top of lower level protocols. If you're interested in poking around a little at the source for this example, see how it's all linked together an run as a test or even run it yourself, all code for this is avaliable on my github with instructions of how to run. 9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
?;20 I 7@ A
,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
!"#$%#"$!&' !$)*+
I hope you found this article interesting, if you have any question please don't hesitate to let me know!
Back Back to Home
! !
Comments
Community
$ Recommend
8
1 !
" Share
Login
Sort by Best
Join the discussion…
Phillip Johnston 2 months ago !
Thanks for writing this up. I was chatting with my buddy about strategies for simulating hardware for testing on your host PC. I had no idea cmocka exists, so glad to know I don't have to create it myself :). !
Reply
!
Share ›
a3zzat 3 months ago !
nice article !
Reply
!
Share ›
ad3a 3 months ago !
HI Sam, nice article but here you're building for the host system, i.e. you're running the test suite on a host processor which will link against libcmocka built for the host itself. What about buildi ng cmocka directly for the embedded platform? Any idea for that? !
Reply
!
Share ›
Sam Lewis !
Mod
# ad3a
3 months ago
Good point! I'd argue that the logic you're unit testing should really work on any (standard) compiler, and if it doesn't I'd ask why not - platform independent 9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
?;20 % 7@ A
,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
!"#$%#"$!&' !$)*+
code is nice! I do see your point though, it'd be nicer to use the same compiler for testing and production code. Depending on the platform you're compiling for, it might even be possible to use cmocka. 1
!
Reply
!
Share ›
ad3a # Sam Lewis !
3 months ago
Right, actually I spent the last couple of days trying to find if anybody has ever tried to build cmocka for some specific embedded platform... actually the cmocka documentation doesn't help a lot in this sense. My goal would be to have the production code unit tested running on a simulator of the embedded platform, I really feel this would speed up code development and final integration and verification. 1
!
Reply
Share ›
!
SAM LEWIS
MSP432 serial printf •
•
— when i run this code i get the error: "#20 identifier
A peek under Bitcoin's hood •
•
— Ah, right! So the signature script creates a "locking
Using the MSP432 serial (eUSCI) modules •
•
— You need to exchange EUSCI_A2_MODULE
Automatically tracking my thesis progress •
•
— When I wrote my thesis I used grunt to autocompile
' Subscribe ( Add Disqus to your siteAdd DisqusAdd ) 9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
Privacy ?;20 & 7@ A
,-./ /01/.-2 03405505 6750 8./9 6376:; < =;3 >08.1
9//BC##888D1;3E08.1D30#"$!%#$+#03405505FG-./F/01/.-2F8./9F6376:;#
!"#$%#"$!&' !$)*+
?;20 A 7@ A