Writing a unit test involves holding the “system under test” in a software vise and controlling all the other interactions of the system under test with the rest of the system. In an object-oriented programming language, the easiest way to control the interactions of your system under test with the rest of the universe is to interact with the universe through polymorphic interfaces. Because your system under test is collaborating with the universe through interfaces, it is not coupled to other concrete classes in your application. To unit test the system and its interactions, you provide “fake” or “mock” objects implementing the interfaces of the system’s collaborators. The system under test has no idea its collaborating with fake objects because it is coupled to an interface, not a concrete class.
Normally in a Direct3D application, the device is considered as a “tell” interface only. You tell the device what you want to draw and what state to use for drawing it. In these situations, what you want to know with your fake or mock object is whether or not your application set the right drawing state and drew the correct data. The essence of the interaction is that you want to know what data your application sent to the device. The easiest way to interrogate that in a unit test is to create an object that implements the device interface and simply records what you sent to it. It provides an extended public interface that allows your test to query the data it received from the system under test. This is generally referred to as a “fake” object, because its nature is state based. You configure the fake by telling it how to respond to the system under test. In the Direct3D context, most methods return an HRESULT indicating success or failure of the method. Some methods return data to the application, but most don’t. I generally write fakes as simple lightweight objects that are configured to return constant return values for a method and remember the last set of input parameters passed to the method.
However, the IDirect3D9 interface is the interface you use to interrogate the system as to what display modes it supports, what device capabilities are present and so-on. This is an interface that interacts with your application repeatedly. This interface has more behavior associated with it and its harder to reproduce real-life scenarios with a simple “fake” object. Instead we need something where we can configure the response of a method based on the inputs given to it. When behavior is involved, the object is generally referred to as a “mock” object instead of a fake object.
Languages with reflection have sophisticated mock frameworks where you simply instantiate the mock framework with the interface and using reflection, the framework generates all the mocked methods on the fly by reflecting on the interface. Direct3D applications are usually written in C++, which doesn’t have reflection. This means that the mock objects must be created by hand. There are a variety of ways that you could accomplish this. You could build the mock behavior step-by-step as you need it. Over time as you generated more and more mocks for your systems under test, you could generalize the behavior specific to mocking and refactor your specific mock objects to share the behavior common to all mock objects. Fortunately, you don’t have to go through all of this. mockpp is a mock framework for C++ objects. Using mockpp we can create a configurable mock object for the IDirect3D9 interface and use it to unit test our device enumeration and creation code.
This has been a brief introduction to the concept of unit testing with interfaces, fake objects and mock objects. I will be writing a more detailed blog post with code at a later date. This will include some sample mocks and fakes for the interfaces in Direct3D that you can use in your unit testing.