skip to content

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:

Terminal window
mkdir unit_testing_example
cd unit_testing_example/

Then clone Unity through GitHub inside of unit_testing_example:

Terminal window
git clone https://github.com/ThrowTheSwitch/Unity.git unity

Installing 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.c

Function 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:

Terminal window
mkdir test
touch test/test_string_join.c

Open 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:

Terminal window
mkdir test/main
touch test/main/all_tests.c
touch test/main/all_tests_runner.c

string_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:

Terminal window
mkdir src/
touch src/string_join.c
touch src/string_join.h

Compilation

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?

Terminal window
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:

Terminal window
./test_exec -v

The -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:

  1. Create simple test.
  2. Write the function.
  3. Make the first test pass.
  4. Refactor code.
  5. Add tests.
  6. Adjust code.