一个简单的C++单元测试框架

source code: SimpleTestSuite.zip


1. 目的

        在实际开发中单元测试必不可少,在迭代开发和回归测试中尤为重要,好的完善的单元测试能够增强开发者的信心,提高开发效率,增强系统的稳定性!为此一个单元测试框架必不可少,CppUnitgtest就是两款非常优秀的单元测试框架。但有时候我们可以只想简单的测试一些结果值(大部分情况如此),用一些比较复杂的单元测试框架有点大材小用了,Bruce Eckel在《Thinking In C++》给出了一个很好的例子,在此基础上做了点小的修改,在开发中只需要简单的包含两个文件,就可以进行大部分测试需求。

        单元测试框架(TestSuite)分两个部分,TestSuite, 都位于命名空间TestSuite中。TestSuite::Test负责具体的测试任务,TestSuite::Suite用于统一管理一组测试类。

2. TestSuite::Test

        测试类的基类,TestSuite::Test::Run是纯虚函数供子类实现。该类提供了成功与失败测试的计数,支持控制台和文件输出。为了能够记录失败时的具体信息,如失败案例的文件名和行号等,定义宏进行测试。具体的测试工作由TestSuite::Test::DoTest完成。

// Test macro
#define DO_TEST(cond) \
	DoTest(cond, #cond, __FILE__, __LINE__)

#define DO_FAIL(desc) \
	DoFail(desc, __FILE__, __LINE__)

#define DO_SUCCESS() \
	DoPass()

// TestSuite::Test
class Test
{
public:
	Test(): m_pOstrm(NULL){ }
	virtual ~Test(){ }

	virtual void Run() = 0;
	virtual void Reset(){ m_nPass = m_nFail = 0;}

	long GetPassNum() const { return m_nPass; }
	long GetFailNum() const { return m_nFail; }
	long Report() const;
	friend class Suite;
	
protected:
	void DoTest(bool cond, const char* desc, const char* fileName, int lineNo);
	void DoFail(const char* desc, const char* fileName, int lineNo);
	void DoPass() { ++m_nPass; }

private:
	Test(const Test& );
	Test& operator=(const Test& );

private:
	long m_nPass;
	long m_nFail;
	ostream* m_pOstrm;
};


// TestSuite::Test implementation
long Test::Report() const
{
	if (m_pOstrm){
		*m_pOstrm << "Test \"" << typeid(*this).name() <<"\"\n" << "\tPass: " 
			<< color<GREEN> << m_nPass << color<DEFAULT> 
			<< "\t\t" << "Fail: ";

		if (m_nFail == 0){
			*m_pOstrm << color<GREEN> << m_nFail << color<DEFAULT> << endl;
		} else {
			*m_pOstrm << color<RED> << m_nFail << color<DEFAULT> << endl;
		}
	}

	return m_nFail;
}

void Test::DoTest(bool cond, const char* desc, const char* fileName, int lineNo)
{
	if (cond){
		DoPass();
	} else {
		DoFail(desc, fileName, lineNo);
	}
}

void Test::DoFail(const char* desc, const char* fileName, int lineNo)
{
	++m_nFail;
	if (m_pOstrm){
		*m_pOstrm << color<RED> << typeid(*this).name() << " failure: (" 
			<< desc << "), " << fileName << "(line: " 
			<< lineNo << ")" << color<DEFAULT> << endl;
	}
}

3. TestSuite::Suite

TestSuite::Suite套件类用于管理一组测试类。该类有一个列表用于保存所有测试类实例的指针,TestSuite::Suite::Run只是遍历该列表,依次执行每个测试类实例的Run函数。

// TestSuite::Suite
class Suite
{
public:
	Suite(const string& name, ostream* pOstrm = &std::cout)
		: m_name(name), m_pOstrm(pOstrm)
	{ }

	void Run();
	void AddTest(Test* test) throw(TestSuiteError);
	void AddSuite(const Suite& suite);
	long GetPassNum() const;
	long GetFailNum() const;
	long Report() const;
	void Reset();
	void Free();

private:
	Suite(const Suite& );
	Suite& operator=(const Suite& );

private:
	string m_name;
	ostream* m_pOstrm;
	vector<Test*> m_tests;
};


// TestSuite::Suite implementation
void Suite::Run()
{
	for (size_t i = 0; i < m_tests.size(); ++i){
		assert(m_tests[i] != NULL);
		m_tests[i]->Run();
	}
}

void Suite::AddTest(Test* test) throw(TestSuiteError)
{
	if (NULL == test){
		throw TestSuiteError("Null pointer in TestSuite::AddTest");
	} else if (m_pOstrm){
		test->m_pOstrm = m_pOstrm;
	}

	test->Reset();
	m_tests.push_back(test);
}

void Suite::AddSuite(const Suite& suite)
{
	const vector<Test*>& tests = suite.m_tests;
	for (size_t i = 0; i < tests.size(); ++i){
		assert(tests[i] != NULL);
		AddTest(tests[i]);
	}
}

long Suite::GetPassNum() const
{
	long count = 0;
	for (size_t i = 0; i < m_tests.size(); ++i){
		assert(m_tests[i] != 0);
		count += m_tests[i]->GetPassNum();
	}

	return count;
}

long Suite::GetFailNum() const
{
	long count = 0;
	for (size_t i = 0; i < m_tests.size(); ++i){
		assert(m_tests[i] != NULL);
		count += m_tests[i]->GetFailNum();
	}

	return count;
}

long Suite::Report() const
{
	if (m_pOstrm){
		long failCounter = 0;
		string strFormat = "Suite \"" + m_name + "\"";
		string seperator = string(strFormat.size(), '=');

		*m_pOstrm << strFormat << endl;
		*m_pOstrm << seperator << endl;

		for (size_t i = 0; i < m_tests.size(); ++i){
			assert(m_tests[i] != NULL);
			failCounter += m_tests[i]->Report();
		}

		*m_pOstrm << seperator << endl;
		*m_pOstrm << "Total:\tPass: " << color<GREEN> << GetPassNum() << color<DEFAULT>
			<< "\t\tFail: ";
		if (failCounter == 0){
			*m_pOstrm << color<GREEN> << failCounter << color<DEFAULT> << endl;
		} else {
			*m_pOstrm << color<RED> << failCounter << color<DEFAULT> << endl;
		}

		return failCounter;
	} else{
		return GetPassNum();
	}
}

void Suite::Reset()
{
	for (size_t i  = 0; i < m_tests.size(); ++i){
		assert(m_tests[i] != NULL);
		m_tests[i]->Reset();
	}
}

void Suite::Free()
{
	for (size_t i  = 0; i < m_tests.size(); ++i){
		assert(m_tests[i] != NULL);
		delete m_tests[i];
		m_tests[i] = NULL;
	}

	m_tests.clear();
}

4. 跨平台输出文本颜色控制

    为了更便捷的看出测试结果,在控制台输出时,用颜色标记测试失败项和测试结果,测试失败信息和失败结果用红色标记,测试成功结果用绿色表示。在windows的控制台和linux的终端,文本颜色控制是跟平台相关的。Windows下有一组API用于控制控制台文本颜色,在linux采用ANSI escape code控制终端文本输出颜色。

#if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif


// Color type
	enum ColorType
	{
		DEFAULT,
		GREEN,
		RED,  
		YELLOW
	};

// A cross platform function
	template <ColorType _ct>
	inline ostream& color(ostream& o)
	{
	#if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__)
		static const int COLOR[] = {7, 10, 12, 14};
		static HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
		SetConsoleTextAttribute(hStdout, COLOR[_ct]);
	#else
		static const char* COLOR[] = {"\e[0;m", "\e[1;32m", "\e[1;31m", "\e[1;33m"};
		o << COLOR[_ct];
	#endif

		return o;
	}

可以这样控制文本输出颜色:

std::cout << color<RED> << "RED" << color<GREEN> << "GREEN" << color<DEFAULT>;


5. 示例

#include "TestSuite/TestSuite.h"

using namespace TestSuite;

class Sample
{
public:
	int Add(int a, int b)
	{
		return a + b;
	}

	int Div(int a, int b)
	{
		if (b == 0){
			throw logic_error("The denominator is zero");
		}

		return a / b;
	}
};

class SampleTest: public Test
{
public:
	void Run()
	{
		TestAdd();
		TestDiv();
		TestDivException();
	}

	void TestAdd()
	{
		DO_TEST(0 == m_sample.Add(1, -1));
		DO_TEST(5 == m_sample.Add(2, 3));
		DO_TEST(1 == m_sample.Add(0, 0));	// This will fail
	}

	void TestDiv()
	{
		DO_TEST(0 == m_sample.Div(0, 1));
		DO_TEST(5 == m_sample.Div(10, 2));
	}

	void TestDivException()
	{
		try{
			m_sample.Div(1, 0);
			DO_FAIL("Invalid denominator in Sample2::Div");
		}catch(logic_error& e){
			DO_SUCCESS();
		}

		try{
			m_sample.Div(1, 1);	// No exception throw
			DO_FAIL("Invalid denominator in Sample2::Div");	// This will fail
		} catch(logic_error& e){
			DO_SUCCESS();
		}
	}

private:
	Sample m_sample;
};

int main(int argc, char* argv[])
{
	Suite suite("Sample Suite");

	suite.AddTest(new SampleTest);
	suite.Run();
	long failNum = suite.Report();
	suite.Free();

	return failNum;
}

如果要将测试结果输出到文件,只需将一个文件对象指针转给Suite构造对象:

std::ofstream ofs("test.log");

Suite suite("Sample Suite", &ofs);

最后别忘关闭文件!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值