1

I am currently trying to refactor some of my embedded C code and am trying to apply what James W. Grenning is recommending in his Test-Driven Development for Embedded C book. For this, I am dividing my code in modules for each of my abstract data types, a bit like it's done with classes in Java. However, I am running in sort of an "issue" here. I have many ADT for which I do not need getters nor setters. The only thing I need to do with them is 1. build the structure by extracting data from a byte array and 2. display the extracted data stored in the ADT on the screen of my device. To display the data, I'm using an external statically compiled library, and am accessing some of the drivers of my screen. I now would like to unit test the function that extracts the data from the byte array and builds my ADT. However, I do not have getters not setters to access the members of my structure. So, the only way for me to really unit test the function is by calling the display function, which is not really unit testable if I do not run the unit tests on an emulator and mocking the drivers. Is it "clean" in this case to implement getters and setters if they are only meant to be used in my unit tests?

To give a better example of my problem, assume I have an ADT that represent a TLV (tag length value) buffer:

in tlv.h, I'd have the following:

struct tlv typedef tlv_t;

// Builds a tlv_t struct from the tlv data stored in a byte buffer
tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length);

// display the tlv data stored in the tlv structure on my device screen.
int display_tlv(const tlv_t *tlv);

and in the tlv.c, I'd have the following:

#include "tlv.h"

typedef enum
{
  TAG_A,
  TAG_B,
  ...
} tlv_tag_t;

struct tlv {
  tlv_tag_t tag;
  size_t length;
  uint8_t *value;
};


tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length) {
   tlv_t *tlv = (tlv_t *)calloc(1, sizeof(tlv_t));
   if(!tlv)
   {
      return NULL;
   }

   // extract the tlv data in buffer and stores them in the tlv struct
   ...

   return tlv;
}


int display_tlv(const tlv_t *tlv) {
   // accesses the field of my tlv struct, and display them
   ...
}

Image I have the following buffer 0x00010004012345678. The tag and the length are uint16_t values in the buffer, so, when calling extract_tlv with the buffer above, I'd expect ending with the following tlv structure:

tlv.tag    = TAG_B,                      // 0x0001
tlv.length = 4,                          // 0x0004
tlv.value  = {0x12, 0x34, 0x56, 0x78},   // 0x12345678

Now, I would like to unit test this extract_tlv function, to be sure that if I send the buffer above, I get the structure above as an output. How can I do that in a clean way if I do not have getters and setters ? I think implementing getters and setters just for your unit test is not a good practice, because they won't make it to the production code, so, they should be used in your unit tests. An other approach we've tried is having the members of the tlv struct in a define, located in the tlv.h file. In our test files, we create a test_tlv struct, that uses the defines to delcare its member, and we do the same for the tlv_t struct in the tlv.c file. Then, we cast every tlv_t struct into a test_tlv_t struct in our unit tests, and just like that, we can access the members without having getters and setters:

In tlv.h:

#defin TLV_STRUCT_MEMBER \
  tlv_tag_t tag; \
  size_t length; \
  uint8_t *value;

typedef enum
{
  TAG_A,
  TAG_B,
  ...
} tlv_tag_t;

struct tlv typedef tlv_t;

// Builds a tlv_t struct from the tlv data stored in a byte buffer
tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length);

// display the tlv data stored in the tlv structure on my device screen.
int display_tlv(const tlv_t *tlv);

In tlv.c:

struct tlv {
  TLV_STRUCT_MEMBER 
};


tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length) {
   ...
}


int display_tlv(const tlv_t *tlv) {
   ...
}

and in test_tlv.c

#include "tlv.h"

struct test_tlv {
  TLV_STRUCT_MEMBER 
} typedef test_tlv_t;


TEST_EXTRACT_TLV() {
  test_tlv_t *tlv = (test_tlv_t *)extract_tlv(...);
  TEST_ASSERT_EQUAL(8, tlv.length);
  ...
}

But this solution is a bit hacky, and I'm not a big fan of casting my ADT into an other, even though they are technically the same.

What is the best "clean" practice here? Is there a good solution?

2 Answers 2

2

(After clarification...) Since all members of your ADT are private, you will not test their values. It is the same case with the standard type FILE, of which we only use pointers to. Like you do with your ADT.

Instead, test what is specified in your "contract": The extract function scans the byte array and fills the structure. The display function shows the values of the structure. The only publicly visible data flow is from the byte array to the display output, the intermediate representation is opaque.

You did not say so, but there might be some error detection in the functions. Test these, too. You will need to mock the error reaction function(s).

Concerning the display function, yes, that means you need to mock the display driver.

OT: If you declare the members of a structure private, do not publish them in the header file...

Sign up to request clarification or add additional context in comments.

5 Comments

Yes, this I am doing. I am testing all the fail cases, to be sure those are handled properly. But yeah, the only way for me to test a success case here would be to check that the function returns a valid pointer. Yet, if someone tomorrow comes, and edit the code that set the members of my struct, I will have no other way to test it than with the display function... Concerning mocking the display function, this will be a real pain in the A**. We are using a statically compiled library for an ARM system, and we would like to test it on a x86 system... What did I get myself into...
It's all about architecture. However, as long as all tests are green, nothing bad happened. (As a utopia.)
Where would be the architecture weak point here? The fact that I have a ADT with only a display function in it? That I actually have a display function in the ADT, where I should have it in an other module? or the fact that the display function depends on too many external element that are too tricky to mock?
Do you have a clean abstract interface between the ADT and the display? If yes, all is well, and you can easily mock the display. If not, it is a weak point. IIRC, this is in the book, too. But please let us stop this discussion, SO is not a forum, and architecture is not the issue of your question.
Concerning the ARM library: In general, I am always testing source code, not compiled code. If I do not trust a compiler or a 3rd party library, I have another big problem, which needs to be tackled separately. However, integration tests and system tests are additionally necessary, with the real device, and the real application.
1

Unit tests are actually white box tests, not blackbox. Nobody hinders you to access internals for these tests. Here are 2 more options:

  • In your test_tlv.c code, you include the tlv.c and not compile tlv.c separately

    • Advantage: you get access to internals of the module (but not to function scoped variables)

test_tlv.c

#include "tlv.h"
// include the module itself to access internals for tests
// don't compile and link tlv.c in the unitt test env
#include "tlv.c"

void test_extract_1(void) {
    uint8_t test1[] = "0x00010004012345678";
    
    tlv_t* res = extract_tlv(test1, sizeof(test1));
    
    TEST_ASSERT_NOT_EQUAL( res, NULL);
    TEST_ASSERT_EQUAL( res->tag, TAG_B); // <- 'tag' available through include tlv.c
    // ...
}   
  • Separate e.g. the struct tlv type in tlv_privtypes.h, which is normally only included in tlv.c, but for your tests, you ca include the tlv_privtypes.h additionally in your test_tlv.c:

tlv_privtypes.h

#ifndef TLV_PRIVTYPES_H_INCLUDED
#define TLV_PRIVTYPES_H_INCLUDED

typedef enum {
    TAG_A,
    TAG_B,
    ...
} tlv_tag_t;
 
struct tlv {
    tlv_tag_t tag;
    size_t length;
    uint8_t *value;
};

#endif

tlv.h

#ifndef TLV_H_INCLUDED
#define TLV_H_INCLUDED

typedef struct tlv tlv_t;

// Builds a tlv_t struct from the tlv data stored in a byte buffer
tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length);

// display the tlv data stored in the tlv structure on my device screen.
int display_tlv(const tlv_t *tlv);

#endif

tlc.c

#include "tlv_privtypes.h"
#include "tlv.h"

tlv_t * extract_tlv(const uint8_t *buffer, size_t buffer_length) {
    //  ...
}

int display_tlv(const tlv_t *tlv) {
    //  ...
}

test_tlv.c

#include "tlv.h"
#include "tlv_privtypes.h" // access to internal types for tests

void test_extract_1(void) {
    uint8_t test1[] = "0x00010004012345678";
    
    tlv_t* res = extract_tlv(test1, sizeof(test1));
    
    TEST_ASSERT_NOT_EQUAL( res, NULL);
    TEST_ASSERT_EQUAL( res->tag, TAG_B); // <- 'tag' available through include tlv_privtypes.h
    // ...
}

main.c - normal user file

#include "tlv.h" // normal users just include tlv.h

int main(void) {
    uint8_t buffer[MAX_BUFLEN] = {0};
    
    int rxlen = UART_Receive(buffer, MAX_BUFLEN);
    
    tlv_t* res = extract_tlv(buffer, rxlen);
    display_tlv(res);
    
    return 0;
}

2 Comments

I kind of like both of your propositions, but I think the first one leads to less tuning when it comes to build automation. Why would you use the first rather than the second option? Or the other way around?
With the first one, you can also have local tests of e.g. internal functions, which are not exported by the header. You can also more easily setup tests preconditions of internal conditions/states. You also don't need then a compiler switch to remove the 'static' keywords for variables or internal functions for testing purpose.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.