简体   繁体   中英

CppUTest not running on target - How to “fake” register

I'm doing Unit Testing of embedded C code without running on target hardware.Here is one part of the code:

uint8 tempReadback = 0;

write_to_i2c( msg->addres, msg->value );


     tempReadback = read_from_i2c( msg->addres);

     if( tempReadback == msg->value )
     {
        somethingA;
     }

     else
     {
        somethingB;
     } 

The function write_to_i2c() writes a value to a specific register. The function read_from_i2c() reads back the value from the register. Further, I'm using the variable tempReadback to compare if the read back value is the same as the written one. So far OK, and this works on the target hardware. Now I'm doing the Uni Tests without running the code on target Hardware (Software in the Loop). This means, the expression tempReadback == msg->value will never be true (tempReadback is 0) and I will run each time in the statement somethingB . Is there any way to fake the register read back? I'm using CppUTest as framework.

Will be thankful!

CppUTest is the perfect fit for embedded C development, since it is the only testing framework that allows to mock free functions (in your case, write_to_i2c() and read_from_i2c() ).

Now, you should really be reading the CppUTest documentation or the excellent book Test Driven Development for Embedded C .

Anyway, the following code shows how to do it.

Your Unit Under Test (UUT), written in a way that is compilable (next time you ask a question on SO please make the effort):

#include "temperature.h"
#include "i2c.h"

void somethingA(void) { }
void somethingB(void) { }

void temperature_do(uint8_t address, uint8_t value) {
    write_to_i2c(address, value);
    const uint8_t tempReadback = read_from_i2c(address);
    if (tempReadback == value) {
        somethingA();
    } else {
        somethingB();
    }
}

As you wrote, we need to "fake", or more exactly we need to "mock" write_to_i2c() and read_from_i2c() . We put the mocks in a separate file, say i2c_mock.cpp, so that when building the Unit Test, we link against the mock and not against the real implementation:

extern "C" {
#include "i2c.h"
};

#include "CppUTestExt/MockSupport.h"

void write_to_i2c(uint8_t address, uint8_t value) {
    mock().actualCall(__FUNCTION__)
        .withParameter("address", address)
        .withParameter("value", value);
}

uint8_t read_from_i2c(uint8_t address) {
    mock().actualCall(__FUNCTION__)
        .withParameter("address", address);
    uint8_t ret = mock().returnIntValueOrDefault(0);
    return ret;
}

Please refer to the CppUMock documentation for details. This is just classic CppUMock boilerplate.

Last part is the Unit Test:

extern "C" {
#include "temperature.h" // UUT
};

#include "CppUTest/TestHarness.h"
#include "CppUTest/CommandLineTestRunner.h"
#include "CppUTestExt/MockSupport.h"

TEST_GROUP(Temperature)
{
    void setup() {}
    void teardown() {
        mock().checkExpectations();
        mock().clear();
    }
};

TEST(Temperature, somethingA)
{
    const uint8_t value = 10;
    mock().ignoreOtherCalls();
    mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
        .andReturnValue(value);
    temperature_do(10, value);
}

TEST(Temperature, somethingB)
{
    const uint8_t value = 10;
    mock().ignoreOtherCalls();
    mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
        .andReturnValue(value+1);
    temperature_do(10, value);
}

int main(int argc, char** argv) {
    return CommandLineTestRunner::RunAllTests(argc, argv);
}

This UT will actually give 100% branch coverage. Again, I cannot explain all the details. If you observe and compare test cases somethingA and somethingB , you will see what is needed to cause the UUT to once go in the path that calls somethingA() and once to go in the path that calls somethingB() .

Let's take for example

mock().expectOneCall("read_from_i2c")
    .ignoreOtherParameters()
    .andReturnValue(value+1);

Here we are saying to CppUmock to expect a call to function read_from_i2c() , to ignore what the parameters are and, this is of fundamental importance, to return value + 1 (or anything else you fancy that is different from value ). This will cause the UUT to go in the path that calls somethingB() .

Happy embedded C development and happy unit testing!

I take a look into your proposed book about TDD and the mock objects. OK, so as I understand, for example in this line (mocks are already created):

mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
    .andReturnValue(value);
temperature_do(10, value);

the program "jumps" to the mocked "read_from_i2c" function (from i2c_mock.cpp) with parameters defined by myself and not the real one from the Unit Under Test? Or we really call our function from Unit Under Test but we manipulate this function with the parameters defined in the mock?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM