So far we been learning how to test different kind of functions, but there is one little important thing we been missing on purpose, embedded means hardware, and so far we didn’t test anything involve with hardware. First one thing that must be very clear is that ceedling and all the unit test runs on our computer using a compiler “similar” but different from the one we use to compile for our microcontroller, and therefore is not possible to test the actual microcontroller hardware in our computer, but we can do “mocks”

Before jumping into microcontroller hardware, let suppose the following piece of code that reads and adc, well in reality simulates this action by generating random numbers from 0 to 255

app/myadc.h

#ifndef __MYADC_H_
#define __MYADC_H_

uint8_t adc_get_sample( void );

#endif

app/myadc.c

#include <stdint.h>
#include "myadc.h"
#include <stdlib.h>

/*Fucntion to simulate a random ADC sampling and lecture
just for educational purpose*/
uint8_t adc_get_sample( void )
{
    srand(10);
    /*return a value from 0 to 255 to represent
    an 8 bit adc lecture */
    return rand() % 256;
}

And that functionality is going to be used by another function in our well now dummy driver

app/dummy.h

#ifndef __DUMMY_H__
#define __DUMMY_H__

uint8_t get_temperature( void );

#endif // __DUMMY_H__

Our function is going use the information from the ADC to calculated the temperature using a simple formula (invented by myself), the thing is we are not interested on testing the fucntion in myadc.c file because it is already tested or involved hardware we can’t test, any of the reason is valid.

app/dummy.c

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

uint8_t get_temperature( void )
{
    /*get the value from the ADC*/
    uint8_t temp = adc_get_sample();
    /*calculate the temperature according to 
    formula: Temp = (ADCval / 10) * 2 */
    temp = (temp / 10) * 2;
    return temp;
}

Since we are clear the function to test is the one in dummy.c, we include myadc.h but with the prefix mock, and in the test case function we write a mock version of adc_get_sample() that will allow us to simulate a return value of a 100. basically we a re testing the line 11 in function get_temperature() is doing what is supposed to do.

test/test_dummy.c

#include "unity.h"
#include "dummy.h"
/*tell ceedling to mock myadc driver*/
#include "mock_myadc.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test__get_temperature__20_degrees( void )
{
    /*this is the mock version of low level function adc_get_sample(), 
    with it simulate we took a lecture of 100, remmenber this fucntions 
    is the one called by the function under test using some "mumbo jumbo"
    ceedling own magic*/
    adc_get_sample_ExpectAndReturn( 100 );

    /*call the function under test that should return a 20*/
    uint8_t temp = get_temperature( );
    TEST_ASSERT_EQUAL_MESSAGE( 20, temp, "Temperature is not the value expected" );
}

Prior to run the test we need to add the cmock configuration to our project.yml file where amount other things we can specify the actual prefix to mock software units and the plugins to generate extra mock versions of a given function

# Configure the mock generator
:cmock:
  :mock_prefix: mock_
  :treat_externs: :include
  :when_no_prototypes: :warn
  :enforce_strict_ordering: TRUE
  :plugins:
  :treat_as:
    uint8: HEX8
    uint16: HEX16
    uint32: UINT32
    int8: INT8
    bool: UINT8

And add to ignore list myadc.c file to avoid complains from GCOV

# enable and configure code coverage
:gcov:
  :abort_on_uncovered: true
  :utilities:
    - gcovr
  :reports:
    - HtmlDetailed
  :uncovered_ignore_list:
    - app/main.c #
    - app/app_ints.c #
    - app/app_msps.c #
    - app/myadc.c #

Run the test and take a look at lines 10 and 14, cmock is running

$ ceedling gcov:all utils:gcov 


Test 'test_dummy.c'
-------------------
Creating mock for myadc...
Generating runner for test_dummy.c...
Compiling test_dummy_runner.c...
Compiling test_dummy.c...
Compiling mock_myadc.c...
Compiling unity.c...
Compiling dummy.c with coverage...
Compiling CException.c...
Compiling cmock.c...
Linking test_dummy.out...
Running test_dummy.out...
Creating gcov results report(s) in 'Build/ceedling/artifacts/gcov'... Done in 0.422 seconds.

--------------------------
GCOV: OVERALL TEST SUMMARY
--------------------------
TESTED:  1
PASSED:  1
FAILED:  0
IGNORED: 0


---------------------------
GCOV: CODE COVERAGE SUMMARY
---------------------------
dummy.c Lines executed:100.00% of 4
dummy.c No branches
dummy.c Calls executed:100.00% of 1
dummy.c Lines executed:100.00% of 4

CMOCK

Ok but that was nice, but where the hell came from the adc_get_sample_ExpectAndReturn function from my test_dummy.c file. Ceedling comes with cmock which is a program that takes a header files and from the prototype functions create mock or “fake” versions of each function, by default for our adc_get_sample fucntion creates the following mocks

adc_get_sample_ExpectAndReturn( cmock_retval ) 
adc_get_sample_ExpectAndThrow( cmock_to_throw )

Where can i see this?, take a look at the mock version of myadc.h in Build/ceedling/test/mocks/mock_myadc.h. cmock will create a series if fucntion depends on the original one, our fucntion get_sample return a parameter, that is why a ExpecteAndReturn mock version is created by mock so you can specify the value to return.

Let add another function to myadc.h with no value to return.

NOTE: you do not even need to declare the fucntion in myadc.c to create a mock version of it, this is great because it will allow you to test the functionality of the calling function without an actual implementation
void adc_init( void );

Run the test again and open the file mock_myadc.h, to see the extra mock functions of adc_init you can see it does not create a ExpecteAndReturn it only made the Expected version, which means the function will be expected to be called by the function under test.

adc_init_Expect()
adc_init_ExpectAndThrow( cmock_to_throw )
adc_get_sample_ExpectAndReturn( cmock_retval)
adc_get_sample_ExpectAndThrow( cmock_to_throw )

Play around with a fucntion to returns and expect a parameter, or several ones to see what kind of mock version generates cmock, also add more plugins to see some more extra functions

# Configure the mock generator
:cmock:
  :mock_prefix: mock_
  :treat_externs: :include
  :when_no_prototypes: :warn
  :enforce_strict_ordering: TRUE
  :plugins:
    - :ignore
    - :callback
    - :ignore_arg
    - :return_thru_ptr
  :treat_as:
    uint8: HEX8
    uint16: HEX16
    uint32: UINT32
    int8: INT8
    bool: UINT8

You can find a nice official documentation in the link down below where you can find how and when to use each mock function

https://github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md