Direct3D Test Doubles, Part 1

When developing Direct3D code using test-driven development, you need test doubles to stand in for interfaces used by your code. This way you can control the interaction of your system under test with its environment. This post is the first in a series of posts that discuss several ways you can create test doubles for the COM interfaces used by a Direct3D application.

What is a Test Double?

There are a wide variety of names floating around for the kind of code I’ll be describing in this post. You have probably enountered the terms “stub”, “fake”, or “mock” at some point. Whether we use the term “dumb object”, “stub”, “fake” or “mock” isn’t so important as long as we can agree on what these terms mean. Personally, I use “test double” as the generic term referring to any code that I’m going to use exclusively for testing to represent some portion of real production code. Here I’m following the language used by Gerard Meszaros in his book “xUnit Test Patterns” in the Test Double pattern. I use the terms “fake” and “mock” in a manner consistent with Michael Feathers in his book “Working Effectively With Legacy Code”.

To summarize, a “fake” is what I call any class that simply records arguments passed to methods and returns fixed answers to queries. A “mock” is what I call any class that simulates more complex behavior. These two terms don’t represent different categories so much as they represent two ends of a continuum. I can add more and more configurable behavior to a fake until at some point its more accurate to call it a mock. Meszaros defines some other terms for other points on this continuum in the Test Double pattern, but I shall stick with the simple distinction of fake and mock.

When to Create a Test Double?

When we are using a test double, we are taking a component of the production environment and replacing it with another piece of code. The test code is going to record the interaction of the system under test with the component and going to supply fixed answers to the system under test in response to queries. However, not every component used by your code needs to be abstracted in this manner. For instance, we rarely need a test double for std::string. Acting on strings doesn’t depend on the time of day, it doesn’t depend on the registry, it doesn’t depend on the network and it doesn’t depend on the filesystem. String operations don’t exhibit variability that could cause our test to fail due to unexpected changes in the environment. Similarly, we don’t find the need to create test doubles for things like floating-point arithmetic or trigonometric functions.

Similarly, for Direct3D applications there are a number of concrete types that don’t necessitate test doubles: vector classes such as D3DXVECTOR3, matrix classes such as D3DXMATRIX and so-on. I call these “concrete types” as they are provided by the DirectX SDK as C++ classes directly. The other kind of object provided by the DirectX SDK is exposed to an application through a COM interface, such as IDirect3D9, IDirect3DDevice9 and ID3DXFont. The classes exposed as COM objects generally represent a higher level of behavior and tend to be the classes where you want to create test doubles so that you can supply doubles to your system under test and make assertions about the interaction between the test double and the system under test. Since COM objects are exposed as pure virtual interfaces, this makes it easy to create test doubles for them in C++.

A Test Double for IUnknown

In C++, the best way to provide test doubles is to cooperate with a component through a pure virtual interface. The test double derives from the interface and provides an implementation of the interface and additional members that can be used to probe how your system under test interacted with the test double. The test double is polymorphic with the production component used by your system under test. Since a COM interface looks like a pure virtual interface in C++, this is easily accomplished by having your test double derive from the COM interface. For example, the base interface for all COM objects is IUnknown and we can create a test double for that interface with the following code:

#include <rt/Exceptions/NotImplementedException.h>
 
class FakeIUnknown : public IUnknown
{
public:
    FakeIUnknown() { }
    virtual ~FakeIUnknown() { }
 
    // IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
    { throw rt::NotImplementedException(); }
 
    virtual ULONG STDMETHODCALLTYPE AddRef()
    { throw rt::NotImplementedException(); }
 
    virtual ULONG STDMETHODCALLTYPE Release()
    { throw rt::NotImplementedException(); }
};

This simple test double does nothing but throw an exception whenever one of its methods are called. Since every COM object derives from IUnknown, you have to provide an implementation for these methods. The implementation may be sufficient for a Direct3D application because its rarely necessary to call IUnknown methods directly when interacting with an existing COM interface obtained from Direct3D or D3DX. If you do call methods on IUnknown, its most likely you would call AddRef and Release and not QueryInterface.

I called this class “FakeIUnknown” because it doesn’t simulate any behavior whatsoever. Every method throws a not implemented exception. Its useful to start out with a fake that throws “not implemented” on every method when you aren’t sure which methods are called by your system under test. As you run the test, you will get exceptions every time a method on your test double is called and you can then go back and create fake implementations for those methods only. When the system under test collaborates with an interface that has many methods, or with many different interfaces, this can be a useful way to focus your efforts on creating only the methods you need for your test to pass.

A more useful fake for this interface is one that remembers when the methods were called and with what arguments and allows you to configure the return values from your test:

class FakeIUnknown : public IUnknown
{
public:
    FakeIUnknown()
        : QueryInterfaceCalled(false),
        QueryInterfaceLastRIID(),
        QueryInterfaceFakeResult(0),
        QueryInterfaceFakeHResult(E_FAIL),
        AddRefCalled(false),
        AddRefFakeResult(0),
        ReleaseCalled(false),
        ReleaseFakeResult(0)
    { }
    virtual ~FakeIUnknown()
    { }
 
    // IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
    {
        QueryInterfaceCalled = true;
        QueryInterfaceLastRIID = riid;
        *ppvObject = QueryInterfaceFakeResult;
        return QueryInterfaceFakeHResult;
    }
    bool QueryInterfaceCalled;
    IID QueryInterfaceLastRIID;
    void *QueryInterfaceFakeResult;
    HRESULT QueryInterfaceFakeHResult;
 
    virtual ULONG STDMETHODCALLTYPE AddRef()
    {
        AddRefCalled = true;
        return AddRefFakeResult;
    }
    bool AddRefCalled;
    ULONG AddRefFakeResult;
 
    virtual ULONG STDMETHODCALLTYPE Release()
    {
        ReleaseCalled = true;
        return ReleaseFakeResult;
    }
    bool ReleaseCalled;
    ULONG ReleaseFakeResult;
};

You’ll notice a couple of things you might find unusual about this class. First, it appears to do nothing useful. Its a parrot. It only repeats back what you tell it beforehand. In particular, notice that the methods always give back the same return values no matter how many times you call them and no matter what arguments are passed to the methods. Second, you’ll notice that there are public data members! Almost every book on C++ and class design tells you to encapsulate data behind getters and setters. But test doubles are really dumb classes and you want them to be naked and expose themselves to your test cases, so we don’t encapsulate their data members.

A Test Double for COM Objects

A test double for IUnknown is useful because all COM objects will derive from IUnknown and you’d like to share that implementation across all your test doubles for COM objects. Suppose you want to make a test double for ID3DXBuffer, which derives from IUnknown. You might think that the easiest way to reuse this implementation is through inheritance, right? You might try this:

class FakeID3DXBuffer : public ID3DXBuffer, public FakeIUnknown
{
public:
    FakeID3DXBuffer() { }
    virtual ~FakeID3DXBuffer() { }
 
    // ID3DXBuffer
    STDMETHOD_(LPVOID, GetBufferPointer)()
    { throw rt::NotImplementedException(); }
    STDMETHOD_(DWORD, GetBufferSize)()
    { throw rt::NotImplementedException(); }
};

When you compile this code, you get the following unexpected messages:

test.cpp(5) : error C2259: 'FakeID3DXBuffer' : cannot instantiate abstract class
        due to following members:
        'HRESULT ID3DXBuffer::QueryInterface(const IID &,LPVOID *)' : is abstract
        c:\dxsdk\include\d3dx9core.h(109) : see declaration of 'ID3DXBuffer::QueryInterface'
        'ULONG ID3DXBuffer::AddRef(void)' : is abstract
        c:\dxsdk\include\d3dx9core.h(110) : see declaration of 'ID3DXBuffer::AddRef'
        'ULONG ID3DXBuffer::Release(void)' : is abstract
        c:\dxsdk\include\d3dx9core.h(111) : see declaration of 'ID3DXBuffer::Release'

What’s going on here? This is an example of the “diamond problem” that arises due to multiple inheritance of implementations in C++. Both ID3DXBuffer and FakeIUnknown derive from IUnknown. The compiler doesn’t know how to provide the implementation for ID3DXBuffer’s IUnknown base and gives us the unexpected message that we can’t instantiate FakeID3DXBuffer without providing implementations for QueryInterface, AddRef and Release. If we controlled the entire source base, we could annotate the interfaces with the virtual keyword so that the C++ compiler could figure out how to resolve the diamond problem. However, we are at the mercy of COM interfaces that are defined by others and we can’t backfill the virtual keyword into the headers (and due to the nature of COM interfaces it might not be the right solution anyway).

Instead of reuse through inheritance, we’ll need to reuse the implementation through composition. If we change FakeID3DXBuffer as follows, then we’ll have a fake that we can instantiate with no problems.

class FakeID3DXBuffer : public ID3DXBuffer
{
public:
    FakeID3DXBuffer()
        : Unknown()
    { }
    virtual ~FakeID3DXBuffer() { }
 
    // IUnknown
    FakeIUnknown Unknown;
    STDMETHOD(QueryInterface)(REFIID iid, LPVOID *ppv)
    { return Unknown.QueryInterface(iid, ppv); }
    STDMETHOD_(ULONG, AddRef)()
    { return Unknown.AddRef(); }
    STDMETHOD_(ULONG, Release)()
    { return Unknown.Release(); }
 
    // ID3DXBuffer
    STDMETHOD_(LPVOID, GetBufferPointer)()
    { throw rt::NotImplementedException(); }
    STDMETHOD_(DWORD, GetBufferSize)()
    { throw rt::NotImplementedException(); }
};

Every time we create a fake COM object, we’ll need to repeat that chunk of code that delegates to the FakeIUnknown member. We can create a macro for that in the header that defines FakeIUnknown so that we can use this in all the derived interfaces:

#define CONTAIN_UNKNOWN(member_)                        \
    FakeIUnknown member_;                               \
    STDMETHOD(QueryInterface)(REFIID iid, LPVOID *ppv)  \
    { return member_.QueryInterface(riid, ppvObject); } \
    STDMETHOD_(ULONG, AddRef)()                         \
    { return member_.AddRef(); }                        \
    STDMETHOD_(ULONG, Release)()                        \
    { return member_.Release(); }

Now we’ve got the basic tools we need to create test doubles for a hierarchy of COM interfaces. So far, the test doubles we’ve shown have only been fakes. In subsequent parts of this series, we will add more configurable behavior to the fakes until they are full blown mock objects. Along the way, we will explore using some open source libraries to assist us in eliminating repetitive code from our fakes.

kick it on GameDevKicks.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: