Search code examples
c++unit-testingc++11googletest

Unit Testing application interface to hardware - to mock or not


im very curious for peoples opinions on what way a software side application that interfaces with hardware should be unit tested.

For example, the main class of the software application "Connection" would be constructing a handle to a USB device.

I want to test the "Connection" class base function, say "OpenConnection" that would attempt to connect to a USB hardware device.

so far I have constructed a MOCK hardware device, and have included in my connection class a compiler flag, so if its built in unit test mode, it will use a mock object, otherwish it will use the actual hardware interface.

See example below

class TConnection
{
public:
    static TConnection* GetConnection();
    static void Shutdown();

    bool DidInitialise();

    bool Write(uint8_t* _pu8_buffer);
    bool Read(uint8_t* _pu8_buffer);

protected:
    TConnection();
    virtual ~TConnection();
    bool init();

private:
    static TConnection* mp_padConnection;
    static bool mb_DidInitialise;

#ifdef _UNIT_TEST_BUILD
    static mock_device* mp_handle;
#else
    static device* mp_handle;
#endif
};

then in the source file I include something like

#include "connection.h"

#ifdef _UNIT_TEST_BUILD
    mock_device* TConnection::mp_handle = nullptr;
#else
    device* TConnection::mp_handle = nullptr;
#endif // _UNIT_TEST_BUILD

TConnection::TConnection()
{
    ...
    init();
    ...
}

bool TConnection::init()
{
    mp_handle = hid_open( _VENDOR_ID, _PRODUCT_ID, nullptr );
    if (mp_hidHandle == nullptr) {
        return false;
    }
    if (hid_set_nonblocking(mp_hidHandle, _DISABLE_NB) == _ERROR_CODE) {
        return false;
    }
    return true;
}

The only thing I really dislike about my code is that my actual connection class contains test code. I would much prefer them to be separate.

Saying that, I also dont agree with having an entirely new mocked connection class written solely for the purpose of unit testing, it makes me feel like im just writting something designed to work as expected.

So I ask, what would be a better approach to testing such a class

Thank you in advance for your time and advice


Solution

  • You can avoid adding test code to your class by using dependency injection. Create an interface IDevice and make class Device implement that interface. Then, in class TConnection, use pointers to this interface instead of a member of type Device. Also create a helper method that allows you to set a new device, something like:

    void setDevice(IDevice *device);
    

    Now, for your production code simple use an instance of class Device, while in your test code use setDevice to swap implementation of device with a mock object. This mock object will be an instance of class MockDevice which will also implement interface IDevice. That way you can change the implementation in tests and use the mock class. Since you are using gtest already, I would suggest you do not write the mock class yourself but use C++ mocking framework gmock (which is fully compatible with gtest) instead. This way, you will also need to create a separate class but almost everything will be handled by the mocking framework. All you need to do is define mocked methods. Creation of an additional interface and mock class seems like overkill at first, but it definitely pays off in the long run. If you want to do any serious test-driving of code, learning to use interfaces, dependency injection and mock classes is essential. Check the documentation for more details:

    https://github.com/google/googlemock/blob/master/googlemock/docs/CheatSheet.md