Krzysztof Smogór

Published
February 7, 2024

Simplify your testing with Mixins in C++

C++
Mixins
Testing

Testing a big codebase can drive any developer insane. Tests require dependencies created in-place, just to satisfy the current scenario and forget about them. In the opinion of many developers, these suites are troublesome to read. Recently, I got a task to prepare a method for shipping custom allocators to our tests. After analysis, I found a lot of independent resources mixed in with single tests, which gave me a headache. I intended to straighten it up. Finally, the result helped Oxla tidy up tests, and it is also a gadget that any handyman should know.

The original problem

Let’s assume we have multiple suites of tests in the GTest framework. Every suite requires some resources to run correctly.

We are searching for a solution which:

  • Expose distinct, independent resources that can be used in tests.
  • Let us combine those distinct parts into one bigger fixture.
  • This one bigger fixture can be slightly modified for some special tests in the suite (e.g., override some function or inherit from it further).

With those ideas in mind, we can move to inspecting a problem.

Example messy implementation

As an example, we can write three tests:

  1. Write data to a big block of memory and assert if the data is still there.
  2. Read data from a file and assert its content.
  3. Read data from a file, write it to a block of memory, and assert its content.

Every test has its fixture. This fixture is responsible for preparing and cleaning resources used within test suites. Using a just-in-time approach would produce three different fixtures. If something is replicated - I don’t care!

Let’s introduce two example fixtures required by test no. 1 and 2. They can be useful in some unit tests.

class MemoryTest : public testing::Test
{
protected:
  static constexpr size_t k_block_size = 4096;
  char* mem_block;

  MemoryTest() : mem_block(nullptr) {}

  virtual void SetUp() override {
    mem_block = new char[k_block_size];
    ASSERT_TRUE(mem_block);
  }
  virtual void TearDown() override { if (mem_block) delete [] mem_block; }
    
  void write_offset(const std::string& data, size_t size, size_t offset = 0) { /*...*/ }
  void read_offset(std::string& output, size_t size, size_t offset = 0) { /*...*/ }
};  
class FileTest : public testing::Test
{
protected:
  std::ifstream file_stream;
    
  FileTest() : file_stream("../res/test.txt") {}
  virtual ~FileTest() { file_stream.close(); }

  virtual void SetUp() override { ASSERT_TRUE(file_stream.is_open()); }
};

Then, it’s time to implement some higher-level test suite that requires both resources at the same time. It would like something like that.

class BothTest : public testing::Test
{
protected:
  static constexpr size_t k_block_size = 4096;
  char* mem_block;
  std::ifstream file_stream;

  BothTest() : mem_block(nullptr), file_stream("../res/test.txt")  {}
  virtual ~BothTest() { file_stream.close(); }

  virtual void SetUp() override {
    ASSERT_TRUE(file_stream.is_open());
    mem_block = new char[k_block_size];
    ASSERT_TRUE(mem_block);
  }
  virtual void TearDown() override { if (mem_block) delete [] mem_block; }
    
  void write_offset(const std::string& data, size_t size, size_t offset = 0) { /*...*/ }
  void read_offset(std::string& output, size_t size, size_t offset = 0) { /*...*/ }
};

What a mess!

As you can see BothTest has everything copied from earlier fixtures. I don’t like it. This is awful.

We can clearly see that code creates blocks taken from different fixtures. Line 13 is from FileTest, and lines 14-15 are from MemoryTest. Let’s try combining those two fixtures at one. I would like to see a solution which decorates classes at compile time. Multiple inheritance is a good point to start.

Multiple inheritance?

This mechanism of C++ language is rather infamous among users, but let’s give it a shot.

First problem – multiple instances of testing::Test

Definition class BothTest : public MemoryTest, public FileTest would produce a class containing two instances of testing::Test class. This code wouldn’t compile because of ambiguous references to testing::Test members.

C++ has a mechanism for handling such situations – a virtual base class. This class lets the most derived object have only one instance of virtual derived classes. Because of this, we have to change the initialization of such virtually inherited objects, which can have unforeseen consequences on further inheritance. Unfortunately, this design introduces a bad code design – an inheritance diamond.

Second problem – which virtual function should be called?

Let’s update the code, test the code, and update the naming. Now, this distinct part will be called a Partial Fixture.

class FileFixture : virtual public testing::Test
{ /*...*/ } // same as before

class MemoryFixture : virtual public testing::Test
{ /*...*/ } // same as before

To use those partial fixtures, create a new fixture class and inherit from selected ones.

class FileTest : public FileFixture {}; // with single partial fixture it is optional

class BothTest : public MemoryFixture, public FileFixture
{
  virtual void SetUp() override {
    MemoryFixture::SetUp();
    FileFixture::SetUp();
  }
  virtual void TearDown() override {
    FileFixture::TearDown();
    MemoryFixture::TearDown();
  }
};

As you can see, this time, everything compiles, but there remains one single problem – virtual functions ambiguity. Google Test has to call a single virtual function for SetUp() and TearDown(), but currently, every partial fixture implements it. We must resolve this issue by defining another SetUp() and TearDown() combine fixtures with calls to base ones.

Once again, I don’t like it. It is error-prone and can lead to omitting SetUp() and TearDown() calls. And even worse – a call order can be important.

OK, let’s head to the drawing board. Our design almost works, but multiple inheritance makes everything harder. Maybe we can mash up classes without depending on each other? We just want to call SetUp() on every partial fixture at once and access all their members. There is such mechanism, but not in C++. It’s called Mix-In. Let’s visit other languages for now.

What is Mix-In?

There are multiple definitions of Mix-In. In the case of this post, I would refer to concatenating code from one block of code to another. This idea can be useful for our problem because we want to call code from multiple classes simultaneously. This technique is used in Java language. There is a Mixin library in Java that can add new code to existing functions. It will be run before or after the original code. It is used extensively by the Minecraft modifications community. This process can be described as decoration.

In C++, an inheritance line can also be seen as decoration. Constructors and destructors decorate classes in different order during creation and destruction. We can do even more. Let’s define a virtual function in A, we can decide how to order it by recursive calls at different places of function body.

There is one more thing. We try to find a solution for combining already defined classes. How does this observation help to solve the original problem? The final part of the puzzle is using templates and CRTP pattern. It lets us inject any base class into the existing definition.

Let’s use Mix-Ins

In this new approach, partial fixtures have to be template classes implementing the CRTP pattern. Additionally, all virtual functions have to call the base implementation recursively.

template <typename Base>
class FileFixture : public Base
{
  using base_t = Base;
  //...
  virtual void SetUp() override  {
    base_t::SetUp(); // Remember to call SetUp recursively.
    ASSERT_TRUE(file_stream.is_open());
  }
  virtual void TearDown() override {
    base_t::TearDown(); // Remember to call TearDown recursively.
  }
};

To use those partial fixtures, we have to define a unique fixture class that inherits from such partial fixtures. Right now, it is possible to combine any partial fixture. At the end of the inheritance line, there must be a single testing::Test class.

class FileTest : public FileFixture<testing::Test>{};
class BothTest : public MemoryFixture<FileFixture<testing::Test>>{};

Finally, we have a solution that satisfies our three assumptions from the beginning of the post. I don’t know what you think about it, but for me, it is still unclear how to use it. Let’s address this problem.

Final polish – easier wrapping of partial fixtures

To make it easier to use, let’s make a recursive template type definition that will handle all parenthesis and always add testing::Test. Using fold expressions and simple recursion template definition, we can make our lives easier. Using MixinFixture<A,B,...,C> as the parent class for the fixture will create a proper line of inheritance using classes A,B,...,C..

template<template <typename> class T1, template <typename> class ...Ts>
struct MixinFixtureInner { using type = T1<typename MixinFixtureInner<Ts ...>::type>; };

template<template <typename> class T>
struct MixinFixtureInner<T> { using type = T<testing::Test>; };

template<template <typename> class ...Ts>
using MixinFixture = typename MixinFixtureInner<Ts...>::type;

// for e.g.
class BothTest : public MixinFixture<FileFixture, MemoryFixture> {};

When I wrote this implementation, I was finally satisfied. As a result, we have some handy gadgets to use within multiple C++ projects. If you are interested in implementation details, I encourage you to look at the code repository with the working example described in this blog post. And if the above sounds interesting, we invite you to look closely at what we're crafting at Oxla. Our database is now production-ready, and you can try it for free by following a tutorial from our documentation. If you're interested in other deployment options, have questions, or simply want to say hi – feel free to contact our team via hello@oxla.com.

Sources

  1. Mixin – general description of Mix-In idea
  2. Derived classes - cppreference.com – virtual base class
  3. Virtual inheritance – further inheritance at the virtual base class hierarchy
  4. https://en.cppreference.com/w/cpp/language/crtp – general description of CRTP pattern
  5. https://github.com/google/googletest/blob/main/docs/faq.md#should-i-use-the-constructordestructor-of-the-test-fixture-or-setupteardown-ctorvssetup – guideline for the use of constructor/destructor and SetUp/TearDown functions
  6. GitHub - ksmogor/gtest-mixin: Fixture mixing POC with GTest – repository with the example described in this blog post
  7. GitHub - SpongePowered/Mixin: Mixin is a trait/mixin and bytecode weaving framework for Java using ASM – Mixin library in Java
  8. Mixins on Minecraft Forge – introduction for the Minecraft Forge developers using the Mixin library