Unit testing framework in C++: fixtures
I have two tests.
TEST(should_hurt_monster_if_cell_hurts)
{
Game game;
game.level.map = Map(2, 2);
Item armor = Item::Builder().sprite(1).wearable().defence(3).name("item");
game.level.monsters.push_back(Monster::Builder().pos(Point(1, 1)).hp(100).name("dummy").item(armor));
game.level.map.celltypes[0].hurts = true;
game.process_environment(game.level.monsters.front());
EQUAL(game.level.monsters.front().hp, 99);
EQUAL(game.messages, MakeVector("It hurts!")("Dummy loses 1 hp.").result);
}
TEST(should_hurt_monster_is_poisoned)
{
Game game;
game.level.map = Map(2, 2);
Item armor = Item::Builder().sprite(1).wearable().defence(3).name("item");
game.level.monsters.push_back(Monster::Builder().pos(Point(1, 1)).hp(100).name("dummy").item(armor));
game.level.monsters.front().poisoning = 10;
game.process_environment(game.level.monsters.front());
EQUAL(game.level.monsters.front().hp, 99);
EQUAL(game.messages, MakeVector("Dummy is poisoned.")("Dummy loses 1 hp.").result);
}
DRY principle wouldn’t let me use this. There are same code snippets used in both tests, in which game objects prepares. Practically it is setup step for the test. So I want to have prepared game object before I even enter test function, and possibly same object for both of test functions. So I declare a test fixture:
struct GameWithDummy {
Game game;
GameWithDummy() {
game.level.map = Map(2, 2);
Item armor = Item::Builder().sprite(1).wearable().defence(3).name("item");
game.level.monsters.push_back(Monster::Builder().pos(Point(1, 1)).hp(100).name("dummy").item(armor));
}
};
Now I want to use it in test. Sure, it could be passed as a variable like ‘fixture’, but in this case ‘fixture.’ prefix would be needed for addressing each field. Which is in turn a violation of DRY principle. Yet, I could left my test as they are (without any prefixes), if each test function were fixture class’ function. I inherit new class, Fixture_GameWithDummy, define run() method, and in Test::run() function I just create fixture object and call its run() method, and all testing will be contained in fixture::run().
#define TEST_FIXTURE(fixture_name, test_name) \
class Fixture_##fixture_name##test_name : public fixture_name { \
public: \
void run(); \
}; \
class Test_##test_name : public Test { \
public: \
Test_##test_name(const char * suite, const char * name) : Test(suite, name) {} \
virtual void run(); \
}; \
Test_##test_name test_##test_name(current_suite_name(), #test_name); \
void Test_##test_name::run() \
{ \
Fixture_##fixture_name##test_name fixture; \
fixture.run(); \
} \
void Fixture_##fixture_name##test_name::run()
This step requires all tests to be converted from simple void(*)()
function to a class, which really isn’t complicate anything:
struct Test {
const char * suite;
const char * name;
Test(const char * test_suite, const char * test_name);
virtual ~Test() {}
virtual void run() = 0;
bool specified(int argc, char ** argv) const;
};
#define TEST(test_name) \
class Test_##test_name : public Test { \
public: \
Test_##test_name(const char * suite, const char * name) : Test(suite, name) {} \
virtual void run(); \
}; \
Test_##test_name test_##test_name(current_suite_name(), #test_name); \
void Test_##test_name::run()
Now test runner just need to treat each test object as a class instance, not function, and call it’s run() method instead of impl().
And original two tests are simplified now:
TEST_FIXTURE(GameWithDummy, should_hurt_monster_if_cell_hurts)
{
game.level.map.celltypes[0].hurts = true;
game.process_environment(game.level.monsters.front());
EQUAL(game.level.monsters.front().hp, 99);
EQUAL(game.messages, MakeVector("It hurts!")("Dummy loses 1 hp.").result);
}
TEST_FIXTURE(GameWithDummy, should_hurt_monster_is_poisoned)
{
game.level.monsters.front().poisoning = 10;
game.process_environment(game.level.monsters.front());
EQUAL(game.level.monsters.front().hp, 99);
EQUAL(game.messages, MakeVector("Dummy is poisoned.")("Dummy loses 1 hp.").result);
}