8
Mar
0

C++ – Unit test – easy, one file, basic, simple

I few weeks ago I wanted to use unit test. But when I was searching for a framework easy to use, fast, that do not need to be installed 10 libs to make it working etc… Well I did not find anything that satisfy me.

So I make my own lib.

My lib is just a include file. It works every where, it is fast, it compiles in one line, you do not need any doc. But, of course, it is useful to make simple case test!! It do not replace real test framework. But it is a good option to make a test in a few minutes just to be sure every thing is okay.

The code

Here is the include you need (you just need this file).

#ifndef UTESTER_HPP
#define UTESTER_HPP
// /!\ Please, you must read the license at the bottom of this page

#include <iostream>
#include <list>
#include <string>
#include <stdio.h>

#define TestClass(X)\
int main(void){\
	X Controller;\
	Controller.Run();\
	return 0;\
}\

/**
* @author Berenger Bramas (berenger.bramas@inria.fr)
* Please read the license
*
* This class is used to make simple unit test cases
*
* Please refer to testUTest.cpp to see an example
* @warning Create a derived class that implement SetTests() and use TestClass() macro
*/
template <class TestClass>
class FUTester{
	// Test function pointer
	typedef void (TestClass::*TestFunc)(void);

	/** Test descriptor */
	struct TestFuncDescriptor{
		TestFunc func;		//< Test adress
		std::string name;	//< Test name
	};

	std::list<TestFuncDescriptor> tests;	//< all tests

	int totalTests;				//< number of tests

	int currentTest;			//< current processing test in the run
	int currentStep;			//< current processing step in the run

	int failledSteps;			//< number of failled step in the current test
	int failledTests;			//< number of failled tests

protected:
	/** Constructor */
	FUTester(){
		totalTests = 0;
	}

	/** Callback before processing test */
	virtual void Before(){}

	/** Callback after processing test */
	virtual void After(){}

	/** Callback before each unit test */
	virtual void PreTest(){}

	/** Callback after each unit test */
	virtual void PostTest(){}

	/**
	* This function has to add tests
        * <code> AddTest(&MyTest::TestOne); </code>
	*/
        virtual void SetTests() = 0;

	/**
	* Add a test without giving a name
	* @param inFunc test function address
	*/
        void AddTest(TestFunc inFunc){
		char buff[256];
		sprintf(buff,"Unamed Test number %d",totalTests+1);
		AddTest(inFunc,buff);
	}

	/**
	* Add a test with a name
	* @param inFunc test function address
	* @param inFuncName function name
	*/
        void AddTest(TestFunc inFunc, const std::string& inFuncName){
		++totalTests;
		TestFuncDescriptor desc;
		desc.func = inFunc;
                desc.name = inFuncName;
		tests.push_back(desc);
	}

	/**
	* To print a message manually in the test
	* @param value a object that ostream can work on
	*/
        template <class Output>
	void Print(const Output& value){
		std::cout<< "--- Output from program : " << value << "\n";
	}

	/**
	* To test
	* @param result the test result
	* if result is false test failled
	*/
        void assert(const bool result){
		++currentStep;
		if(!result){
			std::cout << ">> Step " << currentStep << " Failled\n";
			++failledSteps;
		}
	}

	/**
	* To test equality
	* @param v1 value one
	* @param v2 value 2
	* if v1 is not equal v2 test failled
	*/
	template <class T>
        void equal(const T& v1, const T& v2){
		assert(v1 == v2);
	}

	/**
	* To test equality
	* @param v1 value one
	* @param v2 value 2
	* if v1 is equal v2 test failled
	*/
	template <class T>
        void different(const T& v1, const T& v2){
		assert(v1 != v2);
	}

public :
	/**
	* Processing the test
	*/
	void Run(){
		tests.clear();
		// register tests
		SetTests();

		TestClass* const toTest = static_cast<TestClass*>(this);
		currentTest = 0;
		failledTests = 0;

		Before();

		// for each tests
                const typename std::list<TestFuncDescriptor>::const_iterator end = tests.end();
                for(typename std::list<TestFuncDescriptor>::iterator iter = tests.begin() ; iter != end ; ++iter){
			currentStep = 0;
			failledSteps = 0;

			std::cout << "[Start] " << (*iter).name << "\n";

			PreTest();
			TestFunc ff = (*iter).func;
			(toTest->*ff)();
			PostTest();

			if(failledSteps){
				std::cout << "[Finished] FAILLED (" << failledSteps << "/" << currentStep<< " steps failled)\n";
				++failledTests;
			}
                        else{
				std::cout << "[Finished] PASSED (" << currentStep << " steps)\n";
			}

			++currentTest;
		}

		After();

		std::cout <<"Test is over, " << (totalTests-failledTests) << " Passed, " << failledTests << " Failled\n";
	}

};

#endif

To make the things working you have to :
- inherit from the test class (with your class as template)
- implement SetTests
- add the macro TestClass(your class) at the end of your file

An example

#include "FUTester.hpp"

// compile by g++ utestTest.cpp -o utestTest.exe

/** this class show a simple example of unit test */
class MyTest : public FUTester<MyTest> {
	void Before(){
		Print("Before running the test");
	}

	void TestOne(){
		assert(true);
		assert(false);
		assert(1 == 1);
	}

	void TestTwo(){
		equal(1 , 1);
		different(1 , 1);
	}

	void After(){
		Print("After running the test");
	}

	void PreTest(){
		Print("Before each test");
	}

	void PostTest(){
		Print("After each test");
	}

	// You must implement it
	void SetTests(){
            AddTest(&MyTest::TestOne);
            AddTest(&MyTest::TestTwo,"My Second Test");
	}
};

// You must do this
TestClass(MyTest)

Output :

./utestTest.exe
--- Output from program : Before running the test
[Start] Unamed Test number 1
--- Output from program : Before each test
>> Step 2 Failled
--- Output from program : After each test
[Finished] FAILLED (1/3 steps failled)
[Start] My Second Test
--- Output from program : Before each test
>> Step 2 Failled
--- Output from program : After each test
[Finished] FAILLED (1/2 steps failled)
--- Output from program : After running the test
Test is over, 0 Passed, 2 Failled

Another Example

#include "FUTester.hpp"

#include "../Sources/Containers/FList.hpp"

// compile by g++ utestList.cpp -o utestList.exe

/**
* This file is a unit test for the FList class
*/

/**
* This class is simply used to count alloc dealloc
*/
class TestObject{
public:
	static int counter;
	static int dealloced;

	TestObject(){
		++counter;
	}
	TestObject(const TestObject&){
		++counter;
	}
	~TestObject(){
		++dealloced;
	}
};

int TestObject::counter(0);
int TestObject::dealloced(0);

/** this class test the list container */
class TestList : public FUTester<TestList> {
	// Called before each test : simply set counter to 0
	void PreTest(){
		TestObject::counter = 0;
		TestObject::dealloced = 0;
	}

	// test size
	void TestSize(){
		FList<TestObject> list;
		list.pushFront(TestObject());
		list.pushFront(TestObject());
		list.pushFront(TestObject());
		assert(list.getSize() == 3);

		assert((TestObject::counter - TestObject::dealloced) == list.getSize());

		list.clear();
		assert(list.getSize() == 0);

		assert(TestObject::counter == TestObject::dealloced);
	}

	// test copy
	void TestCopy(){
		FList<TestObject> list;
		list.pushFront(TestObject());
		list.pushFront(TestObject());
		list.pushFront(TestObject());

		FList<TestObject> list2 = list;
		assert(list.getSize() == list2.getSize());

		assert((TestObject::counter - TestObject::dealloced) == (list.getSize() + list2.getSize()));
	}

	// test iter
	void TestIter(){
		FList<TestObject> list;
		{
			FList<TestObject>::BasicIterator iter(list);
			assert(!iter.isValide());
		}
		{
			list.pushFront(TestObject());
			list.pushFront(TestObject());
			list.pushFront(TestObject());

			FList<TestObject>::BasicIterator iter(list);
			assert(iter.isValide());

			int counter = 0;
			while(iter.isValide()){ iter.progress(); ++counter; }
			assert(!iter.isValide());
			assert(counter == list.getSize());
		}
	}

	// set test
	void SetTests(){
            AddTest(&TestList::TestSize,"Test Size");
            AddTest(&TestList::TestCopy,"Test Copy");
            AddTest(&TestList::TestIter,"Test Iter");
	}
};

// You must do this
TestClass(TestList)

Output :

./utestList.exe
[Start] Test Size
[Finished] PASSED (4 steps)
[Start] Test Copy
[Finished] PASSED (2 steps)
[Start] Test Iter
[Finished] PASSED (4 steps)
Test is over, 3 Passed, 0 Failled

Licence

It is under LGPL.

Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.

Comments are closed.

Celadon theme by the Themes Boutique