一个简单地C++ Unit Test framework

一   使用

 

先说一下如何使用这个framework。其基本用法和java的junit差不多,只是没那么强大而已。看一下下面所示的这个IntWraper 类:

 

 

#ifndef _INTWRAPER_H
#define _INTWRAPER_H
class IntWraper
{
public:
	IntWraper(int n):
	  m_nInteger(n){}

	  void Value(int newValue){m_nInteger = newValue;}
	  int Value( void )const{return m_nInteger;}
	  IntWraper& operator += (IntWraper& rhs)
	  { 
		  m_nInteger += rhs.Value();
		  return *this;
	  }
private:
	int m_nInteger;
};
#endif //_INTWRAPER_H

 

 现在要给它写一个unittest ,你只要做如下工作就可以了:

  1.  包含头文件: #include "FAssert.h"
  2.  定义一个测试类,在这个类中使用下列宏来申明test case:

          DECLARE_AS_TESTER                //申明一个类为tester,需要放在开头[必有]

          DECLARE_SETUP                        //申明一个setup方法[可选]

          DECLARE_TEARDOWN                //申明一个teardown方法用于清理工作[可选]

          DECLARE_TEST(testName)          //申明一个test[至少有一个,否则没意义]

   3.  实现定义的setup,teardown,和test方法;

   4.  调用宏CALL_TEST注册这个unit test:

   5.  在主程序中定义一个TestMain,然后调用它的run方法:

         using namespace FTest;

         TestMain myTest(std::cout);

         myTest.run();

对IntWraper的完整的test 代码如下:

 

代码
// SampleTest.cpp : Defines the entry point for the console application.
//

#include
"IntWraper.h"
#include
"FAssert.h"
#include
"IntWraper.h"
class TestInt
{
DECLARE_AS_TESTER(TestInt)

DECLARE_SETUP

DECLARE_TEARDOWN

DECLARE_TEST(testValue)

DECLARE_TEST(testAdd)
private:
IntWraper
*m_pInt;
};

void TestInt::setup( void )
{
m_pInt
= new IntWraper(0);
}

void TestInt::tearDwon( void )
{
delete m_pInt;
}

void TestInt::testValue( void )
{
ASSERTEQUAL(
0,m_pInt->Value());
}
void TestInt::testAdd( void )
{
IntWraper other(
4);
*m_pInt += other;
ASSERTEQUAL(
3 , m_pInt->Value());
}

CALL_TEST(TestInt)

#include
<iostream>
int main(int argc, char* argv[])
{
using namespace FTest;
TestMain myTest(std::cout);
myTest.run();
return 0;
}

 

运行后你会得到输出结果:

从这个结果你可以看到一共运行了多少个test,成功多少,失败多少,如果失败了,还会给出失败的具体位置.

 

二  框架

    下面这个结构图是从xunit的结构图演变而来的,只不过在这里的testcase是由一系列的IFuncObject组成。对上面给出的例子来说,setup,teardown,testValue和testAdd都对应一个IFuncObject.这些IFuncObject只是对保存了setup,teardown,和test函数这些成员函数指针而已。在TestCase的run方法被调用的时候,按照下列顺序调用这些IFuncObject的Do方法:

        setup

        test1

        ...

        teardown



 

   最后在内存中会形成如下图所示的一棵树。

     这棵树有点特殊,因为它只有三层,最顶层的是一个suite,第二层是一些testcase,它们与程序员写的tester是一一对应的。最底层的是一些IFuncObject对象,它们对应与tester中的setup,teardown和具体的测试函数。由于对IFuncObject应用了NullObject模式,所以无论是否定义setup和teardown,每一个testcase,都有一个setup和一个teardown的IFuncObject。

调用的过程基本上是一个广度优先的过程。

三.实现

实现其实是很简单地,这里只说一下注册原理

3.1 注册

注册是通过TestObject来完成的,TestObject的申明如下:

 

template<typename Tester, void (Tester::*RealTest) ( void )>
class TestObject:public IFuncObject

 

它从IFuncObject继承下来的,接收两个模板参数,一个是tester,另一个是一个成员函数指针。一句

           DECLARE_TEST(testValue)

会定义一个TestObject对象,这样在tester被创建的时候,就会自动将这个函数指针注册到tester的对应的TestCase之中。

3.2 跟踪测试用例

    本来是想用异常来跟踪测试用例的运行状态的,可是发现这样就变得复杂啦。所以这个实现没有用,用了最简单地方式,调用类的全局函数来实现。

3.3 源码

  源码只包含两个头文件FTest.h和FAssert.h

 

代码
/*!
* Copyright (c) 2010 FengGe(CN), Inc
* All rights reserved
* This program is UNPUBLISHED PROPRIETARY property of FengGe(CN).
* Only for internal distribution.
*
* @file: FTest.h
*
* @brief: define the framework for unit test
*
* @author: li_shugan@126.com
*
* @version: 1.0
*
* @date: 2010-12-25
*/
#ifndef _FTEST_H
#define _FTEST_H
#include
<list>
#include
<vector>
#include
<algorithm>
#include
<ostream>
namespace FTest
{
using std::list;
using std::vector;
using std::find;
using std::ostream;
class Test
{
public:
virtual ~Test(){}
virtual void run( void ) = 0;
};

class TestSuite:public Test
{
public:
static TestSuite* getRootSuite( void )
{
if (NULL == g_pRootSuite)
{
g_pRootSuite
= new TestSuite();
}
return g_pRootSuite;
}
void add(Test *pTest)
{
m_listTests.push_back(pTest);
}

void remove(Test* pTest)
{
m_listTests.erase(find(m_listTests.begin(),m_listTests.end(),pTest));
}

virtual void run( void )
{
for_each(m_listTests.begin(),m_listTests.end(),RunFun);
}

private:
static void RunFun(Test* itFun)
{
itFun
->run();
}
list
<Test*> m_listTests;
static TestSuite* g_pRootSuite;
};
TestSuite
*TestSuite::g_pRootSuite = NULL;


class IFuncObject
{
public:
virtual void Do( void ) {}
static IFuncObject NULLFucObject;
static IFuncObject* NullFunction( void ){return &NULLFucObject;}
};
IFuncObject IFuncObject::NULLFucObject
= IFuncObject();

class TestCase:public Test
{
public:
TestCase():
m_funSetup(IFuncObject::NullFunction())
,m_funTeardown(IFuncObject::NullFunction())
{

}
virtual void run( void )
{
m_funSetup
->Do();
for_each(m_vTestObjects.begin(),m_vTestObjects.end(),
&RunFunc);
m_funTeardown
->Do();
}

void setSetup(IFuncObject *pSetup){m_funSetup = pSetup;}

void setTearDown(IFuncObject *pTearDown){m_funTeardown = pTearDown;}

void addTestObject(IFuncObject *pObject){m_vTestObjects.push_back(pObject);}
private:
static void RunFunc(IFuncObject* itFun)
{
itFun
->Do();
}
IFuncObject
*m_funSetup;
IFuncObject
*m_funTeardown;
vector
<IFuncObject*> m_vTestObjects;
};

template
<typename Tester, void (Tester::*RealTest) ( void )>
class TestObject:public IFuncObject
{
public:
TestObject(
void ){ Tester::getCase().addTestObject(this);}
virtual void Do( void ){(Tester::g_pInstance->*RealTest)();}
};

template
<typename Tester, void (Tester::*Setup) ( void )>
class SetupObject:public IFuncObject
{
public:
SetupObject(
void ){ Tester::getCase().addTestObject(this);}
virtual void Do( void ){(Tester::g_pInstance->*Setup)();}
};


template
<typename Tester, void (Tester::*TearDown) ( void )>
class TearDownObject:public IFuncObject
{
public:
TearDownObject(
void ){ Tester::getCase().setTearDown(this);}
virtual void Do( void ){(Tester::g_pInstance->*TearDown)();}
};

template
<typename Tester>
class TestCaller
{
public:
TestCaller()
{
Tester::g_pInstance
= new Tester();
TestSuite::getRootSuite()
->add(&Tester::getCase());

}

~TestCaller()
{
delete Tester::g_pInstance;
}
};
}

#define DECLARE_AS_TESTER(tester) typedef tester TestCaseType; \
static FTest::TestCase __testCase; \
public: \
static FTest::TestCase& getCase( void ){return __testCase;} \
static tester* g_pInstance;

#define DECLARE_TEST(testName) public: \
void testName( void ); \
FTest::TestObject
<TestCaseType,&TestCaseType::testName> __test##testName; \

#define DECLARE_SETUP public: \
void setup( void ); \
FTest::SetupObject
<TestCaseType,&TestCaseType::setup> __test##setup;

#define DECLARE_TEARDOWN public: \
void tearDwon( void ); \
FTest::TearDownObject
<TestCaseType,&TestCaseType::tearDwon> __test##tearDwon;


#define CALL_TEST(tester) tester* tester::g_pInstance = NULL; \
FTest::TestCase tester::__testCase
= FTest::TestCase(); \
FTest::TestCaller
<tester> __##tester##callTester;

#endif //FTEST_H

 

代码
/*!
* Copyright (c) 2010 FengGe(CN), Inc
* All rights reserved
* This program is UNPUBLISHED PROPRIETARY property of FengGe(CN).
* Only for internal distribution.
*
* @file: FAssert.h
*
* @brief: used for my test framework
*
* @author: li_shugan@126.com
*
* @version: 1.0
*
* @date: 2010-12-25
*/
#ifndef _FASSERT_H
#define _FASSERT_H
#include
<ostream>
#include
<string>
#include
<sstream>
#include
"FTest.h"

namespace FTest
{
using std::ostream;
using std::endl;
using std::string;
using std::stringstream;

class TestMain
{
public:
TestMain(ostream
& rOstream):
m_pStream(
&rOstream)
,m_nFailedCnt(
0)
,m_nSuccCnt(
0)
{
g_pInstance
= this;
}

~TestMain( )
{
getSteam()
<<"Total: "<<m_nFailedCnt + m_nSuccCnt << " Success: "<<m_nSuccCnt<<" Failed: "<<m_nFailedCnt<<endl;
g_pInstance
= NULL;
}

void run()
{
TestSuite::getRootSuite()
->run();
}

public:
static ostream& getSteam(){return *(g_pInstance->m_pStream);}
static void addFaildCnt() {++g_pInstance->m_nFailedCnt;}
static void addSuccCnt() {++g_pInstance->m_nSuccCnt;}

private:
ostream
* m_pStream;
int m_nFailedCnt;
int m_nSuccCnt;
static TestMain* g_pInstance;
};

TestMain
* TestMain::g_pInstance = NULL;

inline
void Assert(bool bTrue,const string& strFile,int nLine)
{
if (!bTrue)
{
TestMain::getSteam()
<<"Failed at "<<strFile<<"("<<nLine<<")"<<endl;
TestMain::addFaildCnt();
}
else
{
TestMain::addSuccCnt();
}
}

template
<typename T1,typename T2>
void AssertEqual(const T1& lhs,const T2& rhs,const string& strFile,int nLine)
{
if (lhs != rhs)
{
TestMain::getSteam()
<<"Failed at "<<strFile<<nLine<<": expected "<<lhs<<" but "<<rhs<<endl;
TestMain::addFaildCnt();
}
else
{
TestMain::addSuccCnt();
}
}
}

#define ASSERT(b) FTest::Assert(b,__FUNCTION__,__LINE__)
#define ASSERTFAIL(b) FTest::Assert(!b,__FUNCTION__,__LINE__)
#define ASSERTEQUAL(lhs,rhs) FTest::AssertEqual(lhs,rhs,__FUNCTION__,__LINE__)

#endif //FASSERT_H

 

 

 



posted @ 2010-12-26 12:24  李书淦  阅读(1679)  评论(0编辑  收藏  举报