• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-341  評論-2670  文章-0  trackbacks-0
                現在市面上已經有很多Unit Test的工具了。對于C++來說最為著名的莫過于CppUnit。CppUnit已經具有豐富的功能,例如UI、報告生成等等。那么為什么還要自己做Unit Test工具呢?主要還是為了學習,其次是可以為自己的特殊需求打造特殊的工具。

                隨著程序越來越復雜,Unit Test的重要作用已經不言而喻了。在Kernel FP的開發過程中,由于經常需要重構,于是Unit Test就已經成為必不可少的工具之一了。接口不變化的重構,可以通過足夠的Unit Test來在最大的程度上保證新的代碼不會引發更多問題。為了開發Unit Test的工具,我們首先了解一下一個Unit Test的典型需求是什么。

                Unit Test作為一種檢驗代碼是否有已經預期錯誤的手段,我們需要很多的TestCase,每一個TestCase又需要若干的面向具體問題的檢查,稱為TestMethod,每一個TestMethod又需要若干的條件判斷。我們可以在Unit Test當中寫很多返回bool類型的表達式,并且讓true代表成功,false代表失敗。于是Unit Test的一個重要功能就是執行我們定義的所有條件表達式,并作出統計。

                Unit Test的輸入可以有很多,但是測試代碼是必不可少的。于是我們需要一些容器來包含代碼,并且讓一個處于后臺的執行引擎來依次執行我們的代碼然后輸出信息。

                Unit Test的輸出無非就是Unit Test的執行狀態,也就是每一個條件表達式所在的位置、結果以及附帶的供我們查看的文字信息。那么應該如何處理輸出信息呢?在自己開發Unit Test框架的過程中,我們可以將信息進行分類,然后使用Observer模式將信息傳播出去:
             1         class IVL_TestRecorder : public IVL_Interface
             2         {
             3         public:
             4             virtual void                    BeginRun(VL_TestRunner* Runner)=0;
             5             virtual void                    EndRun()=0;
             6             virtual void                    BeginCase(VInt Index)=0;
             7             virtual void                    EndCase()=0;
             8             virtual void                    BeginMethod(VInt Index)=0;
             9             virtual void                    EndMethod()=0;
            10             virtual void                    IgnoreMethod(VInt Index)=0;
            11             virtual void                    ExceptionOccur()=0;
            12             virtual void                    Pass(VUnicodeString Message)=0;
            13             virtual void                    Fail(VUnicodeString Message)=0;
            14             virtual void                    Print(VUnicodeString Message)=0;
            15         };

                從上面的接口我們可以看到,每一步的開始和結束,以及條件的信息以及結構都期望輸入到一個IVL_TestRecorder對象里面。這里我們可以做很多事情。首先VL_TestRunner可以,而且必須可以獲得用于分辨每一個TestMethod的信息,譬如說TestCase的名字以及TestMethod的名字。其次,無論TestMethod里面做了什么樣的事情,VL_TestRunner都應當盡可能地捕捉到。

                在IVL_TestRecorder的幫助之下我們就可以實現一些工具了。譬如一個用于將信息傳播給一堆Recorder的Recorder,譬如一個用于生成HTML報告的Recorder,譬如一個用于統計的Recorder,譬如一個將信息發布到GUI上的Recorder等等。第一種Recorder將Recorder變成了樹,然后其余的Recorder只需要裝飾葉節點,就可以獲得一個可擴展性非常強的Recorder工具了。

                當然,接下去的問題是如何包含測試代碼。我們可以將很多VL_TestCase的實例裝入VL_TestRunner,并且將很多函數裝入VL_TestCase。這個時候boost的functor就可以大大簡化這個過程,當然我自己并沒有使用它,而是自己寫了一個類似的工具。于是VL_TestRunner的職責就是調用VL_TestCase,VL_TestCase的職責就是調用TestMethod,而TestMethod則是我們寫的測試代碼,職責是測試一系列的條件并輸出信息。通過兩個類以及一些宏的配合,我們最終可以得到類似于下面的結果:
             1 class TestCase1 : public VL_TestCase
             2 {
             3 public:
             4     TestCase1()
             5     {
             6         VL_UNITTEST_ADDMETHOD(TestCase1,TestMethod1);
             7         VL_UNITTEST_ADDMETHOD(TestCase1,TestMethod2);
             8         VL_UNITTEST_ADDMETHOD(TestCase1,TestMethod3);
             9     }
            10 
            11     void TestMethod1()
            12     {
            13         VL_UNITTEST_CHECK(1==1);
            14         VL_UNITTEST_CHECK(1==0);
            15     }
            16 
            17     void TestMethod2()
            18     {
            19         VL_UNITTEST_ASSERT(1==1);
            20         VL_UNITTEST_PRINT(L"MESSAGE");
            21     }
            22 
            23     void TestMethod3()
            24     {
            25         VL_UNITTEST_ASSERT(1==0);
            26         VL_UNITTEST_PRINT(L"MESSAGE");
            27     }
            28 };
            29 
            30 class TestCase2 : public VL_TestCase
            31 {
            32 public:
            33     TestCase2()
            34     {
            35         VL_UNITTEST_ADDMETHOD(TestCase2,TestMethod1);
            36         VL_UNITTEST_ADDMETHOD(TestCase2,TestMethod2);
            37     }
            38 
            39     void TestMethod1()
            40     {
            41         int a=0;
            42         a=a/a;
            43         VL_UNITTEST_PRINT(L"REACH");
            44     }
            45 
            46     void TestMethod2()
            47     {
            48         throw "XXX";
            49         VL_UNITTEST_PRINT(L"REACH");
            50     }
            51 };
            52 
            53 class TestRunner : public VL_TestRunner
            54 {
            55 public:
            56     TestRunner()
            57     {
            58         VL_UNITTEST_ADDCASE(TestCase1);
            59         VL_UNITTEST_ADDCASE(TestCase2);
            60     }
            61 };


                上面的宏都是一些很簡單的技巧,就不詳細講了。如果熟悉宏的語法的話,則可以很容易的實現它們。至于數據結構上,VL_TestRunner可以看成一個VL_TestCase的數組,而VL_TestCase則包含了一個函數指針的數組。

                那這兩個框架類的接口應當是什么樣子的呢?我們允許Runner在內部添加TestCase,允許TestCase在內部添加TestMethod,并且允許從Runner獲得整個結構的內容,最后Runner應當能夠執行所有的TestMethod,于是接口很自然地就會變成這個樣子:

             1 class VL_TestCase : public VL_Base
             2 {
             3     friend class VL_TestRunner;
             4 private:
             5     VL_TestMethodMap                FMethods;
             6     IVL_TestRecorder*                FCurrentRecorder;
             7 
             8     VBool                            RunCPPEHProtected(IVL_TestRecorder* Recorder , VL_TestMethod* Method);
             9     VBool                            RunSEHProtected(IVL_TestRecorder* Recorder , VL_TestMethod* Method);
            10     void                            Run(IVL_TestRecorder* Recorder , VL_TestRunner* Runner , VInt CaseIndex , IVL_TestFilter* Filter);
            11 
            12 protected:
            13     virtual void                    Initialize();
            14     virtual void                    Finalize();
            15 
            16     void                            AddMethod(VUnicodeString Name , VL_TestMethodPtr Method);
            17     IVL_TestRecorder*                GetCurrentRecorder();
            18 
            19 public:
            20     VL_TestCase();
            21 
            22     VInt                            GetMethodCount();
            23     VUnicodeString                    GetMethodName(VInt Index);
            24 };
            25 typedef VL_AutoPtr<VL_TestCase>                                    VL_TestCasePtr;
            26 typedef VL_List<VL_TestCasePtr , false , VL_TestCase*>            VL_TestCaseList;
            27 typedef VL_ListedMap<VUnicodeString , VL_TestCasePtr>            VL_TestCaseMap;
            28 
            29 class VL_TestRunner : public VL_Base
            30 {
            31 protected:
            32     VL_TestCaseMap                    FCases;
            33     void                            AddCase(VUnicodeString Name , VL_TestCase* Case);
            34 
            35 public:
            36     VL_TestRunner();
            37 
            38     VInt                            GetCaseCount();
            39     VUnicodeString                    GetCaseName(VInt Index);
            40     VL_TestCase*                    GetCase(VInt Index);
            41     VL_TestCase*                    GetCase(VUnicodeString Name);
            42     void                            Run(IVL_TestRecorder* Recorder , IVL_TestFilter* Filter);
            43 };

                通過private的run加上一個friend class VL_TestRunner的聲明,還有處于protected內的若干函數,我們將每一個函數的可視范圍都清晰地定義了下來。好了,有了Recorder和Runner,我們就可以從外部監視Unit Test的執行狀態,并且不需要修改任何代碼就能進行擴展。因此我們可以很簡單的寫一個Console Host來執行Unit Test,也可以寫一個GUI Host來執行Unit Test。今天由于時間的關系,我只實現了一個簡單的Console Host,并且讓這個Console Host自己將Recorder交給Runner進行監視。這可以使得main函數非常簡單地完成:
            1 void vlmain()
            2 {
            3     GetConsole()->SetPauseOnExit(true);
            4     GetConsole()->SetTestMemoryLeaks(true);
            5     GetConsole()->SetTitle(L"Vczh Library++ 2.0 Unit Test Framework");
            6 
            7     TestRunner Runner;
            8     SimpleConsoleTestHost(&Runner);
            9 }

                其中,SimpleConsoleTestHost的內容如下:
             1 void SimpleConsoleTestHost(VL_TestRunner* Runner , IVL_TestRecorder* Recorder)
             2 {
             3     VL_TestRecorderList RecorderList;
             4     VL_TestConsoleRecorder ConsoleRecorder;
             5     VL_TestAllAcceptFilter AllAcceptFilter;
             6 
             7     if(Recorder)
             8     {
             9         RecorderList.Add(Recorder);
            10     }
            11     RecorderList.Add(&ConsoleRecorder);
            12     Runner->Run(&RecorderList,&AllAcceptFilter);
            13 }

                讓我們看看結果吧!

                這里貼出VL_UNITTEST_*的內容:
             1 #define VL_UNITTEST_ADDCASE(CLASSNAME)                                \
             2     do{                                                                \
             3         AddCase(L#CLASSNAME,(new CLASSNAME()));}                    \
             4     while(0)
             5 
             6 #define VL_UNITTEST_ADDMETHOD(CLASSNAME,METHODNAME)                    \
             7     do{                                                                \
             8         VL_TestMethod* Method=new VL_TestMethod;                    \
             9         Method->Bind(this,&CLASSNAME::METHODNAME);                    \
            10         this->AddMethod(L#METHODNAME,Method);                        \
            11     }while(0)
            12 
            13 #define VL_UNITTEST_PRINT(MESSAGE)                                    \
            14     do{                                                                \
            15         GetCurrentRecorder()->Print(MESSAGE);                        \
            16     }while(0)
            17 
            18 #define VL_UNITTEST_TEST(CONDITION,MESSAGE,ISASSERT)                \
            19     do{                                                                \
            20         if(CONDITION)                                                \
            21         {                                                            \
            22             GetCurrentRecorder()->Pass(MESSAGE);                    \
            23         }                                                            \
            24         else                                                        \
            25         {                                                            \
            26             GetCurrentRecorder()->Fail(MESSAGE);                    \
            27             if(ISASSERT)                                            \
            28             {                                                        \
            29                 return;                                                \
            30             }                                                        \
            31         }                                                            \
            32     }while(0)
            33 
            34 #define VL_UNITTEST_CHECK_MESSAGE(CONDITION,MESSAGE)                \
            35     VL_UNITTEST_TEST(CONDITION,MESSAGE,false)
            36 
            37 #define VL_UNITTEST_CHECK(CONDITION)                                \
            38     VL_UNITTEST_CHECK_MESSAGE(CONDITION,L#CONDITION)
            39 
            40 #define VL_UNITTEST_CHECK_FAIL(MESSAGE)                                \
            41     VL_UNITTEST_CHECK_MESSAGE(false,MESSAGE)
            42 
            43 #define VL_UNITTEST_ASSERT_MESSAGE(CONDITION,MESSAGE)                \
            44     VL_UNITTEST_TEST(CONDITION,MESSAGE,true)
            45 
            46 #define VL_UNITTEST_ASSERT(CONDITION)                                \
            47     VL_UNITTEST_ASSERT_MESSAGE(CONDITION,L#CONDITION)
            48 
            49 #define VL_UNITTEST_ASSERT_FAIL(MESSAGE)                            \
            50     VL_UNITTEST_ASSERT_MESSAGE(false,MESSAGE)

                接下來就是寫一些Test、完成GUI Host、完成Kernel FP了。
            posted on 2008-11-13 09:38 陳梓瀚(vczh) 閱讀(2527) 評論(4)  編輯 收藏 引用 所屬分類: 其他

            評論:
            # re: 打造自己的Unit Test工具 2008-11-13 16:50 | LOGOS
            輪子,雖然是以學習為目的

            使用太繁瑣,我現在手頭上使用的非常方便
            TEST( testa )
            {
            }

            TEST_F( fixture , testb )
            {
            }  回復  更多評論
              
            # re: 打造自己的Unit Test工具 2008-11-13 18:45 | 陳梓瀚(vczh)
            你那個需要全局變量。而且host是自動的話,那么等于失去了添加功能的機會。除了修改測試的代碼。  回復  更多評論
              
            # re: 打造自己的Unit Test工具 2008-11-14 04:19 | 空明流轉
            相比之下我還是個MACRO控,以前我也寫過UnitTest的東東。。。  回復  更多評論
              
            # re: 打造自己的Unit Test工具 2008-11-17 04:33 | Lnn
            不錯哎  回復  更多評論
              
            亚州日韩精品专区久久久| WWW婷婷AV久久久影片| 久久久久久久国产免费看| 久久国产香蕉一区精品| 色诱久久av| 婷婷久久久亚洲欧洲日产国码AV| 亚洲精品乱码久久久久久按摩| 精品久久久久久久| 久久久WWW成人免费精品| 四虎国产精品成人免费久久| 久久精品一区二区国产| 亚洲国产精品综合久久一线| 久久水蜜桃亚洲av无码精品麻豆 | 久久精品国产免费一区| 日本亚洲色大成网站WWW久久| 久久精品国产亚洲av水果派| 久久噜噜久久久精品66| 国产91色综合久久免费| 亚洲国产精品无码久久一区二区 | 色偷偷88欧美精品久久久 | 亚洲v国产v天堂a无码久久| 久久久久亚洲AV片无码下载蜜桃 | 无码精品久久一区二区三区| 国内精品久久久久伊人av| 久久久国产99久久国产一| 91精品免费久久久久久久久| 狼狼综合久久久久综合网| 久久综合一区二区无码| 国产成人综合久久久久久| 国产精品久久99| 7777久久亚洲中文字幕| 久久精品99久久香蕉国产色戒| 国产成人久久精品一区二区三区| 日韩AV毛片精品久久久| 国产精品美女久久福利网站| 亚洲国产成人精品91久久久| 青青热久久国产久精品| 久久免费香蕉视频| 久久久久无码国产精品不卡| 热综合一本伊人久久精品| 亚洲国产成人久久综合野外|