zoukankan      html  css  js  c++  java
  • 一个简单地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



  • 相关阅读:
    借了个屏幕来用
    生命开始的地方
    看了STLPort的安装方法,晕了
    程序员必备的10大健康装备!
    《代码整洁之道》读书笔记
    Mockito使用
    学习Emacs的理由
    shell 脚本编程的10 个最佳实践
    MongoDB入门
    用Orgmode实践《奇特的一生》
  • 原文地址:https://www.cnblogs.com/li_shugan/p/1910463.html
Copyright © 2011-2022 走看看