Tester: Micro Unit-Testing Library

View Usage Example | License

#ifndef TESTER_H
#define TESTER_H

#include <iostream>
#include <vector>

/*! Tester: A Micro Unit Testing Library for C++

	See http://nathan.ca/code/tester for the original and more information.
 */

//! Tester encapsulate all this into a class for scoping convenience.
class Tester
{
public:
	//! Base class for all test cases.
	class ICase
	{
	protected:
		bool verbose;
		int passes, attempts;

		virtual void test() = 0; // defined by the USER

	public:
		ICase() : verbose(false), passes(0), attempts(0) {}
		virtual ~ICase() {}
			
		// mandatory overrides to return the name and to run the test case
		virtual const char *getName() const = 0;
		virtual bool run(bool setverbose) = 0;
			
		// getters
		int getAttempts() const { return attempts; }
		int getPassedAttempts() const { return passes; }
	};

private:
	// test cases to run (we are responsible for these)
	std::vector<ICase*> tests;

public:
	// add an ICase-derived test case (which we take responsibility for) and then run them all
	void addCase(ICase *testcase) { tests.push_back(testcase); }
	bool run(bool verbose)
	{
		int attempts = 0, passes = 0,
			total = 0, successes = 0;

		for(auto it = tests.begin(); it != tests.end(); it++)
		{
			ICase *testcase = *it;
			std::cout << "Running " << testcase->getName() << "\n\n";

			if (testcase->run(verbose))
				successes++;
			total++;

			std::cout << "Finished running " << testcase->getName() << " "
				<< testcase->getPassedAttempts() << " / " << testcase->getAttempts() << "\n";

			attempts += testcase->getAttempts();
			passes += testcase->getPassedAttempts();
		}

		//
		std::cout << "\nTOTAL: " << successes << " / " << total << "\n";
		
		// delete 'em all
		while(tests.size() > 0)
		{
			ICase *t = tests.back();
			tests.pop_back();

			delete t;
		}

		return successes == total;
	}
};

// defines the given class as an ICase derivitive and runs it
#define tester_addsuite(cl, name) \
	namespace tests { class cl : public Tester::ICase { public: \
		cl() : ICase() {} virtual ~cl() {} \
		const char *getName() const { return name; } \
		bool run(bool setverbose) { verbose = setverbose; test(); return passes == attempts; } \
		void test(); \
	}; }

// test whether the boolean `cond` succeeded on the pretense of `msg`.
#define tester_bool(cond, msg) do { \
	bool res = cond; /* keep this as a variable so that it only runs ONCE */ \
	std::cout << "\t[" << (res ? "PASS" : "FAIL") << "] " << msg; \
	if (verbose) \
		std::cout << ":\n\t\t" #cond << "\n"; \
	std::cout << "\n"; \
	attempts++; if (res) passes++; \
} while(0)

// test whether the `left` and `right` fulfill the `operand` given on the pretense of `msg`.
#define tester_cmp(left, operand, right, msg) do { \
	auto lres = left; \
	auto rres = right; \
	bool res = lres operand rres; /* keep these as variables so that it only runs ONCE */ \
	std::cout << "\t[" << (res ? "PASS" : "FAIL") << "] " << msg; \
	if (verbose) \
		std::cout << "\n\t\t" #left " " #operand " " #right "\n\t\t" \
			<< lres << " " #operand " " << rres << "\n"; \
	std::cout << "\n"; \
	attempts++; if (res) passes++; \
} while(0)

// output the given variable for debugging purposes
#define tester_with(var) do { \
	if (verbose) \
		std::cout << "\tWith " << #var << " = " << var << "\n"; \
} while(0)

#endif