1

I guess it'll be easiest if I give an example of what I'm trying to achieve.

Let's say I'd like to implement a unit testing environment, in which implementing a new unit test would involve deriving from a given base class and (possibly) following guidlines involving putting additional macros. Such new test would then be automatically added to list of tests, ran one after another at some point. Two things however:

  1. I'm trying to make creating each new test as quick and easy as possible, especially when it comes to modifying files other than the files with the test itself. A perfect situation would be such, that implementing a new test wouldn't require touching any other files in the project. This is achievable with singletons and possibly CRTP, but now comes point number 2,

  2. The target is an MCU with limited amount of RAM (ROM in general is not a problem) and I'd like to be able to run the tests directly on the target platform. Because of this, static objects occupying memory throughout the entire application lifetime are not acceptable. Instead, I'd like to be able to create and delete each test separately only at the time it needs to be ran.

Basically, the problem comes down to a way of automatically registering derived types - or creator methods - to a factory with minimum RAM overhead (I'm assuming there will be some, i.e. at least pointers to said methods).

Sorry for no code samples, but there's really nothing to show here without already committing to one given implementation.

J_S
  • 2,985
  • 3
  • 15
  • 38

2 Answers2

1

Could you create a static/global vector of function pointers. These would be pointers to creator/factory functions for each test class. The factory functions return pointers to the base test class. I was going to try to write it out, but I think code is easier to write and understand.

class TestBase
{
  public:
    static char registerTest(<function ptr type> creator) {
      testCreators.push_back(creator);
      return 1;
   }

    static void runTests()
    {
      for (auto creator : testCreators)
      {
        auto newTestClass = creator();
        newTestClass->tests();
        delete newTestClass;
      }
    }

  private:
    void tests() = 0;
    std::vector<function ptr type> testCreators;
};

Then the derived class.

class SpecificTest : public TestBase
{
  // Pretend test code is here.

  private:
  static char dummy;
};

// Plan old C function. Need to establish naming conventions so as
// not to get multiple symbol errors during linking. Kind of fragile.
TestBase* specificTestCreator()
{
  return new SpecificTest();
}

In the .cpp file for SpecificTest

char SpecificTest::dummy = TestBase::registerTest(specificTestCreator);

I have tried to compile or run this, but I think it's fundamentally sound.

Michael Albers
  • 3,721
  • 3
  • 21
  • 32
1

I've created an example based on the answer provided by Michael, compiled and ran it. Posting code below.

TestBase.h:

#ifndef TESTBASE_H_
#define TESTBASE_H_

#include <vector>

class TestBase {
public:
    TestBase();
    virtual ~TestBase();
    static void RunAllTests();

protected:
    virtual void test() = 0;
    static char addTestCreator(TestBase* (*creator)());

private:
    static std::vector<TestBase* (*)()> &getTests();
};

#endif /* TESTBASE_H_ */

TestBase.cpp

#include "TestBase.h"

TestBase::TestBase() {
}

TestBase::~TestBase() {
}

char TestBase::addTestCreator(TestBase* (*creator)())
{
    getTests().push_back(creator);
    return 0;
}

void TestBase::RunAllTests()
{
    for(std::vector<TestBase* (*)()>::iterator it = getTests().begin(); it != getTests().end(); it++)
    {
        TestBase *t = (*it)();
        t->test();
        delete t;
    }
}

std::vector<TestBase* (*)()> &TestBase::getTests()
{
    static std::vector<TestBase* (*)()> v;
    return v;
}

ConcreteTest1.h:

#ifndef CONCRETETEST1_H_
#define CONCRETETEST1_H_

#include "TestBase.h"

class ConcreteTest1: public TestBase {
public:
    ConcreteTest1();
    virtual ~ConcreteTest1();

protected:
    void test();

private:
    // both here can be expanded with a macro to make it
    // easier as they'll be same for all derived classes
    static char dummy;
    static TestBase *creator();
};

#endif /* CONCRETETEST1_H_ */

ConcreteTest1.cpp:

#include "ConcreteTest1.h"
#include <iostream>

// can be expanded with a macro
char ConcreteTest1::dummy = TestBase::addTestCreator(ConcreteTest1::creator);
// can be expanded with a macro
TestBase* ConcreteTest1::creator()
{
    return new ConcreteTest1();
}

ConcreteTest1::ConcreteTest1()
{
    std::cout << "Creating test 1" << std::endl;
}

ConcreteTest1::~ConcreteTest1()
{
    std::cout << "Deleting test 1" << std::endl;
}

void ConcreteTest1::test()
{
    std::cout << "Running test 1" << std::endl;
}

Similarly ConcreteTest2.cpp/.h.

Invoked from main with:

TestBase::RunAllTests();

Output is:

Creating test 1
Running test 1
Deleting test 1
Creating test 2
Running test 2
Deleting test 2

which is exactly what I've wanted to achieve.

J_S
  • 2,985
  • 3
  • 15
  • 38
  • Jacek, one think to note. Static variables can get initialized in an unexpected order. Check out: http://stackoverflow.com/questions/211237/static-variables-initialisation-order. Theoretically your tests won't be affected by this, but it's something to keep in mind. – Michael Albers Sep 04 '15 at 03:47
  • Indeed, this is why I'm getting the vector of tests through a getter (to make sure it already exists when adding the first test). As for the order of tests - currently I don't mind having them in random order. Thanks for pointing that out though. – J_S Sep 04 '15 at 06:09