One important activity we shall not miss is unit testing, a “must” on software development, saves a lot of time debugging errors in advance and make our life easier by far. There several frameworks we can use out there to test our code but we going to choose Ceedling since it was created explicitly to cover embedded needs. besides is relatively easy to use well documented and supported, for instance down below you can find very useful links to complete the information you will find here

Throw The Switch
Unit Testing (TDD) Embedded C Code. Making Awesome and Reliable Firmware in C Doesn’t Have to Suck.
Unity/docs/UnityAssertionsReference.md at master · ThrowTheSwitch/Unity
Simple Unit Testing for C. Contribute to ThrowTheSwitch/Unity development by creating an account on GitHub.
Remember we start with the assumption you have installed the tools from here and here

For Linux Users

We need to install ruby first

$ sudo pacman -S ruby

Then install the ceedling gem

$ gem install ceedling
$ gem update

open the shell configuration file

$ code ~/.zshrc

or

$ code ~/.bashrc

and type at the bottom, save and reset your terminal

export PATH=$PATH:~/.local/share/gem/ruby/3.0.0/bin
# Escape square brackets by default (workaraund for ceedling)
unsetopt nomatch

For Windows users

To install it first install ruby using choco

$ choco install ruby

Now add the path to to ruby bin directory to environment variable Path, you can use power shell too

$ $addPath = 'C:\tools\ruby31\bin'

close the power shell window and open it again to type

 $ gem install ceedling

Your first test case

We will assume a fresh new project from the template we usually use. At folder app lets create a custom driver with some dummy functions to perform a simple unit testing.

app/dummy.h

#ifndef __DUMMY_H__
#define __DUMMY_H__

uint32_t sum( uint32_t a, uint32_t b );

#endif // __DUMMY_H__

app/dummy.c

#include <stdint.h>
#include "dummy.h"

uint32_t sum( uint32_t a, uint32_t b )
{
    return a + b;
}

Create a folder called test and make a new file test_dummy.c to place the unit testing functions. By rule of thump name your test files with the name of the file to test with the prefix test_ you can change the prefix in Ceedling options but this is the option by default.

#include "unity.h"
#include "dummy.h"

void setUp(void)
{
}

void tearDown(void)
{
}

/*test case. by default all your test cases shall be start with test_
but as will see later this can be override*/
void test__sum__two_integers(void)
{
    /*run the function under test with known paramters and catch the return value
    in variable res*/
    uint32_t res = sum( 2, 3 );

    /*the following macro validates the result, first parameter is the expected result, 
    second one is the actual result from our function under test, and finally the thirtd one
    is just a message to display in case of error*/
    TEST_ASSERT_EQUAL_MESSAGE( 5, res, "2 + 3 = 5" );
    /*if you don't want to display messages on errors you can use the macro
    TEST_ASSERT_EQUAL( 5, res )*/
}

In order to run Ceedling with the test cases created we need a configuration file called project.yml in the root of your project

:project:
  :build_root: Build/ceedling/ # Directory where ceedling will place its output 
  :release_build: TRUE

:paths:
  :test:
    - test/**     # directory where the unit testing are
  :source:
    - app/**      # directory where the functions to test are

Run the tests to see a beautiful result with no failures

$ ceedling test:all


Test 'test_dummy.c'
-------------------
Compiling dummy.c...
Linking test_dummy.out...
Running test_dummy.out...
test_dummy.c:12:test__sum__two_integers:PASS

-----------------------
1 Tests 0 Failures 0 Ignored 
OK

But let’s force a failure, lets assume 2 + 3 = 6 (for some magic reason) just to make and example, the expected result will be in this case 6, adding a new test case.

void test__sum__error_result(void)
{
    uint32_t res = sum( 2, 3 );
    /*for simplicity reasons we assume 2+3=6 that is why the expected result is 6,
    but our function will return 5, failing the test in turn*/
    TEST_ASSERT_EQUAL_MESSAGE( 6, res, "2 + 3 = 6" );
}

run the tests to see one test passes and the other fails and also display the message as an extra information

$ ceedling test:all


Test 'test_dummy.c'
-------------------
Running test_dummy.out...
test_dummy.c:12:test__sum__two_integers:PASS
test_dummy.c:23:test__sum__error_result:FAIL: Expected 6 Was 5. 2 + 3 = 6

-----------------------
2 Tests 1 Failures 0 Ignored 
FAIL
Maybe Ceedling will throw some warning like these ones
C:/tools/ruby31/lib/ruby/gems/3.1.0/gems/ceedling-0.31.1/vendor/unity/auto/generate_test_runner.rb:344: warning: Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.
C:/tools/ruby31/lib/ruby/gems/3.1.0/gems/ceedling-0.31.1/vendor/unity/auto/generate_test_runner.rb:344: warning: Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.

Just ignore them, this has to do with the language use by ceedling which is ruby. I assume the maintainer will fix this problem soon or later