source code: SimpleTestSuite.zip
在实际开发中单元测试必不可少,在迭代开发和回归测试中尤为重要,好的完善的单元测试能够增强开发者的信心,提高开发效率,增强系统的稳定性!为此一个单元测试框架必不可少,CppUnit和gtest就是两款非常优秀的单元测试框架。但有时候我们可以只想简单的测试一些结果值(大部分情况如此),用一些比较复杂的单元测试框架有点大材小用了,Bruce Eckel在《Thinking In C++》给出了一个很好的例子,在此基础上做了点小的修改,在开发中只需要简单的包含两个文件,就可以进行大部分测试需求。
单元测试框架(TestSuite)分两个部分,Test和Suite, 都位于命名空间TestSuite中。TestSuite::Test负责具体的测试任务,TestSuite::Suite用于统一管理一组测试类。
测试类的基类,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 << m_nPass << color
<< "\t\t" << "Fail: ";
if (m_nFail == 0){
*m_pOstrm << color << m_nFail << color << endl;
} else {
*m_pOstrm << color << m_nFail << color << 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 << typeid(*this).name() << " failure: ("
<< desc << "), " << fileName << "(line: "
<< lineNo << ")" << color << endl;
}
}
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 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& 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 << GetPassNum() << color
<< "\t\tFail: ";
if (failCounter == 0){
*m_pOstrm << color << failCounter << color << endl;
} else {
*m_pOstrm << color << failCounter << color << 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();
}
为了更便捷的看出测试结果,在控制台输出时,用颜色标记测试失败项和测试结果,测试失败信息和失败结果用红色标记,测试成功结果用绿色表示。在windows的控制台和linux的终端,文本颜色控制是跟平台相关的。Windows下有一组API用于控制控制台文本颜色,在linux采用ANSI escape code控制终端文本输出颜色。
#if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__)
#define WIN32_LEAN_AND_MEAN
#include
#endif
////////////////////////////////////////////////////////////////////////
// Color type
enum ColorType
{
DEFAULT,
GREEN,
RED,
YELLOW
};
////////////////////////////////////////////////////////////////////////// A cross platform function
template
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>;
#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);
最后别忘关闭文件!