Software developer blog

Unit test framework for C++ in about a 100 lines

Those who know me well are also aware of the fact, that I'm a very strong proponent of test automation and test driven development. Unfortunately there are times in our lives when installing an existing framework is not an option. So when I've been challenged to write a test framework that is so simple one can memorize it, and flush it out of their brain anytime needed, I was up to the task. The entire framework is 113 lines of C++ code, and using it is just as simple as using google test. Of course it doesn't come with all the features, it's fragile and has a good number of limitations, but it's good enough if you have absolutely no other option.

Here is how to use it, along with a sample output:

#include "TestBase.hpp"
 
class TestGroup : public TestBase {
};
 
TEST_F(TestGroup,PassingTest)
{
	EXPECT_TRUE(true);
	EXPECT_TRUE(true);
}
 
TEST_F(TestGroup,FailingTest)
{
	EXPECT_TRUE(true);
	EXPECT_TRUE(false);
	EXPECT_TRUE(true);
}
 
TEST_F(TestGroup,SecondPassingTest)
{
	EXPECT_TRUE(true);
	EXPECT_TRUE(true);
}

Sample output

Sample output of a simple unit test framework



Production quality unit test frameworks tend to be complex, so I did have to make a good number of sacrifices. My primary aim was to make this one very-very short, and make sure that it works most of the time. I didn't care about making it cross platform, or making sure that nothing can go wrong with the global variables I used. (Normally I wouldn't even use a global variable at all!) I also supposed that I only have a singe file containing tests. So should you ever use this? No... not really. Unless this is your only option, and you really-really want to use a test framework.

For me the most important features of a unit test framework are:

  • It should give a clear indication of success. After running the test suite the last line should unambiguously indicate success or failure with one single word.
  • Success or failure should also be indicated by color. (That way I don't even have to read and my peripheral vision is enough to check results.)
  • It should provide a way to check truth of certain statements without stopping the execution of the current test function.
  • Tests should be automatically registered into the test suite, so that there is no unnecessary duplication of code.

The trick is how one defines the TEST_F macro. This is the heart of the system. So let's begin with that!

#define TEST_F(TESTBASENAME,TESTNAME) \
	class TESTBASENAME##_##TESTNAME : public TESTBASENAME \
	{ \
		public: \
			virtual void run(); \
	}; \
	TESTBASENAME##_##TESTNAME TESTBASENAME##_##TESTNAME##_instance; \
	void TESTBASENAME##_##TESTNAME##::run()

Once you go through this, it becomes quite obvious how the whole framework works. For each test a new class is defined, and a global instance is created. The class redefines the virtual run function, and the macro ends with it's signature. Now it should be apparent how we made our tests look similar to function definitions.

The less obvious part is the usage of the global object. For one thing it's a bad practice to have global objects, but I already excused myself for that. The more mysterious part: why do we need it in the first place? The answer should leave you in horror: we rely on the order of initialization of globals and statics to register the tests into a list. Back from the shock? Yes... it's true that one should never rely on that, but in this case the globals are in one translation unit, which in theory warrants the proper initialization order, and we already gave up safety for compactness.

This is how the TestBase class looks like:

class TestBase {
	public:
		TestBase() : success(true) { 
			registerTestToList();
		}
		virtual ~TestBase() {}
 
		virtual void setUp() {}
		virtual void run() = 0;
		virtual void tearDown() {}
 
		bool isSuccessfull() { return success; } 
 
		static TestBase* begin() { return testBaseBegin; }
		TestBase* getNext() { return next; }
 
	protected:
		void expect_true(bool predicate, int line) {
			if(!predicate) {
				success = false;
				std::cout << "           Assert failed on line: "
					<< line << std::endl;
			}
		}
 
		bool success;
 
		TestBase* next;
		static TestBase* testBaseBegin;
		static TestBase* testBaseEnd;
 
	private:
		void registerTestToList() {
			TestBase* lastTestBase = testBaseEnd;
			if(testBaseEnd == NULL) {
				testBaseEnd = this;
				testBaseBegin = this;
			} else {
				testBaseEnd->next = this;
				testBaseEnd = this;
			}
			next = NULL;
		}
};
TestBase* TestBase::testBaseBegin = NULL;
TestBase* TestBase::testBaseEnd = NULL;

Most of it handles the list pointers. The rest are some empty virtual functions to define the test interface, and the functions and variables that make sure expect_true works. (The function is defined in this class, but normally one uses the macro version, so that line numbers need not be explicitly specified.) Not exactly a single responsibility class, but again: brevity trumps my principles this time.

All that remains is some way to traverse the tests, execute them, and generate colored output. These three tasks are performed by the TestRunner class:

class TestRunner {
	public:
		TestRunner() : allTestsGood(true) {
			hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
		}
		void callTest() {
			allTestsGood = true;
			int testCount = 0;
			int successfullTests = 0;
			for(TestBase* currentTest = TestBase::begin(); 
					currentTest != NULL; 
					currentTest = currentTest->getNext(), 
					++testCount) {
				indicateRun(typeid(*currentTest).name());
				currentTest->setUp();
				currentTest->run();
				currentTest->tearDown();
				indicateSuccess(currentTest->isSuccessfull());
				if(currentTest->isSuccessfull()) 
					++successfullTests;
			}
			std::cout << "[ ------ ] " << successfullTests 
				<< " out of " << testCount 
				<< " tests passed" << std::endl ;
			indicateSuccess(allTestsGood);
		}
 
	private:
		void indicateRun(std::string name) {
				SetConsoleTextAttribute(hConsole, 10);
				std::cout << "[ RUN    ] " ;
				SetConsoleTextAttribute(hConsole, 15);
				std::cout << name << std::endl;
		}
		void indicateSuccess( bool predicate ) {
			if( predicate ) {
				SetConsoleTextAttribute(hConsole, 10);
				std::cout << "[     OK ] " << std::endl;
			} else {
				SetConsoleTextAttribute(hConsole, 12);
				std::cout << "[ FAILED ] " << std::endl;
				allTestsGood = false;	
			}
			SetConsoleTextAttribute(hConsole, 15);
		}
 
		bool allTestsGood;
		HANDLE hConsole;
};

Actually this is the only class that is platform dependent. It should be easy to replace the windows specific text coloring statements, and use the framework on Linux.

Finally an exercise for you: can you make a better and shorter one? If you do, let me know, I'm interested!