I'm working on software for a Cortex-M4 based microcontroller in C++. I have a lot of code (drivers, etc.) that is highly machine dependent. And I have higher level code which is closely dependant on the low level code by using the drivers directly. Example: a low-level part is eg. a UART driver which is very hardware-specific, and a high-level part is a communication protocol which is based on UART. (This software runs on "bare-metal", ie. there is no operating system underneath.)
This code is currently tightly coupled, thus not unit testable.
I'd like to make it testable.
So I figured that I'd create an abstraction of the low-level parts, and make the high-level parts depend only on the abstraction. I could then create mocks of the abstraction which would be used by the unit tests, and a real implementation which would run on the microcontroller.
virtual
functions in embedded systems. What other ways are there?So, in summary, I'd like to create a hardware abstraction layer (HAL), but I'm asking how to do it? Should I use virtual
inheritance in C++, or is there another, better way?
For C++ I would suggest using an interface so say we have a HAL.hpp
and in there we define our pure virtual functions we want to implement:
class HAL
{
virtual void func1() = 0;
virtual void func2() = 0;
};
Then you can have your Mock.cpp
implement this and you can have a Real.cpp implement the same:
Mock.cpp:
class Mock : HAL
{
virtual void func1(){ }
virtual void func2(){ }
}
Now the other way is you define your functions in HAL.h
like and provide no implementation here:
void func1();
void func2();
You then create a HAL.cpp
and add functionality there you wish to see on the target. Create all of this as library called HAL. Link this library to your main project.
Now for mocking and testing. Create a seperate project for your tests. Add the sources you want to test but do not link the HAL library. Instead create another source file Mock.cpp include the HAL.h and provide implementation for it. In this way instead of your functionality from the HAL library the Mock's implementation would be called.
Create the microcontroller HAL in separate files from the mock HAL. For the microcontroller, include the microcontroller HAL sources in your project. For the unit test system, include the mock HAL sources in your project.
You can also test on-target by using compiler macro defines to switch in pieces of the mock HAL, and switch out pieces of the microcontroller HAL.
You can even use your debugger to force values at interface points to trigger all paths; doing this with a code coverage tool will let you know if you've exercised all paths (and MC/DC if you need it). This is sometimes the only way to simulate hardware failures or exceptional conditions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With