To give you a bit of an insight into my system - I've got a hardware device that can be communicated with using USB, SPI or UART bus from a host like Raspberry PI or regular PC (USB only). It's a communication dongle and the library should be available to users so the ease of use is a crucial thing for me.
Now I'd like to apply tests to my code (Gtest). I've already made an interface called Bus, to which you can inject objects of USB/SPI/UART classes.
The Bus interface pointer is in the dongle's class let's say "Dongle". When the user creates the Dongle class object, the constructor is called in which an appropriate Bus object is created based on an enum given by the user in Dongle constructor parameters. Moreover it tries to reset the dongle into a known state using the Bus interface methods (transfer). Some pseudocode to visualize:
#include "uart_dev.hpp"
#include "spi_dev.hpp"
#include "usb_dev.hpp"
class Dongle
{
private:
Bus* bus;
public:
Dongle(busType_E type)
{
switch(type)
{
case busType_E::SPI:
{
bus = new SPI();
break;
}
case busType_E::UART:
{
bus = new UART();
break;
}
case busType_E::USB:
{
bus = new USB();
break;
}
}
reset();
}
bool reset(void)
{
/* uses the selected object under the bus interface */
return bus->transfer(...);
}
};
How can I mock the USB/SPI/UART communication busses, when I have to replace the object created in the Dongle constructor? I know I could pass the USB/SPI/UART object into the Dongle constructor, but this might be confusing for the end users and if possible I'd like to avoid that.
I was also wondering about solutions given here, however I'm not sure how could I replace the created object with a mock just before the reset() method is called.
Maybe my approach is totally incorrect - any advice is appreciated.
CodePudding user response:
You might have extra constructor:
class Dongle
{
private:
std::unique_ptr<Bus> bus;
public:
// also used for testing to pass a MockBus
// Can be made protected, and make friend a test factory.
explicit Dongle(std::unique_ptr<Bus> bus) : bus(std::move(bus)) { reset(); }
explicit Dongle(busType_E type) : Dongle(make_bus(type)) {}
static std::unique_ptr<Bus> make_bus()
{
switch(type)
{
case busType_E::SPI: { return std::make_unique<SPI>(); }
case busType_E::UART: { return std::make_unique<UART>(); }
case busType_E::USB: { return std::make_unique<USB>(); }
}
return nullptr; // Or other error handling as throw
}
// ...
};