Unit Testing in C Part 4 – Testing with Unity

This is the series on Unit testing in C for embedded development. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Unit Testing in C – Testing with Unity tutorial. Let’s get started.

Unit Testing in C – Testing with Unity

Prerequisites

I would recommend you to explore the relevant topics by using the below link.

  1. Unit Testing Introduction
  2. Code Coverage
  3. Installing Ceedling

Introduction

In this tutorial, we are going to discuss –

Before doing anything, we have to know about the unity.

Unity

Unity is simply a rich collection of assertions you can use to establish whether your source code behaves the way you think it does. Unity provides a framework to easily organize and execute those assertions in test code separate from your source code. There are many TEST_ASSERT functions are available in the Unity framework, which is used to validate the values. We will see one by one.

TEST_ASSERT_XXX Functions

Validating Boolean

These are the functions used to validate the boolean condition.

FunctionNote
TEST_ASSERT_TRUE (condition)If the condition is true, then this evaluates to pass otherwise fail.
TEST_ASSERT (condition)This function is another way of TEST_ASSERT_TRUE
TEST_ASSERT_FALSE (condition)If the condition is false, then this evaluates to pass otherwise fail.
TEST_ASSERT_UNLESS (condition)This function is an another way of TEST_ASSERT_FALSE
TEST_ASSERT_NULL (pointer)If the pointer is NULL, then this evaluates to pass otherwise fail.
TEST_ASSERT_NOT_NULL (pointer)If the pointer is not a NULL, then this evaluates to pass otherwise fail.

Example

Validating Integers

Separate set of functions are available for signed, unsigned integers that too for all sizes. Please have a look at below functions.

FunctionsNote
TEST_ASSERT_EQUAL_INT (exp, act)Compare two signed integers for equality and display errors
TEST_ASSERT_EQUAL_INT8 (exp, act)Compare two 8bit signed integers for equality and display errors
TEST_ASSERT_EQUAL_INT16 (exp, act)Compare two 16bit signed integers for equality and display errors
TEST_ASSERT_EQUAL_INT32 (exp, act)Compare two 32bit signed integers for equality and display errors
TEST_ASSERT_EQUAL_INT64 (exp, act)Compare two 64bit signed integers for equality and display errors
TEST_ASSERT_EQUAL (exp, act)This is another way of calling TEST_ASSERT_EQUAL_INT
TEST_ASSERT_NOT_EQUAL (exp, act)Compare two signed integers for a not equality and display errors
TEST_ASSERT_EQUAL_UINT (exp, act)Compare two unsigned integers for equality and display errors
TEST_ASSERT_EQUAL_UINT8 (exp, act)Compare two 8bit unsigned integers for equality and display errors
TEST_ASSERT_EQUAL_UINT16 (exp, act)Compare two 16bit unsigned integers for equality and display errors
TEST_ASSERT_EQUAL_UINT32 (exp, act)Compare two 32bit unsigned integers for equality and display errors
TEST_ASSERT_EQUAL_UINT64 (exp, act)Compare two 64bit unsigned integers for equality and display errors
TEST_ASSERT_INT_WITHIN(delta, expected, actual)
This evaluates to pass if the actual signed value is within plus or minus delta of the expected value. This also comes in size specific variants like 8bits, 16bits, 32bits and 64bits.
TEST_ASSERT_UINT_WITHIN(delta, expected, actual)
This evaluates to pass if the actual unsigned  value is within plus or minus delta of the expected value. This also comes in size specific variants like 8bits, 16bits, 32bits and 64bits.
TEST_ASSERT_GREATER_THAN(threshold, actual)This evaluates to pass if  the actual value is greater than the threshold. This also comes in size specific variants.
TEST_ASSERT_LESS_THAN(threshold, actual)This evaluates to pass if  the actual value is lesser than the threshold. This also comes in size specific variants.

Example

Validating hex values

These are the functions used to validate the unsigned hex values.

FunctionsNote
TEST_ASSERT_EQUAL_HEX (exp, act)Compare two unsigned hex values for equality and display errors
TEST_ASSERT_EQUAL_HEX8 (exp, act)Compare two 8 bit unsigned hex values for equality and display errors
TEST_ASSERT_EQUAL_HEX16 (exp, act)Compare two 16 bit unsigned hex values for equality and display errors
TEST_ASSERT_EQUAL_HEX32 (exp, act)Compare two 32 bit unsigned hex values for equality and display errors
TEST_ASSERT_EQUAL_HEX64 (exp, act)Compare two 64 bit unsigned hex values for equality and display errors

Validating bits

These are the functions used to validate the bits in the value.

FunctionsNote
TEST_ASSERT_BITS (mask, exp, act)Apply the integer mask to specify which bits should be compared between two other integers. In mask, if any bit is 0 means it will ignore and if any bit is 1 means it will compare that bit between exp and act.
TEST_ASSERT_BITS_HIGH (mask, act)This call used to check whether bits are set to high or not using the mask. In mask, if any bit is 0 means it will ignore and if any bit is 1 means it will check the bit is high or not with act value.
TEST_ASSERT_BITS_LOW (mask, act)This is opposite of TEST_ASSERT_BITS_HIGH.This will check whether the masked bit is low or not.
TEST_ASSERT_BIT_HIGH (bit, act)This is used to test a single bit and verify that it is high. The bit is specified 0-31 for a 32-bit integer.
TEST_ASSERT_BIT_LOW (bit, act)This is used to test a single bit and verify that it is low. The bit is specified 0-31 for a 32-bit integer.

Example

Validating Strings and pointers values

These are the functions used to validate the strings and pointers values.

FunctionsNote
TEST_ASSERT_EQUAL_PTR (exp, act)Checks the both exp pointer and act pointer are same or not.
TEST_ASSERT_EQUAL_STRING (exp, act)This checks the two NULL terminated strings. It will fail if any character is different.
TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len)
This checks the two strings till it reach the given len. It will fail if any character is different.

Example

Validating Structures and memory

We cannot validate the structure through its members. But we can validate the structure using memory compare. This function is used to compare the memory and structure as well.

Note : You have to know the structure padding. I will always fail if structure is padded by 0.

FunctionsNote
TEST_ASSERT_EQUAL_MEMORY (exp, act, len)Compare the memory of len bytes from exp and act . Fails if it is not matching.

Example

Validating Arrays

We have functions to validate the arrays also like above. These are the functions to be used to check the arrays.

FunctionsNote
TEST_ASSERT_EQUAL_INT_ARRAY (exp, act, elem)This will compare the two signed integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_INT8_ARRAY (exp, act, elem)This will compare the two 8bit signed integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_INT16_ARRAY (exp, act, elem)This will compare the two 16bit signed integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_INT32_ARRAY (exp, act, elem)This will compare the two 32bit signed integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_INT64_ARRAY (exp, act, elem)This will compare the two 64bit signed integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_UINT_ARRAY (exp, act, elem)This will compare the two unsigned integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_UINT8_ARRAY (exp, act, elem)This will compare the two 8bit unsigned integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_UINT16_ARRAY (exp, act, elem)This will compare the two 16bit unsigned integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_UINT32_ARRAY (exp, act, elem)This will compare the two 32bit unsigned integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_UINT64_ARRAY (exp, act, elem)This will compare the two 61bit unsigned integer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_HEX_ARRAY (exp, act, elem)This will compare the two hex value arrays exp and act of elem elements
TEST_ASSERT_EQUAL_HEX8_ARRAY (exp, act, elem)This will compare the two 8bit hex value arrays exp and act of elem elements
TEST_ASSERT_EQUAL_HEX16_ARRAY (exp, act, elem)This will compare the two 16bit hex value arrays exp and act of elem elements
TEST_ASSERT_EQUAL_HEX32_ARRAY (exp, act, elem)This will compare the two 32bit hex value arrays exp and act of elem elements
TEST_ASSERT_EQUAL_HEX64_ARRAY (exp, act, elem)This will compare the two 64bit hex value arrays exp and act of elem elements
TEST_ASSERT_EQUAL_PTR_ARRAY (exp, act, elem)This will compare the two pointer arrays exp and act of elem elements
TEST_ASSERT_EQUAL_STRING_ARRAY (exp, act, elem)This will compare the two string arrays exp and act of elem elements

Example

_MESSAGE variant

If you add _MESSAGE to the names of any assertion listed above for the message variant (and include your own string as the final parameter in the assertion). The message variant of TEST_ASSERT_EQUAL_INT is given below. That message will be printed when it is failing.

TEST_ASSERT_EQUAL_INT_MESSAGE(exp, act, message)

Example:

You should see the print like this.

Like this you can add the _MESSAGE to any functions.

Ignoring the test case

If you want to ignore the test case then you have to use this function.

TEST_IGNORE()

This will not run the respected test case and ignore it.

Failing the test case

If you want to fail the test case you can use the below function.

TEST_FAIL()

Note:

Functions are available for float and double also. Out of this, I think, I’ve covered most of the functions which we use widely. If I’ve missed anything please find that in ThrowTheSwitch.

Now we will move to the example project.

Creating a new project

Now I am going to create a new project which has no source code initially. In this project ceedling will be linked automatically since we are creating the project using ceedling.

Create the project using ceedling new proj_name using the command terminal (command prompt) in your desired directory (folder).

Example:

After this, you can able to see the prints like below.

Cool. Now you have created the new project using ceedling.

Now you could able to see the new folder (directory) is created by ceedling called proj_name. In our case the folder (directory) name is simple_prog. Inside that folder you can see src and test directories along with that one project.yml file. We will discuss about project.yml later. In those two directories, no files will be there. We have to create those files.

Requirement for the development

First we will get a requirement. Need to develop the function like mentioned below.

  • I’ve to have three 8-bit global variables called Jill, Jung and Jukk.
  • Need to create the function called do_bit_man(uint8_t position).
  • We have to set the bit of the Jill variable based on the position.
  • We have to clear the bit of the Jung variable based on the position.
  • We have to toggle the bit of the Jukk variable based on the position.

Got your requirement? Ok, let’s write the code now. Oops, wait a minute. This is TDD. Before writing the code, we need to have a test plan and test code. Let’s plan how we are going to test.

Testing Plan

Let’s create a test plan based on the requirement above.

  1. Check the argument whether it is valid or not.
  2. If it is valid then do set, clear, and toggle to respected variables.
  3. Then check the variables

Now let’s write the code.

Writing a Sample source code in C

Here we are not going to write the complex program. We will go step by step. So let’s write the code for bit manipulation. To create source code, we need .c and .h files. Using ceedling also we can create the source template. We can create a module using ceedling module:create[module_name].

First go into the project directory where project.yml file stays. Then enter the command to create the module.

After this, you can see the prints like below.

Three files have been created automatically while creating module. Now we can use those files to add our source code.

NOTE: In this tutorial, we are not going to compile the program or test the output of the program. Out intention is to write the unit test for out source code.

I am going to write the source code for above requirement. Please find the code below.

bit_manipulation.c

bit_manipulation.h

All good? I have written one function called do_bit_manThis is one unit of the source code. So we are going to test that function.

Testing those functions with Unity

Till here, we have source code. Now we will write the unit test code. Open the test_bit_manipulation.cunder the test directory. In that test_bit_manipulation.c there might be some default code will be there like below.

We are going to write out own test case. So we can remove the function called test_bit_manipulation_NeedToImplement.

What is setUp function?

This is kinda start function which is used to initialize some variables. This setUp function is executed before each test function is run. If you
have three test functions in your test file, setUp gets called three times.

What is tearDown function?

This is kinda end function which is used to free some variables. This tearDown function is executed after each test function is run. If you have three test functions in your test file, tearDown gets called three times.

Note: You have to include the unity.h file in every test file. And include your require header files also.

Test Case 0

Let’s write our first test case to test the function do_bit_man(uint8_t position). So I am going to create the test function called test_do_bit_man_0(void).This function name can be anything but make sure you are adding test_ in front of that function name. This test case is negative test. When I pass more than 7, it should return -1 and it should not modify the any values of those global variables. Please find the below function.

To verify this, you can run ceedling test:allMake sure that you are running the command terminal on the directory where the project.yml file is present. When you run this, you will get prints like below.

Our test case has passed. It is not modifying any of the variable and returns -1.

Test Case 1

Another negative test case we have to do. When I pass negative value (less than 0), that time also it should behave as test case 0. Write our second test case.

You will get prints like below.

Our two negative test cases also passed. Let’s write our positive test case.

Test Case 2

If we pass the valid argument ( 0 to 7), then it should set, clear and toggle the respective variables in the position of argument and it should return 0.

You should get print like below.

Complete Test code

These are the possible test cases that we can write. Please see the complete test code below.

Code coverage

If you want to see the code coverage, please follow the below steps.

Install the gcovr using pip.

pip install gcovr

You can see the prints like below if you have installed correctly.

Then have to add gcov plugin using project.ymlOpen the project.yml and add - gove after plugin like below.

That’s it. Now you can see the report by using ceedling gcov:all. You will get details like below.

If you want to generate detailed html review, then please use the below command after ceedling gcov:all.

ceedling utils:gcov

You will get prints like this.

Once it is generated, then you can see the html file in simple_prog\build\artifacts\gcovOpen those two files which is generated and analyse. Right now it is 100%. That means we have covered all the lines and branches. You will get report like this.

coverage resultI want you guys to experiment on those by removing any of the test case and regenerate the report. Now you may see some percentage drop. Like this you can learn about code coverage.

Whenever you regenerate the report please clean it and regenerate or follow the steps below to get the updated report.

  1. ceedling clean  – This will clean the generated files.
  2. ceedling test:all – build and test the test case
  3. ceedling gcov:all– generate coverage result
  4. ceedling utils:gcov – Generate the html detailed report

By this experiment you can see how many branches we have and how many lines we have covered etc. You may get report like below if you remove test case 2.

Unit Testing in C - Testing with Unity

If we remove, one test case (test case 2), then we are missing one branch and 4 lines. If you don’t understand the code coverage, please go back here and read about the code coverage.

Note: Let’s say you have one test case where you have three TEST_ASSERT_X function. If any one fails, it will stop there and won’t run next line in that test case. It will run the next test case. So all TEST_ASSERT_X should pass in order to make the test case to pass.

0 0 vote
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x
%d bloggers like this: