0

let's suppose I have a c source file (source file under test) with content:

uint8 reg1 = I2CRead(20)
uint8 reg2 = I2CRead(24)
uint8 reg3 = I2CRead(28)

if (reg1 != 0x10) || (reg2 != 0x11) || (reg3 != 0x12)
{
   register content wrong ...
} 

else
{
   register content is OK...
}

The I2CRead() function is constructed as follow:

uint8 I2CRead (uint8 address)
{
   I2C_Hw_Istance()->DTR = address  //DTR = Data Transmit Register

   return I2C_Hw_Instance()-> DRR   //DRR = Data Receive Register 
}

Now I'm trying to write a Unit Test (using cpputest framework) where I would like to "fake" the values read back by the function I2CRead() so that all equality expressions within the if() are fulfilled.

The Unit Test file is isolated from the "source file under test" but I can access on a special way the following function within the source file under test: I2C_Hw_Instance().

Using this function I can "fake" my register values within the Unit Test file on a way like this:

I2C_Hw_Istance()->DTR = 20;
I2C_Hw_Istance()->DRR = 0x10;

Because I have 3 I2CRead() function called successively I need to fake the DTR and DRR by each I2CRead() call individually. So this means the Unit test file needs to know when the next I2CRead() function is called to be able to manipulate the values in DTR and DRR.

How to do this in general, any idea?

My Idea was to have a kind of a fake file which is build together with the unit test (just "conceptual"):

FakeFile.c

void fakeRegisters()
{
   if first call of I2CRead() than:
     I2C_Hw_Istance()->DTR = 20;
     I2C_Hw_Istance()->DRR = 0x10;

   if second call of I2CRead()than:
     I2C_Hw_Istance()->DTR = 24;
     I2C_Hw_Istance()->DRR = 0x11;

   if tird call of I2CRead() than:
      I2C_Hw_Istance()->DTR = 28;
      I2C_Hw_Istance()->DRR = 0x12;
}
2501
  • 25,460
  • 4
  • 47
  • 87
JohnDoe
  • 825
  • 1
  • 13
  • 31
  • 1
    That's called *mocking* and there are many ways to accomplish what you want. Please do some searches for *mock* or *mocking* in relation to unit testing and you should be able to get links to both tutorials, examples and frameworks to help you. – Some programmer dude Mar 09 '17 at 13:56
  • Does `I2CRead` reside in the same source file as the function being tested or in a different file? – dbush Mar 09 '17 at 13:57
  • @dbush : It's in the same file – JohnDoe Mar 09 '17 at 13:59
  • 1
    Please note that testing an I2C driver without any real I2C hardware is a complete nonsense test. – Lundin Mar 09 '17 at 14:00
  • @Lundin : That's clear. The idea here is just to test that an special function is called if wrong or right values are read by I2C. It's an "expected" function call test. – JohnDoe Mar 09 '17 at 14:03
  • @ Some programmer dude Mocking is used to fake the interaction of functions which are located in other modules. My function is located within the source file under test, so mocking will not help here. – JohnDoe Mar 09 '17 at 17:09
  • You'll need to indirect the function so that you can mock it. The obvious way is through a function pointer; in normal use, it points to your i2c driver, and in your tests, you set it to point to your fake implementation. – Toby Speight Mar 10 '17 at 08:33
  • @Tom Thanks for the info. I tried something what you presented in the example. So, in my Unit Test i added a new file "Fake.c" and implemented the I2CRead_fake(uint8 address) within this file. Also within this fake file I defined uint8 (*I2CRead)(uint8) = I2CRead_i2c. Now in my Unit Test I'm injecting the mock implementation I2Cread = I2CRead_fake. But when compiling I get the error that I2CRead is already defined within the Fake.c . What I'm doing wrong? – JohnDoe Mar 10 '17 at 15:44

1 Answers1

1

You need a choice of implementations of I2CRead():

uint8_t I2CRead_i2c(uint8_t address)
{
   I2C_Hw_Istance()->DTR = address  //DTR = Data Transmit Register
   return I2C_Hw_Instance()-> DRR   //DRR = Data Receive Register
}


uint8_t I2CRead_fake(uint8_t address)
{
    switch (address) {
    case 20: return 0x10;
    case 24: return 0x11;
    case 28: return 0x12;
    }
    fail_test();                /* however you do this in your xUnit */
}

Now, in your code, you normally use the "real" implementation:

uint8 (*I2CRead)(uint8) = I2CRead_i2c;

When you run your tests, you need to inject your mock implementation:

I2CRead = I2CRead_fake;

This is one of the simplest fake implementations; there's a whole spectrum of fakery available to you if you need it (you might want to have an array containing your register contents so that you can test both the success and failure paths for starters).

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
  • This means it's legal in my Unit Test, when it's time to call the real I2CRead_i2c(), instead this to redirect the call to the I2CRead_fake() ? – JohnDoe Mar 10 '17 at 10:03
  • 1
    Sorry, I couldn't understand that question. Your test will have that last line (`I2CRead = I2CRead_fake;`), but you do that only in your test, and not your main production code. It's called *Dependency Injection*, and that's a good search term to read up on. HTH. – Toby Speight Mar 10 '17 at 10:18
  • Ok, sorry but I'm a beginner in this area and therefore maybe my question was not clear. My understanding regarding Unit Testing up to now was that if I have to test a function within an "source file under test", than the unit test should really call the "real" function and not redirect to a "fake" function. Fakes are allowed only on functions which are external within the source file under test. But yes, I have to read more about it. – JohnDoe Mar 10 '17 at 10:30