• <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>
            隨筆-60  評論-98  文章-0  trackbacks-0

            用CPPUnit做單元測試
            例子程序下載:http://www.codeproject.com/library/Using_CPPUnit/my_tests.zip
            CPPUnit最新版本免費下載:
            http://cppunit.sourceforge.net/
            CPPUnit是基于C++的單元測試框架,可以有效提高開發(fā)的系統(tǒng)質(zhì)量。
            引言:
            QA過程常采用兩種測試方法:
            1、單元測試(acceptance測試):為軟件系統(tǒng)中的每一個邏輯單元制定的一系列驗證方法。僅測試單元的功能,而不考慮各個單元之間的協(xié)作關(guān)系。
            2、系統(tǒng)測試(集成測試):測試系統(tǒng)的功能,尤其是各單元模塊之間的協(xié)作關(guān)系。
            下面要講的是如何采用CPPUnit對C/C++工程進行單元測試。
            文章假設(shè)讀者熟悉單元測試的概念及其重要性。
            單元測試設(shè)計:
            想一下開發(fā)團隊中常常出現(xiàn)的一種場景:程序員正在使用Debugger工具測試代碼。采用Debugger工具可以可以隨時隨地檢查每個變量。步步跟蹤,檢查變量的值是否異常。Debugger是一種強有力的調(diào)試工具,但是調(diào)試速度相當慢,并且包含不少錯誤。在這種情況下調(diào)試是讓人崩潰的。這些復(fù)雜有大量重復(fù)的驗證方法是可以通過自動化的手段完成的,需要做的是選擇合適的工具并編寫少量代碼。
            下面要介紹的工具叫做“單元測試框架”,借助這種工具,可以通過編寫一些小的模塊來完成模塊(可以是類、函數(shù)和庫)的單元測試。
            下面來看一個例子:編寫一個小的模塊,主要功能是求兩數(shù)之和。其C語言代碼如下:
            BOOL addition(int a, int b)
            {
                return (a + b);
            }
            測試單元編寫成另外一個模塊(C函數(shù))。該模塊測試所有可能的求兩數(shù)之和的組合,通過返回True或False來判斷被測模塊是否通過了測試。代碼如下:
            BOOL additionTest()
            {
                if ( addition(1, 2) != 3)
                {
                    return (FALSE);
                }

                if ( addition(0, 0) != 0)
                {
                    return (FALSE);
                }

                if ( addition(10, 0) != 10)
                {
                    return (FALSE);
                }

                if ( addition(-8, 0) != -8)
                {
                    return (FALSE);
                }

                if ( addition(5, -5) != 0)
                {
                    return (FALSE);
                }

                if ( addition(-5, 2) != -3)
                {
                    return (FALSE);
                }

                if ( addition(-4, -1) != -5)
                {
                    return (FALSE);
                }

                return (TRUE);
            }
            測試的情況包括:
            正數(shù)+正數(shù)
            0+0
            正數(shù)+0
            負數(shù)+0
            正數(shù)+負數(shù)
            負數(shù)+正數(shù)
            負數(shù)+負數(shù)
            每一次測試都是通過對比被測模塊的返回值和期望值,如果二者不同,返回FALSE。如果最終返回TRUE,說明模塊通過了所有的測試。
            這個用以測試其他模塊的小模塊(函數(shù))被稱為Test Case, 其中包含了程序員需要對被測單元的一系列檢查。每一個確認(對被測單元的一次調(diào)用)都必須和被測單元相對應(yīng)。在這個例子中,檢查了“求和操作”在操作數(shù)符號不同的情況下的運行情況。當然了,還需要另外寫一些Test Case來驗證其他情況下的運行情況。比如其他一些常見的加法組合。例子如下:
            int additionPropertiesTest()
            {
                //conmutative: a + b = b + a
                if ( addition(1, 2) != addition(2, 1) )
                {
             return (FALSE);
                }

                //asociative: a + (b + c) = (a + b) + c
                if ( addition(1, addition(2, 3)) != addition(addition(2, 1), 3 ) )
                {
             return (FALSE);
                }

                //neutral element: a + NEUTRAL = a
                if ( addition(10, 0) != 10 )
                {
             return (FALSE);
                }

                //inverse element: a + INVERSE = NEUTRAL
                if ( addition(10, -10) != 0 )
                {
             return (FALSE);
                }

                return (TRUE);
            }
            上面的例子測試了多個數(shù)據(jù)相加順序不同的情況。
            上述的兩個Test Case組成了一個Test Suite,Test Suite是指用來測試同一被測單元的一組Test Case。
            在開發(fā)被測模塊時必須同時編寫這些Test Case和Test Suite的代碼,被測模塊變更時,要同時變更(有時需要增加)相應(yīng)的Test Case和Test Suite。
            舉例來說,當求和模塊升級為可以對小數(shù)求和的模塊,就必須變更Test Case和Test Suite,加入諸如addDecimalNumbersTest之類的Test Case。
            極限編程建議程序員在編寫目標模塊之前就開發(fā)出所有單元測試中要用到的Test Case。其主要理由是:一旦程序員處于開發(fā)過程之中,那么他就進入了一個持續(xù)改進的階段,必須同時考慮單元模塊功能、需要公布的接口、需要給方法傳遞的參數(shù)、外部訪問、內(nèi)部行為等等。在編寫目標單元之前通過開發(fā)Test Case,可以對需要考慮的這些因素有更好的了解,這樣編寫目標模塊與其他方法相比速度會更快,代碼的質(zhì)量也會更好。
            每當開發(fā)團隊需要發(fā)布新版本的時候,都要進行徹底的單元測試。所有的單元必須通過單元測試,這樣就可以發(fā)布成功的版本。如果有1個或以上的單元沒有通過所有的測試,Bug就出現(xiàn)了。遇到這種情況就需要在進行測試,如果需要的話還需要增加新的Test Case,檢查可以使Bug再現(xiàn)的所有情況。如果新的Test Case可以使Bug重現(xiàn),就可以修正這個Bug,然后再進行測試,如果模塊通過了測試,就可以認為Bug已經(jīng)修正,可以發(fā)布新的無Bug版本了。
            為每一個發(fā)現(xiàn)的Bug添加新的Test Case是很有必要的,因為Bug會反復(fù)出現(xiàn),當其重復(fù)出現(xiàn)時需要有效的測試來檢測Bug。這樣的話,Test Bettery會逐漸膨脹直至覆蓋所有的歷史Bug和潛在的錯誤。
            測試工具:
            有兩個小伙子,一個叫Kent Beck,另一個叫Eric Gamma,他們寫了一系列的Java類,希望可以把測試做的盡可能自動化,并稱之為JUnit,JUnit使整個單元測試界產(chǎn)生的很大的震動。其他的開發(fā)者們把JUnit的代碼移植到其他語言上,構(gòu)建了一大系列稱為xUnit框架的產(chǎn)品。其總包括C/C++的CUnit和CPPUnit,Delphi的DUnit,Visual Basic的VBUnit,.NET平臺上的NUnit,等等。
            所有這些框架都采用同樣的規(guī)則,對語言的依賴性很小,熟悉其中一個框架就能夠熟練應(yīng)用其他框架。
            下面要講的是如何通過使用CPPUnit來編寫測試代碼并提高單元的質(zhì)量。
            CPPUnit采用面向?qū)ο蟮木幊谭椒ǎ虚g會遇到諸如封裝、繼承、多態(tài)這些概念。另外,CPPUnit采用C++ SEH(Structured Exception Handling),所以還會遇到異常的概念,以及throw, try, finally, catch這些指令。
            CPPUnit
            每一個Test Case都需要在TestCase類的派生類中定義。TestCase類中包含了許多基本的功能,比如運行測試、在Test Suite中注冊Test Case等。
            比如在需要寫一個在磁盤上存儲數(shù)據(jù)的小模塊的時候,模塊(定義為DiskData類)主要實現(xiàn)兩個功能:讀取數(shù)據(jù)和裝載數(shù)據(jù)。例程如下:
            typedef struct _DATA
            {
                int number;
                char string[256];
            }DATA, *LPDATA;

            class DiskData
            {
            public:
                DiskData();
                ~DiskData();

                LPDATA getData();
                void setData(LPDATA value);

                bool load(char *filename);
                bool store(char *filename);

            private:
                DATA m_data;
            };

            此時,首先要做的事情不是弄明白上面的代碼是如何變出來的,而是要確定上面所定義的類是否完成了設(shè)計的全部功能——正確地讀取和存儲數(shù)據(jù)。

            為此,需要設(shè)計一個新的Test Suite,其中包含兩個Test Case:一個讀取數(shù)據(jù)、一個存儲數(shù)據(jù)。

            使用CPPUnit

            最新版本的CPPUnit可以在http://cppunit.sourceforge.net/上免費下載到,其中包含所有的庫文件、文檔、例子程序和其他有趣的素材。

            在Win32環(huán)境下,可以在VC++(6.0或更新版本)中使用CPPUnit,由于CPPUnit采用的是ANSI C++,所以可應(yīng)用于C++ Builder等開發(fā)環(huán)境中的版本較少。
            構(gòu)建庫文件的步驟可以在CPPUnit發(fā)布版本的INSTALL-WIN32.txt文件中找到。構(gòu)
            建好庫文件之后就可以著手編寫Test Suite了。

            在VC++下編寫單元測試程序的步驟如下:
             創(chuàng)建一個基于MFC的對話框應(yīng)用程序(或者文檔應(yīng)用程序)
             開啟RTTI:Project Settings -> C++ -> C++ Language
             在include目錄中加入CPPUnit\include:Tools -> Options -> Directories -> Include
             連接cppunitd.lib(靜態(tài)連接)或者cppunitd_dll.lib(動態(tài)連接),testrunnerd.lib。如果是在“Release”配置下編譯,同樣需要連接這些庫文件,只是需要把名稱中的“d”字母去掉。
             拷貝testrunnerd.dll文件到可執(zhí)行文件夾的下面(或者路徑下的其他文件夾中),如果是動態(tài)連接的話,還需要拷貝cppunitd_dll.dll(“Release”配置下需要拷貝testrunner.dll和cppunit_dll.dll)。

            配置好之后即可以著手進行單元測試類編碼了。

            待測試的DiskData類,主要實現(xiàn)兩個功能:讀取和存儲磁盤上的數(shù)據(jù)。要測試這兩個功能,需要兩個Test Case:一個負責(zé)讀取數(shù)據(jù)、一個負責(zé)存儲數(shù)據(jù)。
            下面是單元測試類的定義:
            #if !defined(DISKDATA_TESTCASE_H_INCLUDED)
            #define DISKDATA_TESTCASE_H_INCLUDED

            #if _MSC_VER > 1000
            #pragma once
            #endif // _MSC_VER > 1000

            #include <cppunit/TestCase.h>//為了從基類TestCase派生新的測試類
            #include <cppunit/extensions/HelperMacros.h>//方便快速定義測試類的宏

            #include "DiskData.h"

            class DiskDataTestCase : public CppUnit::TestCase
            {
                CPPUNIT_TEST_SUITE(DiskDataTestCase);//定義Test Suite的起點
             CPPUNIT_TEST(loadTest);//定義Test Case
             CPPUNIT_TEST(storeTest);
                CPPUNIT_TEST_SUITE_END();//定義Test Suite的終點

            public:
                void setUp();
                void tearDown();

            protected:
                void loadTest();
                void storeTest();

            private:
                DiskData *fixture;
            };

            #endif

            例程中,DiskDataTestCase類重載了兩個方法:setUp()和tearDown()。這兩個方法在Test Case開始和結(jié)束的時候自動運行。

            測試邏輯是在兩個Protected方法中實現(xiàn)的,稍后要涉及到如何為測試邏輯編碼。 

            例程的最后定義了指向DiskData類型數(shù)據(jù)的指針fixture,用以保存測試過程中的目標對象。setUp()是初始化函數(shù),在調(diào)用每一個Test Case之前調(diào)用setUp(),同時負責(zé)初始化目標對象。Test Case運行過程中要使用fixture。在每一個Test Case運行結(jié)束之后,調(diào)用tearDown()銷毀fixture。這樣,每次運行Test Case時所使用的都是新產(chǎn)生的fixture。

            測試步驟如下:
             開啟測試程序
             點擊“Run”按鍵
             調(diào)用setUp()方法:初始化fixture
             調(diào)用第一個Test Case函數(shù)
             調(diào)用tearDown()方法:釋放fixture
             調(diào)用setUp()方法:初始化fixture
             調(diào)用第二個Test Case函數(shù)
             調(diào)用tearDown()方法:釋放fixture 
             ...

            經(jīng)過編碼:
            #include "DiskDataTestCase.h"

            CPPUNIT_TEST_SUITE_REGISTRATION(DiskDataTestCase);


            void DiskDataTestCase::setUp()
            {
                fixture = new DiskData();
            }

            void DiskDataTestCase::tearDown()
            {
                delete fixture;
                fixture = NULL;
            }


            void DiskDataTestCase::loadTest()
            {
                // our load test logic
            }


            void DiskDataTestCase::storeTest()
            {
                // our store test logic
            }

            現(xiàn)在,編碼已經(jīng)變得非常簡單了:setUp()和tearDown()實現(xiàn)了創(chuàng)建、釋放fixture,下面要做的就是為loadTest()、storeTest()編碼了。

            Test Case編碼

            搞清楚需要測試那些方面之后的工作是編碼實現(xiàn)。可以通過使用庫函數(shù)、第三方庫函數(shù)、Win32 API或者C/C++操作符和指令的內(nèi)部屬性。

            有時需要輔助的文件或者數(shù)據(jù)庫表來存儲正確的數(shù)據(jù)。在本例中,通過對比內(nèi)部不數(shù)據(jù)和外部文件的數(shù)據(jù)來判斷結(jié)果是否正確。

            當出現(xiàn)錯誤時(比如內(nèi)部數(shù)據(jù)和外部數(shù)據(jù)不同),需要拋出異常。可以通過CPPUNIT_FAIL(message)宏實現(xiàn),也可以通過assertions宏實現(xiàn)。
            以下是一些常用的assertion宏:
             CPPUNIT_ASSERT(condition): 檢查condition,如為false,拋出異常
             CPPUNIT_ASSERT_MESSAGE(message, condition): 檢查condition,如為false,拋出異常,并顯示預(yù)先設(shè)定的信息
             CPPUNIT_ASSERT_EQUAL(expected,current): 檢查expected與current的值是否相等,拋出異常,顯示expected和current的值
             CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,current): 檢查expected的值與actual的值是否相等,拋出異常,顯示expected,current的值,并顯示預(yù)先設(shè)定的信息
             CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,current,delta): 檢查expected, current之差是否小于delta,如果不小于,顯示expected和current的值

            下面講一下loadTest編碼的編碼構(gòu)想:首先需要一個外部文件,其中存儲這一個DATA型數(shù)據(jù),文件的創(chuàng)建方式并不重要,關(guān)鍵是要保證里面的數(shù)據(jù)的正確性。然后,要進行的操作是檢查load函數(shù)從外部文件中讀出的數(shù)據(jù)和實現(xiàn)存在其中的數(shù)據(jù)是否一致。代碼如下:
            //
            // 前提:外部文件中已存儲了正確的數(shù)據(jù)。
            //
            #define AUX_FILENAME    "ok_data.dat"
            #define FILE_NUMBER    19
            #define FILE_STRING    "this is correct text stored in auxiliar file"

            void DiskDataTestCase::loadTest()
            {
                // 相對路徑轉(zhuǎn)化為絕對路徑
                TCHAR    absoluteFilename[MAX_PATH];
                DWORD    size = MAX_PATH;

                strcpy(absoluteFilename, AUX_FILENAME);
                CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &size) );

                // 執(zhí)行操作
                CPPUNIT_ASSERT( fixture->load(absoluteFilename) );

                // 通過assertion檢查運行結(jié)果
                LPDATA    loadedData = fixture->getData();

                CPPUNIT_ASSERT(loadedData != NULL);
                CPPUNIT_ASSERT_EQUAL(FILE_NUMBER, loadedData->number);
                CPPUNIT_ASSERT( 0 == strcmp(FILE_STRING,
                        fixture->getData()->string) );
            }

            通過這樣一個簡單的Test Case測試了4個可能存在的錯誤:
             load函數(shù)返回值
             getData函數(shù)返回值
             number結(jié)構(gòu)的成員值
             string結(jié)構(gòu)的成員值
             
            storeTest要復(fù)雜一些,因為需要把fixture中的數(shù)據(jù)存儲到臨時文件中,之后打開兩個文件(新的臨時文件和外部文件),讀出數(shù)據(jù)并比照內(nèi)容。代碼如下:

            void DiskDataTestCase::storeTest()
            {
                DATA    d;
                DWORD   tmpSize, auxSize;
                BYTE    *tmpBuff, *auxBuff;
                TCHAR   absoluteFilename[MAX_PATH];
                DWORD   size = MAX_PATH;

                // 填充結(jié)構(gòu)體
                d.number = FILE_NUMBER;
                strcpy(d.string, FILE_STRING);

                // 相對路徑轉(zhuǎn)化為絕對路徑

                strcpy(absoluteFilename, AUX_FILENAME);
                CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &size) );

                // 執(zhí)行操作
                fixture->setData(&d);
                CPPUNIT_ASSERT( fixture->store("data.tmp") );

                // 讀出兩文件的內(nèi)容并對比
                // ReadAllFileInMemory 是一個分配緩沖區(qū)的外部函數(shù)
                // 把文件內(nèi)容存入其中. 調(diào)用函數(shù)負責(zé)釋放緩沖區(qū).
                tmpSize = ReadAllFileInMemory("data.tmp", tmpBuff);
                auxSize = ReadAllFileInMemory(absoluteFilename, auxBuff);

                // 文件不存在則拋出異常
                CPPUNIT_ASSERT_MESSAGE("New file doesn't exists?", tmpSize > 0);
                CPPUNIT_ASSERT_MESSAGE("Aux file doesn't exists?", auxSize > 0);

                // 文件大小可獲得,否則拋出異常
                CPPUNIT_ASSERT(tmpSize != 0xFFFFFFFF);
                CPPUNIT_ASSERT(auxSize != 0xFFFFFFFF);

                // 緩沖區(qū)必須可用,否則拋出異常
                CPPUNIT_ASSERT(tmpBuff != NULL);
                CPPUNIT_ASSERT(auxBuff != NULL);

                // 兩個文件的大小必須和DATA一致
                CPPUNIT_ASSERT_EQUAL((DWORD) sizeof(DATA), tmpSize);
                CPPUNIT_ASSERT_EQUAL(auxSize, tmpSize);

                // 兩文件的內(nèi)容必須一致
                CPPUNIT_ASSERT( 0 == memcmp(tmpBuff, auxBuff, sizeof(DATA)) );

                delete [] tmpBuff;
                delete [] auxBuff;

                ::DeleteFile("data.tmp");
            }

            啟動用戶界面
            最后,看看如何顯示基于MFC的用戶界面對話框(事先在其內(nèi)部編譯了TestRunner.dll)。

            打開實現(xiàn)類的文件(ProjectNameApp.cpp),把下列代碼復(fù)制到InitInstance方法中:
            #include <cppunit/ui/mfc/TestRunner.h>
            #include <cppunit/extensions/TestFactoryRegistry.h>

            BOOL CMy_TestsApp::InitInstance()
            {
                ....

                // 聲明Test Runner,用以注冊的測試填入其中,并運行
                CppUnit::MfcUi::TestRunner runner;

                runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() );

                runner.run();   

                return TRUE;
            }
             
            很簡單,不是嗎?只需要定義一個"runner"實例,添加注冊過的test(test是通過CPP文件中的CPPUNIT_TEST_SUITE_REGISTRATION宏注冊的),就可以運行run函數(shù)了。

            編譯、運行,開始你的單元測試吧:)

            posted on 2007-05-26 10:53 創(chuàng)建更好的解決方案 閱讀(2810) 評論(4)  編輯 收藏 引用 所屬分類: CPPUnit專欄

            評論:
            # re: 用CPPUnit做單元測試(原文在E文全翻中) 2007-09-03 17:28 | seu
            不錯  回復(fù)  更多評論
              
            # re: 用CPPUnit做單元測試(原文在E文全翻中) 2007-12-24 08:42 | FongLuo
            有時間一定要用用。  回復(fù)  更多評論
              
            # re: 用CPPUnit做單元測試(原文在E文全翻中) 2008-10-15 17:16 | 浪跡天涯
            這和自己寫測試代碼有什么最大的好處???
            剛學(xué)cppunit,不是很明白,感覺還不如自己寫測試代碼來的快!  回復(fù)  更多評論
              
            # re: 用CPPUnit做單元測試(原文在E文全翻中) 2008-10-22 11:36 | 陳勇
            謝謝啦,很有用!!!!!!!!!!!!  回復(fù)  更多評論
              
            日本欧美久久久久免费播放网| 国产精品久久新婚兰兰 | 国内精品伊人久久久久AV影院| 久久精品国产一区| 久久久久久人妻无码| 久久受www免费人成_看片中文| 91精品国产高清久久久久久io| 久久婷婷午色综合夜啪| 色偷偷久久一区二区三区| 91久久精品国产成人久久| 国产精品久久久久AV福利动漫| 国产福利电影一区二区三区久久久久成人精品综合 | 久久精品一区二区影院| 久久亚洲欧美日本精品| 久久综合九色综合97_久久久| 精品久久久久国产免费 | 亚洲乱亚洲乱淫久久| 久久久久久精品无码人妻| 97精品伊人久久大香线蕉| 久久精品国产69国产精品亚洲 | 久久久无码精品亚洲日韩蜜臀浪潮 | 人人狠狠综合久久亚洲88| 狠狠综合久久综合88亚洲| 亚洲中文久久精品无码ww16| segui久久国产精品| 国内精品伊人久久久久AV影院| 欧美亚洲国产精品久久高清 | 欧美午夜A∨大片久久 | 久久福利片| 久久久久99精品成人片| 热久久国产精品| 秋霞久久国产精品电影院| 嫩草影院久久99| 久久夜色tv网站| 国产亚洲色婷婷久久99精品91| 亚洲午夜久久久精品影院| 国产成人精品久久亚洲高清不卡 | 精品精品国产自在久久高清| 99久久免费国产特黄| 国产精品欧美久久久天天影视| 国内精品久久久久影院优|