1

I'm working in a project targeting a Silicon Labs microcontroller. I want to create a wrapper for the Bluetooth SDK calls. SDK libraries are heavy on dependencies, so by creating a seem in that interface, I can avoid adding them to my tests. Note that this problem is not specific of the Silicon Labs platform, but I will leave the SiLabs SDK file names in case the reader is familiar with the platform.

I'm using CPPUTest as my testing framework. My code is a mix of C and C++, but for the purposes of this question, it's C.

For example, one of my wrapping functions (in a module called Bluetooth_HW) would be

/* On "Bluetooth_HW.c" */

#include "native_gecko.h" // declares gecko_cmd_le_gap_bt5_set_adv_data()

// Set a new advertisement, either as advertisement packet or as a response.
bool BT_setAdvertisementMessage(uint8_t scanResponse, uint8_t adLength, uint8_t* newAdvertisement) {
    uint16_t result = gecko_cmd_le_gap_bt5_set_adv_data(0, scanResponse, adLength, newAdvertisement)->result;
    if (result != bg_err_success) {
        return false;
    } // Else, no errors setting the advertisement
    return true;
}

BT_setAdvertisementMessage() wraps gecko_cmd_le_gap_bt5_set_adv_data(), which is defined in native_gecko.h, an SDK header file. I declare BT_setAdvertisementMessage() in Bluetooth_HW.h and define it in bluetooth_HW.c. My code that needs to set the advertisement message then includes Bluetooth_HW.h instead of native_gecko.h, and Bluetooth_HW.h is easy to mock.

I would like to test Bluetooth_HW.c. I need to mock native_gecko.h for that, as I am not interested in running its code (it's provided by the manufacturer, I trust it, plus I'm not unit testing native_gecko, I'm unit testing Bluetooth_HW). Normally, I would include the production .h file in my tests, to get declarations of the functions, and then substitute the .c file that contains the production code definitions with my fake implementation.

My problem is that native_gecko.h defines gecko_cmd_le_gap_bt5_set_adv_data() as an inline inside the header, instead of the more common approach of doing it on a .c file.

I could wrap native_gecko.h itself on a header that just declares the functions that I need, and make my code include that header, but then I don't have access to other stuff declared in native_gecko.h, like custom types and enums. I can't move those types and enums to the wrapper, as I can't remove them from native_gecko.h and the compiler would then complain of being defined twice, and I don't want to touch native_gecko.h, because it's an SDK file.

So, my question is how to mock modules that define code in their headers?

Joel Santos Rico
  • 326
  • 3
  • 17
  • I reply my own question, as I managed to get working after a day of different approaches. This might be just one way, and it might not be the best, happy to hear others – Joel Santos Rico Dec 15 '21 at 11:54
  • Why are you designing a test which isn't testing the actual function? That seems rather pointless. What exactly do you aim to do with this test, check if your compiler can generate code to pass parameters to functions, or...? A proper test of a "HAL" like layer will also test the underlying implementation. – Lundin Dec 15 '21 at 12:16
  • My test tests `BT_setAdvertisementMessage()`, which is code I wrote. It doesn't need to test `gecko_cmd_le_gap_bt5_set_adv_data()`, which is in a different unit. I can't mock `gecko...()` because it's header has it defined as inline, hence the question. Why not test `gecko...()`. 1. It's defined by manuf. I trust them to provide good code. 2. It doesn't run off-target, I want to test this on a PC (it's faster, I will do on-target tests at a different stage with other tools. Plus, this is an example, I have more complex funtions on that module that need more complex tests – Joel Santos Rico Dec 16 '21 at 11:18
  • I don't want the example to distract from the question. The question is how to mock a module when the header defines the functions' body, which to me was tricky. Mmaybe it's a stupid question, I wrote it and then came up with a solution and though somebody could benefit from it and decided to post it. Thanks for your opinion on what a proper "HAL" test should be, and about your concerns about my efforts being pointless, but I have my reasons to do it that way, which I don't think should distract from the question. Don't want to be snarky, apologies, but running out of comment space :D – Joel Santos Rico Dec 16 '21 at 11:21
  • My point is that the only thing this function does is to act as a thin wrapper around the actual function. So the only thing you could have possibly messed up is the parameter passing. Which should ideally be tested by calling the actual function to see if it behaves or starts acting strangely. Also, testing embedded systems code outside its intended hardware ranges from pointless to harmful - you could be missing out bugs that only surface in the real world application. The purpose of testing is to test _functionality_, not to run tests just for the sake of it. – Lundin Dec 16 '21 at 11:27
  • About it being a thin wrapper, you are right. This is the simplest example, it helps making the question more concise. About testing outside of HW being pointless to harmful, some people disagree (http://www.throwtheswitch.org/build/which). And I'm not saying I'm not testing on HW. This test will not run on HW, though, others with other tools, will. Again, please, focus on the question. If you think the question is invalid, or stupid, please flag it, don't let the example distract you from it. I'm not asking on advice on how to test. – Joel Santos Rico Dec 16 '21 at 11:50

1 Answers1

1

I managed to get it working by providing a fake native_gecko.h. The test environment is then pointer to the fake header instead of the production one. In my case, as native_gecko.h is massive, I just surgically added the minimum amount of elements required for it to work. I added just the declarations for the functions needed. Although I could have mimicked the behaviour of the real module and add them as inline functions, this would require including the mocking framework from CPPUTest to be included to native_gecko.h, which is C++. When the compiler tries to compile Bluetooth_HW.c as C code and pulls the header, it will complain about the presence of the C++ code. By adding the mocked function definitions to their own file, I could leave the mocking framework code out of the header, and my original, production code, C file would compile with just C code.

Roughly, the steps would be

  1. Make sure that the test project can't see the real, production code, native_gecko.h.
  2. Provide a fake version of it, substituting any defined functions for declarations (if the file is too big, as is the case of native_gecko.h, just create declarations for the functions used in your Code-Under-Test).
  3. Provide definitions for those functions.

Creating a fake header is not ideal, as the contents can change with an SDK update, for example, and there's room for human error, but it's the cleanest solution I could come up with.

Joel Santos Rico
  • 326
  • 3
  • 17