Unit Testing in C Part 5 – Mock using CMock in Embedded

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 – Mock using CMock in Embedded. Let’s get started.

Mock using CMock in Embedded

Prerequisites

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

  1. Unit Testing Introduction
  2. Code Coverage
  3. Unit Testing using Unity

Introduction

In our last tutorial, we have used only one module. In that module, we have not called any other module. So we have just use unity to test that. Let’s discuss one scenario.

I have one function called do_bit_man(same from last tutorial). In that function do_bit_man, I am accessing hardware or another module.  If you access hardware it won’t work. Because we are going to run this unit test without hardware. And also our intention is to test only that function (do_bit_man ), not the hardware. So, How will we test that function (do_bit_man) now?

In this case, mocking will come into the picture. When your code depends on hardware or another module, you need to mock those hardware or other module functions.

What is mocking?

In simple English, Mocking is making a replica or imitation of something. Like that, in coding mocking is a method, that simulates the behavior of a real method/object in controlled ways. In other words, Mocking is a way to replace some functions with alternate implementations (“mocks”).

An object that you want to test may have dependencies on other modules. To isolate the behavior of the code or function you want to test, you need to replace the other dependencies by mocks. That will create some fake functions in controlled ways. Using that fake functions, we can eliminate the dependencies and test the code that we want to test. So, Mocks are used to break module dependencies so that they can be tested in isolation. There are many frameworks are available to do so. But ceedling has an inbuilt tool for doing that mocking. That is CMock. So we don’t need to install anything.

CMock

CMock is a framework for generating mocks based on a header API. All you have to do to use CMock is add a mock header file to the test suite file. You can generate the mock functions using #include "mock_example.h". Here, example.h is your file to create mock.

You can use these mocks to simulate different return values and to verify that your code calls functions in a particular order and with specific arguments.

You will understand very clearly if you go through the example below.

What happens if we don’t use mock?

I have just used the last tutorial‘s code. On top of that, I have made the below changes.

  1. I have created new source files (adc.c and adc.h files) where the adc_read function is present.
  2. In do_bit_man function, We need to print whether the temperature is high or low.
  3. I have not done any changes in test_bit_manipulation.c. I have just added #include "adc.h".

So, now the file structure will be, like the below image.

Mock using CMock in Embedded

Note: These adc.c and adc.h files are just for explaining the concept. So there, we aren’t actually accessing the hardware. So don’t look for any logical mistakes like movies. Hehe 😆 

I have attached the source code for your reference.

Code

adc.h

adc.c

bit_manipulation.c

bit_manipulation.h

test_bit_manipulation.c

That’s all. Let’s run the unit test using ceedling test:all.

Wahooooo. All the test cases were passed. Then why do we need to mock here? Okay, let’s check the code coverage.

Generate the report using the below commands.

  • ceedling gcov:all
  • ceedling utils:gcov

This is the result. Your result will be stored in simple_prog\build\artifacts\gcov.

Mock using CMock in Embedded

Here, adc.c is 100% but bit_manipulation.c is 90.9%. adc.c is out of scope for us. Because we are testing do_bit_man not adc_read.  Why this 9.1% got reduced in bit_manipulation.c? Let’s see the complete report of bit_manipulation.c.

Mock using CMock in Embedded

Here We have missed one line and branch. This branch (if) is not been covered by our unit testing code. What we have to in order to cover that branch? whenever you call adc_read, always it will return 0. Because hardware is not connected and it is not running in hardware. So how will adc_read return more than 30? Even If you add a new test case, you cannot control the hardware and adc.c file. Because it is not part of this module which you are testing. In order to control the functions of another module (dependencies), you need to use mock. Now we will see how mock is working.

Generating Mock using CMock

To generate mock for the header file, all we need to do is include the mock header file name in one of our unit test files. The mock header file name is the original header name prepended with mock_. For example, to create mocks for the functions in adc.h, we just include mock_adc.h in our test_bit_manipulation.c instead.

Ceedling automatically creates this mock_adc.h file and a corresponding implementation in mock_adc.c by using CMock. Let’s test this using ceedling test:all.

Now you forgot about the errors. We will discuss that later. Just look into the line number 7 of the above output. You can see that it is creating mock functions for adc. Then see line number 11. It has generated a mock file called mock_adc.c and compiling it. So this is how it is generating mock files.

For each function present in adc.h, It generates a bunch of new functions that you use to control the mock function. These new functions are based on the name and parameters of the original function. The below functions will be generated for adc_read() function.

  • adc_read_ExpectAndReturn(int return_value)
  • adc_read_IgnoreAndReturn(int return_value)
  • adc_read_StubWithCallback(callback)

The functions generated may vary based on the header file (mock_filename.h) and config file(project.yml). This means that, it may generate more functions or fewer functions based on your configurations and header file. If you want to see all the mock control functions that CMock has generated for a particular module, you can find them in the .h files in build/test/mocks.

By using this Mocked version, you can then verify that it receives the data you want as an argument, and return whatever data you desire, make it throw errors when you want, and more…

We will see all the possible generated functions below.

Mock Variants

There are multiple variants of mock functions will be generated. We will see some of the most used variants below.

Expect

The expect functions are always generated.

Original Function  Generated Mock Function Details
void func(void)  void func_Expect(void) By calling this, you are telling CMock that you expect that function to be called during your test. This won’t expect any arguments and won’t return anything.
void func(params)  void func_Expect(expected_params) By calling this, you are telling CMock that you expect that function to be called during your test with the given expected_params. It won’t return anything.
retval func(void)  void func_ExpectAndReturn(retval_to_return) By calling this, you are telling CMock that you expect that function to be called during your test. It won’t expect any argument and It will return the retval_to_return.
retval func(params)  void func_ExpectAndReturn(expected_params, retval_to_return) By calling this, you are telling CMock that you expect that function to be called during your test with the given expected_params. It will return the retval_to_return.

Example

Le’ts assume I am going to test this function.

Here, gpio_read() takes one argument which is gpio number. If we want to mock this gpio_read function, We have to use gpio_read_ExpectAndReturn(expected_params, retval_to_return)Because it has an argument and return value. The test case will be as below.

So based on your arguments and return value, you can use the respective variant of the Expect mock functions.

Ignore Arguments

If you don’t want to check the argument which is passed and you want to use the similar kinda Expect variants then you can use this method. This will just ignore the argument. This feature is not enabled by default. You have to add ignore_arg under plugin in the project.yml file. Please refer below.

Before change (project.yml) After change (project.yml)

 

 

This is particularly useful when that argument is a pointer to a value that is supposed to be filled in by the function. This will just ignore the argument which you are telling. The rest of other arguments will be checked.

Original Function  Generated Mock Function Details
void func(void)  nothing Mock function won’t be generated since it doesn’t have any arguments.
void func(paramName)  void func_IgnoreArg_paramName(void) By calling this, you are telling CMock to ignore the argument (paramName). It should be used along with expect variants. See the example below.

Example

Le’ts assume I am going to test this function.

Here, gpio_read() takes one argument which is gpio_num. We are going to ignore that gpio_num and return 1.  The test case will be as below.

Note: When you use this ignore function, it will ignore the particular argument until this current test case. You may call multiple instances of this to ignore multiple arguments after each expectation if desired.

So based on your arguments and return value, you can use the respected variant of the IgnoreArg mock functions.

Array

When you have an array argument, then you may need to use this feature. This feature is not enabled by default. You have to add array under plugin in the project.yml file. Please refer below.

Before change (project.yml) After change (project.yml)

 

 

Now, CMock provides an ExpectWithArray function your each mocked function that contain pointer arguments. For each pointer argument, the ExpectWithArray function takes an additional argument which specifies the number of array elements to compare.

Original Function  Generated Mock Function Details
void func(void)  nothing It won’t generate any mocked functions. Because an additional function is only generated if the params list contains pointers
void func(ptr * param, other_params)  void func_ExpectWithArray(ptr* param, int param_depth, other_params) It will add one more argument after the pointer parameter which tells the depth of the array.
void func(Other_params, ptr * param)  void func_ExpectWithArray(other_params, ptr* param, int param_depth) It will add one more argument after the pointer parameter which tells the depth of the array.
retval func(Other_params, ptr * param)  void func_ExpectWithArrayAndReturn(other_params, ptr* param, int param_depth, retval_to_return) It will add one more argument after the pointer parameter which tells the depth of the array. And it returns the retval_to_return.

Example

Le’ts assume I am going to test this function.

Here, gpio_read() takes one argument which is gpio_num (int array). The test case will be as below.

So based on your arguments and return value, you can use the respective variant of the Array mock functions.

Ignore

If you don’t want to care about any functions then you can use this feature. This feature is enabled by default.

Original Function  Generated Mock Function Details
void func(void)  void func_Ignore(void) By calling this, you are telling CMock to ignore any of this call.
void func(params)  void func_Ignore(void) By calling this, you are telling CMock to ignore any of this call.
retval func(void)  void func_IgnoreAndReturn(retval_to_return) By calling this, you are telling CMock to ignore any of this call and return this retval_to_return.
retval func(params)  void func_IgnoreAndReturn(retval_to_return) By calling this, you are telling CMock to ignore any of this call and return this retval_to_return.

Note: These Ignore only needs to be called once per test. It will then ignore any further calls to that particular mock. 

Example:

Le’ts assume I am going to test this function.

Here, gpio_read() takes one argument which is gpio_num. We are going to ignore these calls and return 1.  The test case will be as below.

So based on your arguments and return value, you can use the respective variant of the Ignore mock functions.

ReturnThruPtr

In embedded, sometimes you may need to pass a pointer to another module and get data from that pointer. In such a case, you can use this feature. This feature is not enabled by default. You have to add return_thru_ptr under plugin in the project.yml file. Please refer below.

Before change (project.yml) After change (project.yml)

 

 

Now, CMock provides an ReturnThruPtr function your each mocked function that contain pointer arguments.

Original Function  Generated Mock Function Details
void func(param) void func_ReturnThruPtr_paramName(val_to_return) This will be widely used for writing the data into int, struct etc.
void func(param) void func_ReturnArrayThruPtr_paramName(cal_to_return, len) This will be used for arrays
void func(param) void func_ReturnMemThruPtr_paramName(val_to_return, size) This will be used for memory and struct.

Example

Le’ts assume I am going to test this function.

Here, gpio_read() takes one argument which is gpio_num (pointer).  In this case, we should not care about the argument. Because it is taking pointer. We don’t know which address it is passing. So we have to ignore that. But we need to write 3 elements in that pointer. Then only we can check that if else part. So we are going to use this feature and writing data through the pointer. The test case will be as below.

So based on your arguments and return value, you can use the respective variant of the ReturnThruPtr mock functions.

Callback

If you want to mock something complicated, then you can use this feature. This feature is enabled by default. This will create a mock function called _StubWithCallback.

Original Function  Generated Mock Function Details
void func(void)  void func_StubWithCallback(Callback) You have to pass your own callback function’s pointer as an argument. That callback will be getting called when that original function been called. This callback function should match the original function.

 

So if you have a function that looks like this:

Your callback function needs to look like this:

You need to provide one more extra argument like above. That will be used to track how many times the callback is called.

Note: The name of the callback function can be anything. But it should not match with original name.Whatever you return from your callback is what is provided to the calling function during your test — just like the custom callback was called instead of the mock function.

You will understand that better way If you see the example below.

Example

Le’ts assume I am going to test this function.

Here, gpio_read() takes one argument which is gpio number. To test this case we are going to create the custom callback and register that using gpio_read_StubWithCallbackSo this callback function will be called when you call the gpio_read function in the code. That custom callback function should match with the original function of arg and return type.

The test case will be as below.

So according to the above example, custom_callback_gpio_read will be getting called when Test_gpio calls the gpio_read. That custom_callback_gpio_read returns 0 if gpio_num is not 1. Otherwise, it will return 1.

I think now you will have a better idea about Mock using CMock in Embedded. So now we will come to the original example. I expect you to achieve the code coverage to 100% by using mock functions by yourself. If you have any doubt you can refer to the below answer.

The solution of the original code

You can find the original code here. Now we are going to modify the test_bit_manipulation.c in order to achieve 100% code coverage. In the code, we have two branches which are if and else. So we are going to update our old test_bit_manipulation.c with cmock.

Please find the below code.

test_bit_manipulation.c

I have added CMock functions to Test cases 3 and 4. Let’s see the code coverage by using the below commands.

  • ceedling gcov:all
  • ceedling utils:gcov

We have achieved 100%. Let’s see the detailed report. Your result will be stored in simple_prog\build\artifacts\gcov.

Unit Testing in C Part 5 – Mock using CMock in EmbeddedNow you can see that we have covered all the branches and lines. You can try from your side and confirm the same.

What if I have created Mock and not using it in our test file?

Your test case will fail like this.

What if I want to mock more functions in one unit test?

Let’s take the below example.

In the above example, We need to mock the three functions (gpio_read, gpio_write, gpio_toggle). So you have to use the mocking function in the correct order like how they have called in the source code. Please refer to the example below.

If you change the order then your test case will fail and you will get an error. If you don’t want to follow the order then you have to change the settings of the project.yml file. We will see how to do that in our next tutorial.

That’s all about  Mock using CMock in Embedded. Sorry for the very big tutorial 🙄 . In our next tutorial, we will see the Advanced options.

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: