Unit Testing your C functions with Unity
A practical guide to unit testing C code using the Unity framework and Test-Driven Development.
4 min read
We’ve all done it—written a bunch of code and then toiled to make it work. Build it and then fix it. Testing was something we did after the code was done. It was always an afterthought, but it was the only way we knew
Test-Driven-Development for Embedded C by James W. Grenning
Warning: This is a pretty long tutorial, you can also just look at the code in the repository if you don’t feel like reading.
Contents
- My experiences
- Installing Unity: A unit-test framework for C
- An example of the Test-Driven-Development workflow
My experiences
Writing unit-tests first, and the functions after, worked magical for me. Some positive experiences I had/have:
- The bugs in my code almost vanished. The few bugs that are present are located very fast thanks to the unit-tests.
- Because you write the test first, you need to think about the function, its arguments and return value so that you don’t start to code immediately without thinking. This makes my code better and straight to the point.
- When you refactor your code or try to add small functionalities, the unit tests immediately show you whether you broke your code by changing things.
Installing Unity: A unit-test framework for C
Unity is the unit-testing framework we will be using throughout this tutorial, but there are a lot of others as well and they all do the same: Improve your code!
First, create a directory for the example we’ll cover below:
mkdir unit_testing_examplecd unit_testing_example/Then clone Unity through GitHub inside of unit_testing_example:
git clone https://github.com/ThrowTheSwitch/Unity.git unityInstalling Unity is as simple as that! Now let’s go on to an example in which we will be using Unity.
An example of the Test-Driven-Development workflow
”Test Driven Development (TDD) is software development approach in which test cases are developed to specify and validate what the code will do. In simple terms, test cases for each functionality are created and tested first and if the test fails then the new code is written in order to pass the test and making code simple and bug-free.” Thomas Hamilton’s article on guru99
Project structure
The example-project will have the following structure:
unit_testing_example/|---> unity/||---> src/| |---> string_join.c| |---> string_join.h||---> test/| |---> main/| | |---> all_tests.c| | |---> all_tests_runner.c| || |---> test_string_join.cFunction prototype
We are going to write a string join function in C. We are first going to write a test, before writing our string_join function itself (TDD). So let’s think about how the function prototype looks like:
char *string_join(char *s1, char *s2);Create our first test
A test case could be the following:
- Input s1: “Hello “
- Input s2: “World!”
- Output should be: “Hello World!”
Create a test directory and a test_string_join.c file:
mkdir testtouch test/test_string_join.cOpen test/test_string_join.c in your favorite text editor and open one of the toggles below!
Setting up the main function for Unity
Create main directory:
mkdir test/maintouch test/main/all_tests.ctouch test/main/all_tests_runner.cstring_join function
Now that we have our first simple test ready, let’s write the first version of our string_join.
Create the source files and directory:
mkdir src/touch src/string_join.ctouch src/string_join.hCompilation
You can also check the Makefile in the repo to see how the compilation works. Run make test to create the test_exec executable. Or run make test_run to create and run the tests.
We have all necessary source files and tests, but how to compile?
gcc -Wall -Wextra -Werror -D UNITY_OUTPUT_COLOR -D UNITY_FIXTURE_NO_EXTRAS -I./unity/src -I./unity/extras/fixture/src unity/src/unity.c unity/extras/fixture/src/unity_fixture.c test/main/all_tests.c test/main/all_tests_runner.c test/test_string_join.c src/string_join.c -o test_exec-D UNITY_OUTPUT_COLOR: Output red and green colors for fails and successes respectively.-D UNITY_FIXTURE_NO_EXTRAS: Disable memory handling by Unity. Read more about it here.-I./unity/src: Path to Unity headerfile.-I./unity/extras/fixture/src: Path to another necessary headerfile.unity/src/unity.c: Unity source file.unity/extras/fixture/src/unity_fixture.c: Another necessary source file.test/main/all_tests.c: The main function of our tester.test/main/all_tests_runner.c: The group runner for our tester.test/test_string_join.c: The tests for string_join.src/string_join.c: The source code of string_join.-o test_exec: Executable name is test_exec.
Run tester:
./test_exec -vThe -v option makes the output more verbose (it shows you what tests you passed, which gives me a good feeling).
More tests
Add more tests to test_string_join.c and update the group runner in all_tests_runner.c as needed.
If you run the InputNullForS1 test, you will get a segfault because of the NULL pointer as input we are trying to dereference. Go back to the string_join function and fix the problem, then run all tests again to see if you fixed it!
Unity Assertions
You can compare much more than strings and NULL pointers. For example, structs, integers, arrays etc. You can find them all here.
Conclusion
Using unit-tests is as important as writing the code itself. I see the tests as a harness for your code—they protect it. Once the harness breaks, you have to repair it. The basic workflow is described below:
- Create simple test.
- Write the function.
- Make the first test pass.
- Refactor code.
- Add tests.
- Adjust code.