Udostępnij za pośrednictwem


Building for Testability in C++

Ten months ago, when I joined my current team, we had a C++ application that was written as many applications are, with a number of classes, inheritance, and classes creating other classes they needed in order to do their work. The trouble was that writing tests for this code was very hard because it was very difficult to isolate a single class for testing. We had some tests, but they were all written as black-box tests that exercised the UI. Unfortunately, as I mentioned in my blog post on No Regressions, UI tests are hard to write, hard to maintain, run slowly, and are fragile (in other words, small, intentional changes to the product can easily break the UI test).

In this blog, I’ll describe the architecture that I proposed and then implemented in order to allow us to test at the different levels of the pyramid. We’re dealing with a fairly small codebase (less than 10,000 lines), so we were able to rewrite the program over a period of about six months, while also adding new functionality. As I write this, we have a total of about 1,000 automated tests, divided into roughly 700 unit tests and 300 acceptances/UI tests (written by three people).

When I set out to build this architecture, I had the goal of being able to write tests in C# that would exercise our C++ code. Most of our unit tests are written in C++/CLI (see Writing Unit Tests in Visual Studio for Native C++ and C++/CLI to C++ Tips and Tricks) because we didn’t have all the pieces in place to write C# tests until a month or so ago. If I were to do this project again, I think I would have tried to get the C# part working earlier because our testers are more comfortable writing in C# (when we searched for testers, far more have C# than C++ experience). The 300 acceptance/UI tests are written in C#. More importantly, we can write mock objects in C#.

Using C# Mocks with C++ Product Code

Think about that last statement, because this is pretty interesting. Our testers can write mocks in C# that replace production C++ components during testing. In other words, it looks something like this:

image

How can you write a mock in C# (or VB.NET) that will replace a C++ component? Using COM (Component Object Model). COM has been around for a long time and is used inside Windows by a number of components. Some people dislike COM, and if you’re one of those people, please read on. I chose to adopt the very core of COM, and nothing else. Since .NET interoperates very well with COM, and it’s fairly easy to write COM components in C++, the COM object model acts very nicely as a bridge between the .NET test methods and the C++ product code that we’re testing.

Why Choose COM?

Why did I chose COM? Basically, because both C++ and .NET support COM. Here is the .NET support for COM that I needed:

  • .NET supports calling methods on COM interfaces
  • .NET supports writing classes that implement COM interfaces

When people think of COM, they don’t always think of the same thing, as there are many different levels (OLE, DCOM, COM++, etc.). But the very core of COM is actually quite simple. Every component supports at least one interface called IUnknown plus one or more other interfaces. And that’s really all there is to the core of COM.

The IUnknown interface supports object lifetime through reference-counting, and it also has a method, called QueryInterface, that allows access to other interfaces supported by the component. Here is the notation that is used for a COM component that supports two interfaces:

image

This component is a button that implements IUnknown and IControl. All interfaces inherit from IUnknown, so the three methods from IUnknown are always available. I’m not going to go into details on how to implement a COM component in C++ here—there are a lot of resources where you can find that, plus I want to focus on the high-level approach.

Using Private Classes and Class Factories

Traditionally C++ applications are written with the class declaration in a header file and the implementation in a “.cpp” file. This allows you to subclass an existing class. However, subclassing can make it more difficult to test classes, particularly when the base class contains implementation that could change and break a number of subclasses.

I decided to go the pure component route. Using a component model, you can’t subclass a component, nor can you create create a class directly. This approach makes it easier to test and reuse components since dependencies are explicit (I’ll explain dependencies between components in the next section), and it also helps focus design with separation of concerns in mind. In this section I’ll focus on using the factory pattern, where you define a public class with a public method for creating a new instance of a component. The class for the component, on the other hand, is private:

image

Using this approach has several advantages:

  • It ensures that no class is dependent on any other class. You can still have dependencies on other components, which is done by using interfaces
  • When building a component, you don’t have to flip back and forth between the header and implementation—it’s all in one file. You can have the implementation directly inline instead of having a separate class definition and implementation
  • Components are easier to replace with other components, which helps with testability (replacing a component with a mock, for example)
  • If a class depends on another component, you’ll need to supply that class to the component. This also make mocking easier

Here is a concrete example of this, showing the full header file, as well as an abbreviated implementation file.

RadioButtonWrapper.h
 #pragma once

#include "IRadioButton.h"

class RadioButtonWrapperFactory
{
public:
    static void Create(HWND hwndControl, HWND hwndDailog, IRadioButton **ppInstance);
};

This is the class factory. Pretty simple.

RadioButtonWrapper.cpp
 #include "stdafx.h"
#include "BaseControl.h"
#include "RadioButtonWrapper.h"
#include "UnknownImpl.h"
class RadioButtonWrapper : public BaseControl<IRadioButton>
{
public:
    RadioButtonWrapper(HWND hwndControl, HWND hwndDialog) : BaseControl(hwndControl), m_hwndDialog(hwndDialog)
    {
    }
 

private:
    ...
    virtual void EnableRadio(int id, BOOL enable)
    {
        HWND hWnd = ::GetDlgItem(m_hwndDialog, id);
        ::EnableWindow(hWnd, enable);
    }

    
    const HWND    m_hwndDialog;
};
 
void RadioButtonWrapperFactory::Create(HWND hwndControl, HWND hwndDialog, IRadioButton **ppInstance)
{
    *ppInstance = static_cast<IRadioButton *>(new RadioButtonWrapper(hwndControl, hwndDialog));
}

I’ve removed all except for one method. As you can see, the component is entirely contained within the CPP file, and therefore completely private. Only the factory is public, and it’s implementation is very simple.

Creating a Mock in C#

I’ll be filling the details on how to make all this work in some future posts. I also plan to provide a downloadable Visual Studio solution that has all the details working. For now, I’m going to provide a high-level overview of how you would write a mock object in C#.

There are a few things you’ll need. The interfaces that we use are all defined in header files, but .NET requires an interop assembly. In a later blog post I’ll describe how to create an interop assembly based on the C++ code. For now, let’s assume that assembly exists. Here is an actual mock object from our test suite:

 class MockCPUCheck : ICpuInfo
{
    private bool m_Is64bit;
 
    public MockCPUCheck(bool is64bit)
    {
        m_Is64bit = is64bit;
    }
 
    public int Is64Bit()
    {
        return m_Is64bit ? 1 : 0;
    }
}

In this case, ICpuInfo is an interface that was defined in the C++ product code that is present in the interop assembly. To create a mock, all you have to do is subclass from this interface and implement the methods for that interface, which in this case is the method Is64Bit. In the product there is a “real” class that checks to see if the CPU is a 64-bit CPU. But when we’re testing the code with component/acceptance tests, we want some tests pretend to run on a 32-bit machine, and other tests pretend to run on a 64-bit machine.

When you’re testing a component that expects an ICpuInfo interface, simply create an instance of this class and pass it to the C++ method, which ties into the next section on dependency injection.

Isolating Components with Dependency Injection

It can be very hard to test an object that creates other objects because of the tight coupling between the object you want to test, and the objects it creates. Dependency injection is a technique that helps decouple dependencies. The idea is that the caller of a component, rather than the component, is responsible for creating dependent components. The caller creates the other components and then passes them to the component it wants to use. This may seem a little backwards, but it allows test code to provide mock instead of the real implementations of these required components, thus completely isolating a component during testing.

To make this more concrete, let’s say you have a component that can use regular expressions to validate the contents of a text box control. But this component, called RegExValidator, uses a shared component called RegEx to actually evaluate the regular expressions. RegExValidator handles reading text from a text box, calling the RegEx methods, and returning status (perhaps including a message). The signature might look something like this:

 HRESULT Init(IRegEx *pRegEx, LPCWSTR pattern);
HRESULT Validate(ITextControl *pText, LPBSTR pMessage);

Any consumer of RegExValidator is required to call Init before calling Validate. The caller of the Init method is injecting the dependency on IRegEx into the RegExValidator component.

In your test code, you would create a mock object that implements IRegEx, called MockRegEx. You test code would create an instance of MockRegEx and then pass this to RegExValidator’s Init method before calling Validate. You can now test RegExValidator isolated from any “real” component, which means your test methods have complete control over the environment in which your tests run. Isolating the components makes it much easier to write tests that pinpoint regressions to a single class, and hopefully a single method.

Using a Factory Registry to Inject Dependencies

In some of our code, we use a factory registry as a way components can obtain dependent components rather than using dependency injection. For example, the RegExValidator could ask the factory registry for the factory that, in turn, creates a new instance of RegEx. Test code can overwrite the registered factory for a specific type so that a component will receive a mock instance when it asks for a new instance.

This approach is particularly useful for acceptance tests, as apposed to unit tests. Acceptance tests are typically higher-lever tests that exercise parts of the entire application rather than individual components. As a result, the product code, such as RegExValidator described in the previous section, will be created as a result of running the application. Because the test won’t be creating such components directly, you need a way to inject the mock components at a deeper level.

As an example, our application has code that shows different results on 32- and 64-bit CPUs. But since this code that uses the process type isn’t accessible directly to the acceptance tests, our acceptance tests register a mock class factory with the registry, and this factory returns a mock CPU test:

 MockCPUCheckFactory cpuCheckFactory = new MockCPUCheckFactory();
m_page.Wizard.RegisterFactory(WizardComponents.CpuInfo, cpuCheckFactory);

Now when the code asks for information about the CPU type, it will get this mock component instead of the real component. This means the acceptance tests can validate both 32- and 64-bit results regardless of the actual CPU type.

Here is the definition of the class factory, which implements the IClassFactory interface:

 class MockCPUCheckFactory : WizardTestLibrary.IClassFactory
{
    public bool Is64Bit { get; set; }

    public int CreateInstance(IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject)
    {
        ppvObject = Marshal.GetIUnknownForObject(new MockCPUCheck(Is64Bit));
        return 0;
    }

    public int LockServer(bool fLock)
    {
        return 0;
    }
}

I’ll go into more detail in a future post. The main thing to notice here is the call to Marshal.GetIUnknownForObject. This method will return a native IUnknown interface from the C# mock object, which can then be passed back to the native code as an IntPtr, which represents a pointer.

How do you Make this Work?

Obviously, I’ve left out al lot of details in this post. There is some more groundwork I’ll need to lay, which I plan to do in future blog posts. My next blog post, for example, will cover how to define an interface in C++ that will appear in an interop assembly that can be consumed in .NET.