Scrunitizer offers a lot of different ways to create unit tests. Here you could see how unit test could be constructed and which approach is the best in which situation.

Please have a look at the example class to fully understand the following test drivers.

The Classical Approach

The way to use Scrunitizer which is most similar to what is done in JUnit (and other framework heavily inspired by JUnit) is to create tests by writing unit test classes.

In almost all unit test frameworks, tests are defined by specifying test case classes that implement the according test functionality. So first a test case that tests the equals function the classical way:



class ClassicEqualsTest : public Scrunitizer::TestCase {

	private:
		Rect* r1;
		Rect* r2;
		Rect* r3;

	public:
		ClassicEqualsTest () : TestCase ("Equals Test") { }

		void setUp () {
			r1 = new Rect (0, 0, 10, 10);
			r2 = new Rect (0, 0, 10, 10);
			r3 = new Rect (0, 0, 10, 20);
		}


		void test () {
			assert (r1->equals (*r2), "r1 == r2");
			assert (r1->equals (*r1), "r1 == r1");
			assert (r2->equals (*r1), "r1 == r2");
			assert (! r1->equals (*r3), "r1 != r3");
			assert (! r3->equals (*r1), "r1 != r3");
		}

		void tearDown () {
			delete r1;
			delete r2;
			delete r3;
		}

};
That's quite straightforward and perfectly valid.
  • First, some rectangle instances are created in the setUp function,
  • in the test function this rectangles are used for assertions and
  • finally they are destroyed in the tear down function.
But thatīs quite a lot of code, and - more important - rectangle pointers are used, so that using the rectangle functions (which use references as parameters) becomes a bit odd.

So why not use member variables that are initialised in the constructor. A test case that tests the covers function would look like this:


class DangerousCoversTest : public Scrunitizer::TestCase {

	private:
		Rect cover;
		Rect notCovered;
		Rect partlyCovered;
		Rect fullyCovered;

	public:
		DangerousCoversTest () : 
			TestCase ("Covers Test"),
			cover		(  0,   0, 100, 100),
			notCovered	(150, 150,  50,  50),
			partlyCovered	( 50,  50, 100, 100),
			fullyCovered	( 50,  50,  50,  50) {
		}

		void test () {
			assert (! cover.covers (notCovered), 
				"! (  0,   0, 100, 100) covers (150, 150, 200, 200)");
			assert (! cover.covers (partlyCovered),
				"! (  0,   0, 100, 100) covers ( 50,  50, 150, 150)");
			assert (  cover.covers (fullyCovered), 
				"(  0,   0, 100, 100) covers ( 50,  50, 100, 100)");
		}


};
Well - that's valid C++ code in typical coding C++ style. But it is not a good test case!

The reason why such a kind of test case is problematic is that a part of the domain functionality, the rectangle constructor, is executed when the test case is instantiated! But at that point in time the unit test framework is not running. In this simple example it does not matter, but in case of a complex constructor which does a lot of things, which could even throw exceptions or crash, this is a real problem. So stay away from this kind of test cases.

A better example below:



class ClassicCoversTest : public Scrunitizer::TestCase {

	public:
		ClassicCoversTest () : TestCase ("Covers Test") { }

		void test () {
			Rect cover		(  0,   0, 100, 100);
			Rect notCovered		(150, 150,  50,  50);
			Rect partlyCovered	( 50,  50, 100, 100);
			Rect fullyCovered	( 50,  50,  50,  50);

			assert (! cover.covers (notCovered), 
				"! (  0,   0, 100, 100) covers (150, 150, 200, 200)");
			assert (! cover.covers (partlyCovered),
				"! (  0,   0, 100, 100) covers ( 50,  50, 150, 150)");
			assert (  cover.covers (fullyCovered), 
				"(  0,   0, 100, 100) covers ( 50,  50, 100, 100)");
		}

};
Here the test code looks better. And constructing the rectangle instances in the test function is also OK because creating a rectangle is neither complex nor costly.

Assuming that both tests should be added to a test suite, the following code is needed in addition:



	Scrunitizer::TestSuite* classic = new Scrunitizer::TestSuite ("Classic Tests");
	classic->add (new ClassicEqualsTest ());
	classic->add (new ClassicCoversTest ());
If you look at the ClassicCoversTest class example, you might notice that apart from the test function all the rest is more or less additional burden that is only needed because the test functionality must be bound to test case classes. But thereīs another way to create test cases...

The Function Bases Approach

One way to build unit tests is to put the test functionality in a plain function. That's not very object oriented, but for unit tests that do not need a complicated fixture set-up (and fixture tear-down), thatīs quite OK.

It looks like that:



void EqualsTestFunction () {
	Rect r1 (0, 0, 10, 10);
	Rect r2 (0, 0, 10, 10);
	Rect r3 (0, 0, 10, 20);

	assert (r1.equals (r2), "r1 == r2");
	assert (r1.equals (r1), "r1 == r1");
	assert (r2.equals (r1), "r1 == r2");
	assert (! r1.equals (r3), "r1 != r3");
	assert (! r3.equals (r1), "r1 != r3");
}



void CoversTestFunction ()  {
	Rect cover		(  0,   0, 100, 100);
	Rect notCovered		(150, 150,  50,  50);
	Rect partlyCovered	( 50,  50, 100, 100);
	Rect fullyCovered	( 50,  50,  50,  50);


	assert (! cover.covers (notCovered), 
		"! (  0,   0, 100, 100) covers (150, 150, 200, 200)");
	assert (! cover.covers (partlyCovered),
		"! (  0,   0, 100, 100) covers ( 50,  50, 150, 150)");
	assert (  cover.covers (fullyCovered), 
		"(  0,   0, 100, 100) covers ( 50,  50, 100, 100)");
}
And how do I create tests from that functions? Well, by instantiating a TestCaseAdapter template with the functions for test, setUp and tearDown as parameters. Sounds complicated?. To ease the pain of manually calling template class constructors, some helper functions are defined. The makeTestCase function (a template function) takes the name of the test case and a function as parameter, constructs an according test case class and finally creates a test case instance. There are also versions that take an additional setUp and an setUp and tearDown function as parameters.

For the two test functions above, it goes like that:



	Scrunitizer::TestSuite* functionBased = new Scrunitizer::TestSuite ("Function based Tests");
	functionBased->add (makeTestCase ("Function based equals Test", EqualsTestFunction));
	functionBased->add (makeTestCase ("Function based covers Test", CoversTestFunction));
That's quite good because itīs short. Only one function and one line of code to create and register (add) a test case.


The All-in-one Approach

Now you might say: Yep, nice eaxmple, but in real life the tests must be more systematic! Thatīs quiet true. In our recangle class example, we must make sure that all the test functions work under all circumstances, meaning regardless of the relative position and size of the involved rectangles. It could easily happen that you type _x0 instead of _y0 by accident, and that only a limited amount of assertions (like in the previous test function) do not detect this mistake. So what is needed is an exhaustive set of test rectangles that are systematically tested agaist a refernce rectangle.

To detect all possible error cases, a center rectangle at (0,0) is tested against a rectangle in all nine possible geometirc relationships. The relative psoitions are identified by the according wind direction:
-1 0 +1
-1 NW N NE
0 W C E
+1 SW S SE
That could be all checked in a single test function:



void testIntersectionPred () {
	Rect centerRect (0, 0, 10, 10);

	Rect testRectNW   (-10, -10, 10, 10);
	Rect testRectN    (  0, -10, 10, 10);
	Rect testRectNE   ( 10, -10, 10, 10);

	Rect testRectW    (-10,   0, 10, 10);
	Rect testRectC    (  0,   0, 10, 10);
	Rect testRectE    ( 10,   0, 10, 10);

	Rect testRectSW   (-10,  10, 10, 10);
	Rect testRectS    (  0,  10, 10, 10);
	Rect testRectSE   ( 10,  10, 10, 10);

	assert (! centerRect.intersects (testRectNW),
		"Failure in NW intersection");
	assert (! centerRect.intersects (testRectN ),
		"Failure in N intersection");
	assert (! centerRect.intersects (testRectNE),
		"Failure in NE intersection");

	assert (! centerRect.intersects (testRectW),
		"Failure in W intersection");
	assert (  centerRect.intersects (testRectC ),
		"Failure in C intersection");
	assert (! centerRect.intersects (testRectE),
		"Failure in E intersection");

	assert (! centerRect.intersects (testRectSW),
		"Failure in SW intersection");
	assert (! centerRect.intersects (testRectS ),
		"Failure in S intersection");
	assert (! centerRect.intersects (testRectSE),
		"Failure in SE intersection");

}
Regsitering this function is quite simple:


	Scrunitizer::TestSuite* allInOneFunc = new Scrunitizer::TestSuite ("All in one functions");
	allInOneFunc->add (makeTestCase ("All in one intersection predicate test", testIntersectionPred));
The Problem with this test approach is that if the first assertion fails, all the remaining assertion are not tested anymore. That requires that the cause of the first failing assertion must be fixed before further problems could be detected. That might be acceptable in some cases, but there might be situation where you cure the symptoms before even the real disease is discovered. So in general, it is a good idea to keep the test as separate as possible.


The Exhaustive Approach

That could be addressed by splitting up the test function in nine smaller ones:


void testNWIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectNW (-10, -10, 10, 10);
	assert (! centerRect.intersects (testRectNW),
		"Failure in NW intersection");
}

void testNIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectN  (  0, -10, 10, 10);
	assert (! centerRect.intersects (testRectN ),
		"Failure in N intersection");
}

void testNEIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectNE ( 10, -10, 10, 10);
	assert (! centerRect.intersects (testRectNE),
		"Failure in NE intersection");
}

void testWIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectW  (-10,   0, 10, 10);
	assert (! centerRect.intersects (testRectW),
		"Failure in W intersection");
}

void testCIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectC  (  0,   0, 10, 10);
	assert (  centerRect.intersects (testRectC ),
		"Failure in C intersection");
}

void testEIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectE  ( 10,   0, 10, 10);
	assert (! centerRect.intersects (testRectE),
		"Failure in E intersection");
}

void testSWIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectSW (-10,  10, 10, 10);
	assert (! centerRect.intersects (testRectSW),
		"Failure in SW intersection");
}

void testSIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectS  (  0,  10, 10, 10);
	assert (! centerRect.intersects (testRectS ),
		"Failure in S intersection");
}

void testSEIntersection () {
	Rect centerRect (0, 0, 10, 10);
	Rect testRectSE ( 10,  10, 10, 10);
	assert (! centerRect.intersects (testRectSE),
		"Failure in SE intersection");
}
They could be combined to a test suite as shown below:


	Scrunitizer::TestSuite* exhFuncBased = new Scrunitizer::TestSuite ("Exhaustive function based");
	exhFuncBased->add (makeTestCase ("NW intersection test", testNWIntersection));
	exhFuncBased->add (makeTestCase ("N intersection test",  testNIntersection));
	exhFuncBased->add (makeTestCase ("NE intersection test", testNEIntersection));
	exhFuncBased->add (makeTestCase ("W intersection test",  testWIntersection));
	exhFuncBased->add (makeTestCase ("C intersection test",  testCIntersection));
	exhFuncBased->add (makeTestCase ("E intersection test",  testEIntersection));
	exhFuncBased->add (makeTestCase ("SW intersection test", testSWIntersection));
	exhFuncBased->add (makeTestCase ("S intersection test",  testSIntersection));
	exhFuncBased->add (makeTestCase ("SE intersection test", testSEIntersection));
The big disadvantage is still that 9 test functions have to be written only for the intersection tests.

The according examples for the coverage test are left out here.


The Parametrised Test Function Approach

But youīve might guessed it already: Thereīs also a solution for this problem: Parametrised test functions. It is possible to bind 1 to arguments of a function when it is called during execution of a test case. The convenience function binddoes the job. It automatically creates a function call adapter (a class template) that is used instead of the original function. To make use of that feature, a parametrised version of the test function is needed:


void testIntersects (int xc, int yc, bool expectedResult) {
	Rect centerRect (0, 0, 10, 10);
	Rect testRect   (xc * 10, yc * 10, 10, 10);
	assert (centerRect.intersects (testRect) == expectedResult,
		"Failure in intersection test");
}

void testCovers (int xc, int yc, bool expectedResult) {
	Rect centerRect (0, 0, 10, 10);
	Rect testRect   (xc * 10, yc * 10, 10, 10);
	assert (centerRect.covers (testRect) == expectedResult,
		"Failure in coverage test");
}
Now, creating the test cases looks like that:


	Scrunitizer::TestSuite* paramFuncBased = new Scrunitizer::TestSuite("Parameterised function based");
	paramFuncBased->add (makeTestCase ("NW intersection test", bind (testIntersects, -1, -1, false)));
	paramFuncBased->add (makeTestCase ("N intersection test",  bind (testIntersects,  0, -1, false)));
	paramFuncBased->add (makeTestCase ("NE intersection test", bind (testIntersects,  1, -1, false)));
	paramFuncBased->add (makeTestCase ("W intersection test",  bind (testIntersects, -1,  0, false)));
	paramFuncBased->add (makeTestCase ("C intersection test",  bind (testIntersects,  0,  0, true )));
	paramFuncBased->add (makeTestCase ("E intersection test",  bind (testIntersects,  1,  0, false)));
	paramFuncBased->add (makeTestCase ("SW intersection test", bind (testIntersects, -1,  1, false)));
	paramFuncBased->add (makeTestCase ("S intersection test",  bind (testIntersects,  0,  1, false)));
	paramFuncBased->add (makeTestCase ("SE intersection test", bind (testIntersects,  1,  1, false)));
	paramFuncBased->add (makeTestCase ("NW coverage test", bind (testCovers, -1, -1, false)));
	paramFuncBased->add (makeTestCase ("N coverage test",  bind (testCovers,  0, -1, false)));
	paramFuncBased->add (makeTestCase ("NE coverage test", bind (testCovers,  1, -1, false)));
	paramFuncBased->add (makeTestCase ("W coverage test",  bind (testCovers, -1,  0, false)));
	paramFuncBased->add (makeTestCase ("C coverage test",  bind (testCovers,  0,  0, true )));
	paramFuncBased->add (makeTestCase ("E coverage test",  bind (testCovers,  1,  0, false)));
	paramFuncBased->add (makeTestCase ("SW coverage test", bind (testCovers, -1,  1, false)));
	paramFuncBased->add (makeTestCase ("S coverage test",  bind (testCovers,  0,  1, false)));
	paramFuncBased->add (makeTestCase ("SE coverage test", bind (testCovers,  1,  1, false)));

The Object Based Approach

What is possible with plain functions could also be done with member functions and fixture objects. Sometimes it might be nice to share helper functions between several test functions or to save state information between, set-up, test execution and tear-down. That could be done by combining all this elements to a test fixture class. The previous example as a test fixture and handler class is shown below:


class RectangleTester {

	public:
		void testIntersects (int cx, int cy, bool expectedResult) {
			Rect centerRect (0, 0, 10, 10);
			Rect testRect (cx * 10, cy * 10, 10, 10);
			assert (centerRect.intersects (testRect) == expectedResult,
				"Failure in intersection test");
		}

		void testCovers (int cx, int cy, bool expectedResult) {
			Rect centerRect (0, 0, 10, 10);
			Rect testRect (cx * 10, cy * 10, 10, 10);
			assert (centerRect.covers (testRect) == expectedResult,
				"Failure in coverage test");
		}

};
Again, creating the test cases is done the following way:


	Scrunitizer::TestSuite* objBased = new Scrunitizer::TestSuite ("Parametrized object based Tests");
	objBased->add (makeTestGadget ("NW", apply (&RectangleTester::testIntersects, -1, -1, false)));
	objBased->add (makeTestGadget ("N",  apply (&RectangleTester::testIntersects,  0, -1, false)));
	objBased->add (makeTestGadget ("NE", apply (&RectangleTester::testIntersects,  1, -1, false)));
	objBased->add (makeTestGadget ("W",  apply (&RectangleTester::testIntersects, -1,  0, false)));
	objBased->add (makeTestGadget ("C",  apply (&RectangleTester::testIntersects,  0,  0, true )));
	objBased->add (makeTestGadget ("E",  apply (&RectangleTester::testIntersects,  1,  0, false)));
	objBased->add (makeTestGadget ("SW", apply (&RectangleTester::testIntersects, -1,  1, false)));
	objBased->add (makeTestGadget ("S",  apply (&RectangleTester::testIntersects,  0,  1, false)));
	objBased->add (makeTestGadget ("SE", apply (&RectangleTester::testIntersects,  1,  1, false)));
	objBased->add (makeTestGadget ("NW", apply (&RectangleTester::testCovers, -1, -1, false)));
	objBased->add (makeTestGadget ("N",  apply (&RectangleTester::testCovers,  0, -1, false)));
	objBased->add (makeTestGadget ("NE", apply (&RectangleTester::testCovers,  1, -1, false)));
	objBased->add (makeTestGadget ("W",  apply (&RectangleTester::testCovers, -1,  0, false)));
	objBased->add (makeTestGadget ("C",  apply (&RectangleTester::testCovers,  0,  0, true )));
	objBased->add (makeTestGadget ("E",  apply (&RectangleTester::testCovers,  1,  0, false)));
	objBased->add (makeTestGadget ("SW", apply (&RectangleTester::testCovers, -1,  1, false)));
	objBased->add (makeTestGadget ("S",  apply (&RectangleTester::testCovers,  0,  1, false)));
	objBased->add (makeTestGadget ("SE", apply (&RectangleTester::testCovers,  1,  1, false)));
	top->add (objBased);
Test case classes that are generated by applying member functions to test fixture class instances are called test gadgets.
Instead of bind, the convenience function apply is used to assign parameter values. One difference is that apply must also be used if no parameters are needed for a member function. The is necessary because Scrunitizer uses a special interface, Applicable, to apply member functions to objects.

The Fixture Class Based Approach

Well, this does not look like a big step forward. To make real use out of fixture object, their main advantage must be used; the ability to hold state and configuration information. A bit more advanced version of the previous test class is shown below:


class BasicRectTester {

	protected:
		virtual void setUpCenterRect (Rect& r) {
			r.reshape (0, 0, 10, 10); 
		}

		virtual void setUpTestRect (Rect& r, int cx, int cy) {
			r.reshape (cx * 10, cy * 10, 10, 10); 
		}

		virtual bool expectedIntersectsResult (int cx, int cy) {
			return cx == 0 && cy == 0;
		}

		virtual bool expectedCoversResult (int cx, int cy) {
			return cx == 0 && cy == 0;
		}


	public:
		void testIntersects (int cx, int cy) {
			Rect centerRect;
			Rect testRect;

			setUpCenterRect (centerRect);
			setUpTestRect (testRect, cx, cy);

			assert (centerRect.intersects (testRect) == expectedIntersectsResult (cx, cy),
				"Failure in intersection test");
		}

		void testCovers (int cx, int cy) {
			Rect centerRect (0, 0, 10, 10);
			Rect testRect;

			setUpCenterRect (centerRect);
			setUpTestRect (testRect, cx, cy);

			assert (centerRect.covers (testRect) == expectedCoversResult (cx, cy),
				"Failure in coverage test");
		}

	public:
		static Scrunitizer::TestSuite* makePredicateTests (
			const string& suiteName,
			const string& testNamePrefix,
			void (BasicRectTester::*testFunc)(int,int),
			BasicRectTester* fixture
		);
};


Scrunitizer::TestSuite* BasicRectTester::makePredicateTests (
	const string& suiteName,
	const string& testNamePrefix,
	void (BasicRectTester::*testFunc)(int,int),
	BasicRectTester* fixture
) {
	using Scrunitizer::apply;
	using Scrunitizer::makeTestGadget;

	Scrunitizer::TestSuite* suite = new Scrunitizer::TestSuite (suiteName);
	suite->add (makeTestGadget (testNamePrefix + "NW", fixture, apply (testFunc, -1, -1)));
	suite->add (makeTestGadget (testNamePrefix + "N",  fixture, apply (testFunc,  0, -1)));
	suite->add (makeTestGadget (testNamePrefix + "NE", fixture, apply (testFunc,  1, -1)));
	suite->add (makeTestGadget (testNamePrefix + "W",  fixture, apply (testFunc, -1,  0)));
	suite->add (makeTestGadget (testNamePrefix + "C",  fixture, apply (testFunc,  0,  0)));
	suite->add (makeTestGadget (testNamePrefix + "E",  fixture, apply (testFunc,  1,  0)));
	suite->add (makeTestGadget (testNamePrefix + "SW", fixture, apply (testFunc, -1,  1)));
	suite->add (makeTestGadget (testNamePrefix + "S",  fixture, apply (testFunc,  0,  1)));
	suite->add (makeTestGadget (testNamePrefix + "SE", fixture, apply (testFunc,  1,  1)));

	return suite;
}
The main difference is that the set-up of the central rectangle, the test rectangle and the expected result computation is moved to some virtual (!) helper functions. Also a static function was added to create a test suite.

Now it is quite easy to create completely different test suites with a few lines of code. I.e. if we want to test if the intersection and the coverage test function produce correct results when they must handle cases in which rectangles slightly overlap, this could be done in a systematic way like this: A new fixture class is derived that changes some configuration parameters (boundaries of the test rectangles) and by using such derived fixture objects when creating test suites.



class GrownRectTester : public BasicRectTester {

	protected:
		virtual void setUpTestRect (Rect& r, int cx, int cy) {
			r.reshape (cx * 10 - 2, cy * 10 - 2, 10 + 4, 10 + 4); 
		}

		virtual bool expectedIntersectsResult (int cx, int cy) {
			return true;
		}

		virtual bool expectedCoversResult (int cx, int cy) {
			return false;
		}


};
Setting up two different test suites now take only a few lines of code:


	BasicRectTester* basicRectFixture = new BasicRectTester ();
	GrownRectTester* grownRectFixture = new GrownRectTester ();
	manager.takeFixture (basicRectFixture);	
	manager.takeFixture (grownRectFixture);	
	top->add (BasicRectTester::makePredicateTests (
		  "Basic intersection test", "Basic Intersection ", 
		  &BasicRectTester::testIntersects, basicRectFixture));
	top->add (BasicRectTester::makePredicateTests (
		  "Grown rect intersection test", "Overlapping Rect Instersection ",
		  &BasicRectTester::testIntersects, grownRectFixture));
	top->add (BasicRectTester::makePredicateTests (
		  "Basic covrage test", "Basic Coverage ", 
		  &BasicRectTester::testCovers, basicRectFixture));
	top->add (BasicRectTester::makePredicateTests (
		  "Grown rect coverage test", "Overlapping Rect Coverage ",
		  &BasicRectTester::testCovers, grownRectFixture));
Now you can see the full power of fixture objects. With two fixture classes and some lines for generating the test suites 38 distinct (!) tests are generated that test the intersection and coverage test functions systematically under all constellations.

Please note that both fixture instances, basicRectFixture and grownRectFixture are handed over to the test manager by invoking takeFixture. After calling this function, the fixtures are owned by the test manager, so they are deleted when the manager is deleted. This prevents us from worrying about which test case that uses the fixture object actually owns the test case. This might be no problem if the number of (auto-generated) test cases is small; but if the set of test cases is huge or varies over time, it is better to delegate the deletion of a shared fixture object to the test manager.

The Scrunitizer C++ Unit Test Framework
by Bernd Linowski

[Scrunitizer]  [Overview]  [Cookbook]  [Download]  [Index]  [Linosphere]

Page generated: 1 Nov 2000
(C) by Bernd Linowski
scrunitizer@linosphere.de