Skip to main content

The header is the interface

The header is the interface

In this post I discuss a light weight pattern for introducing stubbing/mocking/faking and test-ability into C++ programs. The title is stated in contrast to the typically used dependency inversion pattern where a separate class interface is declared and the implementation of that class interface defines the implementation of that interface, a stub/mock/fake implementation of this interface is frequently dependency injected when implementing tests. However when using this pattern, by contrast, virtually the same outcome is achieved by replacing the implementation when compiling the tests. This is possible with C++ code due to the header file already defining the interface to a translation unit separately to the implementation.

To demonstrate this pattern I am first off presenting it in its most direct form. To start off we have a header file which defines the system architecture and should be included into the code base virtually everywhere. In a large project this should be added to a pre-compiled header or using a command line flag to reduce repetition. This header firstly declares that some namespaces contents are the contents of similarly named namespace suffixed with _impl. Secondly it defines a couple of macro's which will be used to open these namespaces where they are used in the system.

---- Architecture.h ----

#ifndef Architecture_h
#define Architecture_h

#define ARCH_IMPL_SUFFIX             _impl

// These two macros just join 2 tokens together.
#ifndef JOIN_NAMES
#define JOIN_NAMES(tokenA, tokenB)                 tokenA ## tokenB
#endif

#ifndef JOIN_NAMES_2
#define JOIN_NAMES_2(tokenA, tokenB)               JOIN_NAMES(tokenA, tokenB)
#endif

// This will be defined only when compiling tests.
#ifndef ARCH_COMPILING_TESTS

// Add the implementation to the actual namespace.
#define ADD_IMPLEMENTATION(layer)                                \
    namespace JOIN_NAMES_2(layer, ARCH_IMPL_SUFFIX) { }          \
    namespace layer {                                            \
        using namespace JOIN_NAMES_2(layer, ARCH_IMPL_SUFFIX) ;  \
    }

// TODO: Configure your layers here. You may also want to
//       comment on what kinds of code should inhabit a
//       particular layer.

// Code in the gateway namespace should be where the serialization
// and de-serialization to storage happens.
ADD_IMPLEMENTATION(Gateway);

// Code in the Entity namespace should be the most abstract
// and self contained objects of the system.
ADD_IMPLEMENTATION(Entity);

#undef ADD_IMPLEMENTATION

#endif // ARCH_COMPILING_TESTS

// This macro will be used to open the namespaces
// listed with ADD_IMPLEMENTATION above.
#define ARCH_NAMESPACE(layer)               namespace JOIN_NAMES_2(layer, ARCH_IMPL_SUFFIX)

#endif // Architecture_h

Now we will implement some classes which work together to store a type. This kind of implementation can be quite problematic to test in practise as the entity code is depending directly on static methods of the serialisation code. Usually to test storing Type we will need to spin up a test database so TypeQueries can execute actual SQL statements due to the tight coupling.

---- TypeQueries.h ----

#ifndef TypeQueries_h
#define TypeQueries_h

#include "Architecture.h"

#include "Type.h"

ARCH_NAMESPACE(Gateway) {
    class TypeQueries {
    public:
        // Static code (and constructor calls) are usually
        // a hazard to test-ability. But not with this pattern!
        static void storeType(Type type);
    };
}

#endif


---- TypeQueries.cpp ----


#include "TypeQueries.h"

ARCH_NAMESPACE(Gateway) {
    void TypeQueries::storeType(Entity_impl::Type type) {
        // Heavy SQL stuff in here.
    }
}

---- Type.h ----

#ifndef Type_h
#define Type_h

#include "Architecture.h"

ARCH_NAMESPACE(Entity) {
    class Type {
    public:
        void save();
        void markDirty();

    private:
        bool hasUnsavedChanges = false;
    };
}

#endif


---- Type.cpp ----

#include "Type.h"

#include "TypeQueries.h"

ARCH_NAMESPACE(Entity) {
    void Type::save() {
        if (!hasUnsavedChanges) { return; }

        Gateway::TypeQueries::storeType(*this);
        hasUnsavedChanges = false;
    }

    void Type::markDirty() {
        hasUnsavedChanges = true;
    }
}

Now for the purposes of this example imagine we want to test just the Type side of the implementation. The namespace trick introduced into the Architecture.h header file allows this to be done by compiling Type.cpp against a different implementation of TypeQueries. This can look a little like the following test code (using the google test framework).

---- MockArchitecture.h ----

#include "Architecture.h"

#define ARCH_TEST_SUFFIX               _test

#ifdef ARCH_NAMESPACE
#undef ARCH_NAMESPACE
#endif

// This gets re-defined for test code.
#define ARCH_NAMESPACE(layer)          namespace JOIN_NAMES_2(layer, ARCH_TEST_SUFFIX)

#define TEST_MOCK_NAMESPACE(layer)     \
    namespace layer {                  \
        namespace

// These macros are used for mocking/faking/stubbing declarations
// in tests.
#define MOCK_NAMESPACE(layer)          TEST_MOCK_NAMESPACE(layer)
#define END_MOCK_NAMESPACE             }

---- TypeTest.cpp ----

#include <gtest gtest.h>

namespace {
    int numberOfStoreTypeCalls;
}

#include "MockArchitecture.h"

// Forward declaration of type.
ARCH_NAMESPACE(Entity) {
    class Type;
}

MOCK_NAMESPACE(Gateway) {
    class TypeQueries {
    public:
        static void storeType(Entity_test::Type type) {
            // This is a very weak way of finding out
            // if storeType was called.
            numberOfStoreTypeCalls++;
        }
    }
} END_MOCK_NAMESPACE

// The implementation of the code under test is included
// here so it is re-compiled against the types mocked
// above.
#include "Type.cpp"

// During testing the implementation included is found in
// the namespace suffixed with _test so Entity_test in this
// example. To test the real version just include the header
// and call up the one in Entity_impl in the tests, and don't
// include MockArchitecture.h in this case.

TEST(Type, will_call_store_when_dirty) {
    numberOfStoreTypeCalls = 0;

    Entity_test::Type type;
    type.makeDirty();
    type.save();

    EXPECT_EQ(1, numberOfStoreTypeCalls);
}

TEST(Type, will_not_call_store_when_clean) {
    numberOfStoreTypeCalls = 0;

    Entity_test::Type type;
    type.save();

    EXPECT_EQ(1, numberOfStoreTypeCalls);
}

Summary

This pattern has some significant advantages over the run-time poly-morphism version where an abstract interface is defined and implemented at run-time.
  • There is no need to repeat the class interface again in interface form.
  • There is no run-time overhead added by the test code.
  • The most appropriate interface to the mocked class can be used, including constructors, free functions, static function calls.
  • Where the class interface being mocked has other functionality not associated with the test then this does not need to be implemented for the mock/stub/fake test version.
  • The dependencies of the implementation don't need to be exposed by the interface of the class under test.
  • Almost any C++ language constructs can be mocked/stubbed/faked for testing purposes with the only necessary implementation change needed to facilitate this being wrapping them in a namespace. This makes it radically easier to add tests to code which doesn't already facilitate testing.

This pattern is very light weight and broadly applicable.  

Comments

Popular posts from this blog

Dependency inversion does not simplify the design

Dependency inversion does not simplify the design After coming up with the code pattern (for adding compile time mocks/stubs/fakes) described in my previous blog post I started to think about system architecture more broadly. One thing which becomes readily apparent is this dependency inversion stuff can only be to facilitate testing. Once it becomes possible to implement the same testing without writing an interface (and using run-time poly-morph-ism) it becomes obvious that the implementation without the interface is the simpler of the designs. Figure 1 To make this concrete take this simple software design where some Behaviour type requires access to a Storage type. Figure 1 shows this in UML notation. The behaviour type probably implements some business rule and the storage implements the sql query code. The dashed line represents a significant component boundary like a package boundary which is considered significant. Figure 2 Figure 2 shows a typical implementat...

RAII + Move semantics enhancing Encapsulation

This introduces a little pattern I discovered the other day, to enhance encapsulation around safe resource access. Maybe we have some resource which we want to apply Resource Acquisition is Initialization (RAII) to. For any readers not familiar with RAII that may be something like an open file, or a mutex or some other resource for which we care about it being released by the program once the program has finished accessing it. It order to manage this in C++ we typically create a class type which acquires the resource in its constructor, allows access to the acquired resource via public methods and releases the resource in the destructor. The C++ language guarantees that the destructor of such an object will be called when its enclosing scope terminates. class RAIIType { RAIIType(int param1, int param2, const char* param3) : resource(nullptr) { // Call external library function to access resource. resource = acquireTheResource(param1, param2, param3); } ...