Jesse Brands
Jesse Brands

Reputation: 2877

How to write tests for classes relying on the operating system

I have written a class that enumerates over the operating system's physical displays, retrieving their information and capabilities. I actually would like to test that this class works appropriately and embrace (unit) tests in general.

However, I have no idea how I would go about testing this class. Leaving the implementation details aside, it's basically defined as so:

class VideoMode {
public:
    int64 width;
    int64 height;
    std::vector<int64> frequencies;
}

class Display {
protected:
    std::vector<VideoMode> modes_;
    std::string display_name_;
    std::string adapter_name_;
    bool primary_;

public:
    Display(char* osDevName, char* osAdapterDevName);

    // typical getters
}

How would I go about testing something that is so deeply integrated and dependent on the OS, and the physically attached hardware. I understand that writing an unit test for a class like this is difficult, so what alternatives do I have?

Upvotes: 2

Views: 334

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275800

First step: don't unit test not the interface with the OS, but the rest of the code. There is some code there; not much, but some.

Create an even lower API that your nice C++ interface talks to; it mimics a C style API your OS probably provides, even if it is a C++ class.

Like

struct OSDisplayInterface {
  virtual ~OSDisplayInterface() {}
  virtual std::size_t GetDisplayName( char const* name, char const* adapter, char* name, std::size_t name_buf_len ) = 0;
  virtual bool IsDisplayPrimary( char const* name, char const* adapter ) = 0;
  virtual bool GetVideoModeCount( char const* name, char const* adapter, std::size_t* mode_count ) = 0;
  struct video_mode {
    int64 width, height, frequency;
  };
  virtual bool GetVideoMode( char const* name, char const* adapter, std::size_t n, video_mode* mode ) = 0;
};

Or something that matches your lower level API as directly as you can. The idea is that this code should only fail in the real case if the OS's API fails.

Then test your C++ code against that, with fake OS video mode sets.


If you want to go further, this is basically outside of unit tests, but you can test either the above "OS display interface" or directly test your class against the OS.

To do this, however, you'll need a bunch of different hardware configurations.

Start with a farm of test machines you can remote-deploy code to and get results back.

Deploy your code, isolated from the rest of your code base, to those machines.

Each machine has an identifier on it.

Build a machine-identifier to test-results table.

Test against that. Ensure that when run on system "bob37", you get the right set of resolutions and the like.

Then, when an OS upgrade hits and the API is deprecated, you get an immediate red flag. (Of course, you also get red flags when a new driver is patched that gives new frequencies)

Unit test harness looks up the machine identifier, runs your code, and confirms the test results match.

Automate the deployment of the code to said machines so you can run the test on dozens of hardware platforms on a code change.

This is at best at the border of "unit testing". But you could imagine being in the middle of porting the OS-interface code to a new OS, environment or hardware, and running into problems that this would catch.

Upvotes: 1

Jarod42
Jarod42

Reputation: 218098

You should unit-test your class, not the os/external library functions.

You may add a facade layer which allows to mock those method. (That layer would not be unit tested by your UTs).

Something like:

class IOsVideoModeRetriever
{
public:
     virtual ~IOsVideoModeRetriever() = default;
     virtual std::vector<VideoMode> RetrieveVideoModes(/*...*/) = 0;
     // ...
};

// CLass with implementation of OS specific functions
class OsVideoModeRetriever : public IOsVideoModeRetriever
{
public:
     std::vector<VideoMode> RetrieveVideoModes(/*...*/) override;
     // ...
};

// Class for UT
class OsVideoModeRetrieverMock : public IOsVideoModeRetriever
{
public:
     MOCK(RetrieveVideoModes(/*...*/)); // Mock according to your framework
     // ...
};

And your other class use it something like:

class Foo
{
public:
    explicit Foo(IOsVideoModeRetriever&);

private:
    IOsVideoModeRetriever& mOsVideoModeRetriever; // Or use `shared_ptr`
                                                  // depending of life time guaranty
};

Now you can test Foo.

You indeed would have problems if the os specific functions doesn't behave as you expected (Format of result, limitation to handle, edge case, ...), that should be limited to implementation part and not the interface.

Upvotes: 1

Related Questions