這是第一部分,主要針對C和C++項目的(包括了Windows環境和Linux');" href="javascript:;" target=_self>Linux環境),下部分將針對Java及J2EE項目。
1. 目的
為了減少代碼中的錯誤數量, 減少調試所花的時間和精力, 改善軟件質量, 減少開發和維護的時間和成本。
2. 適用范圍
適用于C及C++的所有產品。
3. 適用內容
3.1 C++標準
3.1.1測試環境使用Visual C++,Windows窗口應用程序
3.1.1.1前題:使用CppUnit1.6.2版,解壓后,路徑為x:\\cppunit-1.6.2;
在工程文件中配置測試框架使用環境:加入執行頭文件的路徑x:\\cppunit-1.6.2\include,加入導入庫文件的路徑x:\\cppunit-1.6.2\lib;
配置DEBUG(測試)版環境:
加入需要鏈接的靜態測試框模塊testrunnercd.lib(運行測試用例的選擇對話框)和cppunitcd.lib(測試框架);
加入測試Add-ins,庫名為x:\\cppunit-1.6.2\lib\TestRunnerDSPlugInD.dll;
在Project Settings/C++/C++ Language中啟用RTTI;
3.1.1.2建立測試用例:
1、以類名加前輟“Test”命名測試單元文件名,比如“CMabString”類的類文件名為MabString.cpp,則測試單元文件命名為TestMabString.cpp;
2、加入測試框架頭文件以及要測試的單元頭文件,以TestMabString為例:
頭文件:testmabstring.h
#ifndef CPP_UNIT_TestNode_H #define CPP_UNIT_TestNode_H //包含測試框架的頭文件 #include <cppunit/TestCase.h> #include <cppunit/extensions/HelperMacros.h> //包含被測試單元的頭文件 #include "mabstring.h" //派生測試框架的測試用例類 class TestMabString : public CppUnit::TestCase { //定義測試用例列表,此列表將出現在運行測試用例的選擇對話框中 CPPUNIT_TEST_SUITE( TestMabString ); CPPUNIT_TEST( FindByName ); CPPUNIT_TEST_SUITE_END(); protected: // CMabString m_MabStr; public: //用例初始化,可作為樁函數 void setUp (); //用例析構 void tearDown(); protected: //測試用例 void FindByName (void); }; #endif 類文件:testmabstring.cpp #include "TestMabString.h" #include "iostream.h" #include "strstrea.h" //注冊本測試單元 CPPUNIT_TEST_SUITE_REGISTRATION( TestMabString ); //定義測試用例 void TestMabString::FindByName () { //功能性測試,屬黑盒測試 //normal test //條件及錯誤測試,屬白盒測試 //extra test, //例外測試,屬白盒測試 //exception test, bool bRet=false; try{ //put the exception code here... } //catch(CXXX& e) catch(...) { bRet=true; } CPPUNIT_ASSERT(bRet); //由于并不能夠執行所有單元測試應該執行的路徑,比如CMabString是從CString //類中派生出來的,而可能CMabString中的Find只簡單調用了CString中的Find方法,//所以并不需要測試; //在此處說明所有不用測試的路徑; //other test, see the ... } void TestMabString::setUp () { //開始測試前的初始代碼 m_pNode=new Node(); } void TestMabString::tearDown() { //測試結束代碼 if(m_pNode) delete m_pNode; } |
3、在啟動程序中加入以下代碼,以便運行“測試用例選擇”對話框:
#ifdef _DEBUG //包括測試頭文件 #include <msvc6/testrunner/TestRunner.h> #include <cppunit/extensions/TestFactoryRegistry.h> static AFX_EXTENSION_MODULE extTestRunner; #endif //以下為測試代碼,此部分測試不會出現在發布版中 #ifdef _DEBUG TestRunner runner; runner.addTest ( CppUnit::TestFactoryRegistry::getRegistry().makeTest() ); runner.run (); #endif |
4、制作發行版
發行版需要做以下工作:
將Project的屬性設置為Release(這將自動去除_DEBUG的聲明);
從工程項目中去掉測試文件(即帶有test前輟的文件);
3.1.2測試環境使用Visual C++,Windows非窗口應用程序
3.1.2.1前題:使用CppUnit1.6.2版,解壓后,路徑為x:\\cppunit-1.6.2;
在工程文件中配置測試框架使用環境:加入執行頭文件的路徑x:\\cppunit-1.6.2\include,加入導入庫文件的路徑x:\\cppunit-1.6.2\lib;
配置DEBUG(測試)版環境:
加入需要鏈接的靜態測試框模塊cppunitcd.lib(測試框架);
在Project Settings/C++/C++ Language中啟用RTTI;
3.1.2.2建立測試用例:
1、以類名加前輟“Test”命名測試單元文件名,比如“CMabString”類的類文件名為MabString.cpp,則測試單元文件命名為TestMabString.cpp;
2、加入測試框架頭文件以及要測試的單元頭文件,以TestMabString為例:
頭文件:testmabstring.h
3、測試示例同上;
3.2 C標準
3.2.1測試環境使用gcc,Linux非窗口應用程序
前題:使用check0.8.0版,解壓后,路徑為/xx/check-0.8.0;
配置測試框架使用環境(我建議采用標準組織推薦的使用Autoconf和Automake來生成配置文件configure和Makefile,因為使用它們可以建立符合國際標準的configure腳本 和Makefile文件,并且可以有效的建立壓縮包和方便分發必需的文件(也方便在發行版中去除測試用例文件):
l 首先需編寫configure.in文件,此文件用于Autoconf生成configure可執行腳本;configure.in的框架大致如下:
dnl 此文件用于生成configure腳本,
dnl AC_INIT的xxxx.h參數代表本目錄下一個有效的文件名
AC_INIT(xxxx.h)
dnl AM_INIT_AUTOMAKE的兩個參數分別是生成應用程序的版本及版本號,
dnl 可能有些版本的Autoconf和Automake不支持此宏
AM_INIT_AUTOMAKE(xxxx, x.x)
dnl 以下為編譯依賴的檢測
dnl Checks for programs.
AC_PROG_AWK
AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_LN_S
dnl Checks for libraries.
AC_CHECK_LIB(check,suite_create)
dnl Checks for header files.
AM_CONFIG_HEADER(config.h)
dnl Checks for typedefs, structures, and compiler characteristics.
dnl Checks for library functions.
dnl 將Automake生成的Makefile.in文件輸出為Makefile文件
AC_OUTPUT(Makefile)
(提示:autoscan可以生成configure.in文件的基本框架,但很基本,可其生成的configure.scan文件的基礎補充,然后更名為configure.in)
l 編寫Makefile.am文件,用于Automake生成Makefile.in文件,Makefile.am文件的大致框架如下:(其中xxxx為應用程序文件名,比如program.c文件的測試程序文件名我建議為check_program.c;)
TESTS = check_xxxx noinst_PROGRAMS=check_xxxx frame_path=xx/check-0.8.0 xxxx_docs =\ srcfilelist_1\ srcfilelist_2\ .......\ ..... xxxx_SOURCES=\ srcfilelist_1\ srcfilelist_2\ ....... EXTRA_DIST = $(xxxx_docs) INCLUDES = -I$(frame_path)/src -I$(other_path)/include LDADD= \$(frame_path)/src/libcheck.a CLEANFILES=*.*~ |
。∕akefile.am有很許多標記,可以參閱相應文檔。但常用的如:noinst_PROGRAMS為生成的可執行文件,xxxx_SOURCES(應用程序名加后輟_SOURCES)為源文件列表,EXTRA_DIST為發布程序時不需要的文件列表(用此方法可以將測試文件去掉),INCLUDES為要包含的頭文件路徑,check的頭文件位置在其安裝目錄下的src中;LDADD為要鏈接的庫文件名,libcheck.a為check測試框架的庫文件;)
使用Automake –a –-foreign來生成Makefile.in文件,--foreign是為了生成幾個外部文件如install.sh等,如果已有這些文件則可以省略這個參數;
使用Autoconf來生成configure執行腳本;然后執行./configure來生成Makefile文件;
執行make來生成可執行程序;
3.2.2 建立測試用例:
1、以程序文件名加前輟“check_”命名測試單元文件名,比如money.c文件的測試單元文件命名為check_money.c;
2、加入測試框架頭文件以及要測試的單元頭文件,以check_money為例:
頭文件:money.h;源文件:money.c;測試單元文件:check_money.c:
測試文件框架如下:
#include <stdlib.h> #include <check.h> #include "money.h" /*建立必要的測試變量,Money為money.h中定義的結構struct money*/ Money *five_dollars; /*單元測試初始化函數*/ void setup (void) { five_dollars = money_create(5, "USD"); } /*單元測試結束函數*/ void teardown (void) { money_free (five_dollars); } /*單元測試用例,用例名為test_create*/ /*test functions: money_amout()*/ START_TEST(test_create) { /*功能性測試,屬黑盒測試*/ /*normal test*/ fail_unless (money_amount(five_dollars) = = 5, "Amount not set correctly on creation"); fail_unless (strcmp(money_currency(five_dollars),"USD") = = 0, "Currency not set correctly on creation"); /*條件及錯誤路徑測試,屬白盒測試*/ /*extra test*/ } END_TEST /*單元測試用例,用例名為test_net_create*/ START_TEST(test_neg_create) { Money *m = money_create(-1, "USD"); fail_unless (m = = NULL, "NULL should be returned on attempt to create with a negative amount"); } END_TEST /*單元測試用例,用例名為test_net_create*/ START_TEST(test_zero_create) { Money *m = money_create(0, "USD"); fail_unless (money_amount(m) = = 0, "Zero is a valid amount of money"); } END_TEST /*單元測試組裝,將所有單元測試組裝到一個“箱子”里面,“箱子”名為Money*/ Suite *money_suite (void) { Suite *s = suite_create("Money"); /*測試用例分組*/ TCase *tc_core = tcase_create("Core"); TCase *tc_limits = tcase_create("Limits"); /*將分組加入“箱子” suite_add_tcase (s, tc_core); suite_add_tcase (s, tc_limits); /*分別將不同用例加入分組*/ tcase_add_test (tc_core, test_create); tcase_add_checked_fixture (tc_core, setup, teardown); /*此用例注冊初始化和結束函數*/ /*以下用例將不注冊初始化和結束函數*/ tcase_add_test (tc_limits, test_neg_create); tcase_add_test (tc_limits, test_zero_create); return s; } /*執行測試用例*/ int main (void) { int nf; Suite *s = money_suite(); SRunner *sr = srunner_create(s); srunner_run_all (sr, CK_NORMAL); nf = srunner_ntests_failed(sr); srunner_free(sr); suite_free(s); return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } |
3.2.3 制作發行版:
制作發行版只須配置另外一份Makefile.am,在此文件中的源文件列表加入執行主體,即應用程序包含main函數的文件;也可在制作測試版的Makefile.am中加入發行版的配置,這樣就可以直接生成測試版程序和發行版程序。
3.3 JAVA標準
3.3.1測試環境使用JSDK1.3
3.3.1.1前題:
使用JUnit3.7版,解壓后,路徑為x:\\junit3.7;
使用Ant1.3版,解壓后,路徑為x:\\ant1.3;
配置測試框架使用環境:
測試框架使用環境可以在build.xml文件中配置,此文件是ant的輸入文件,ant根據其來生成和執行應用程序;(這相當于C/C++的Makefile文件,但比之功能要強大、且配置更為簡單);
1.x:\\ant1.3\bin目錄下含有ant執行程序的批處理文件,所以要將此目錄加入到系統環境變量path中;
2.建立項目文件目錄結構;我建議在進行項目開發能有一個完整的目錄結構,如下所示:
“AntCons”為項目目錄名稱,可以替換為所需要的項目名稱;在項目目錄下有兩個子目錄分別為build和src,這兩個子目錄分別為源文件目錄和發布文件目錄;而src(源文件目錄)又分為main(源文件)和test(測試文件)目錄。而com.myjava則是包名,對應于源java文件中的package;
3.在項目目錄中建立build.xml文件(未涉及測試的),文件大致結構如下所示:
定義項目名稱及位置
首先聲明一些變量,便于使用
|
定義目標:編譯;
在此目標中將建立發布目錄、編譯目標java源文件并且指定class路徑;
classpath="${java.class.path};${extclasslib}"> |
定義目標:運行;
此目標依賴于編譯目標;
|
(build.xml相關標記的說明可以在ant下載包的說明文檔中找到,ant定義了很多方便操作的指令)
4.編寫代碼文件,我們假設代碼文件位于com.myjava包中,則代碼文件將放置于src/main/com/myjava目錄下;
5.編寫單元測試文件,依照上面的項目目錄設置,則單元測試文件放置于src/test下;
3.3.1.2建立測試用例:
1、以類名加前輟“Test”命名測試單元文件名,比如“Sample”類的類文件名為Sample.java,則測試單元文件命名為TestSample.java;
2、編寫測試用例類,在測試用例類中引入測試框架包以及要測試的包聲明,以TestSample為例:
測試文件TestSample.java:
package test.Sample; //測試包聲明 import junit.framework.*; //引入測試框架包 import xxxx; //引入被測試單元包 //派生測試框架的測試用例類 public class TestSimple extends TestCase { protected int fValue1; protected int fValue2; public TestSimple(String name) { super(name); } //用例初始化,可作為樁函數 protected void setUp() { fValue1= 2; fValue2= 3; } //用例析構 protected void tearDown(){ } //組裝測試用例 public static Test suite() { /* * the type safe way * //增加測試用例到“箱子” TestSuite suite= new TestSuite(); suite.addTest( new TestSimple("add") { protected void runTest() { testAdd(); } } ); suite.addTest( new TestSimple("testDivideByZero") { protected void runTest() { testDivideByZero(); } } ); return suite; */ /* * the dynamic way */ return new TestSuite(TestSimple.class); } //用例1: public void testAdd() { //normal test, 功能測試,屬黑盒測試 double result= fValue1 + fValue2; assertTrue(result == 6); //extra test, 條件及錯誤路徑測試,屬白盒測試 //except test, 異常測試,屬白盒測試 //other test, see the functions "...", //其它測試,比如被測試類的基類方法,在此需要做出說明 } //用例2: public void testDivideByZero() { //................ } //用例3: public void testEquals() { //................. } } |
3. 編寫驅動測試用例類,將各個測試用例組裝到一起進行測試:
package test.Sample; //測試包聲明 import junit.framework.*; //引入測試框架包 import xxxx; //引入被測試單元包 /** * TestSuite that runs all the sample tests * */ public class AllTests { public static void main (String[] args) { junit.textui.TestRunner.run (suite()); } public static Test suite ( ) { //建立測試用例的“箱子” TestSuite suite= new TestSuite("All Tests"); suite.addTest(TestSimple.suite()); //此處增加一些其它單元測試類 //...... suite.addTest(junit.tests.AllTests.suite()); return suite; } } |
4. 將測試單元的編譯及運行加入到build.xml中;
在build.xml的project標記加入兩個目標,現在的build.xml如下所示:
定義項目名稱及位置
首先聲明一些變量,便于使用
|
定義目標:依賴庫檢測;
|
定義目標:編譯;
在此目標中將建立發布目錄、編譯目標java源文件并且指定class路徑;
classpath="${java.class.path};${extclasslib}"> |
定義目標:打包;
在此目標中將建立分發包目錄、將編譯完成的.class文件打包為jar文件;
basedir="${builddir}" includes="com/**"/> |
定義目標:編譯測試用例;
|
定義目標:運行應用程序;
此目標依賴于編譯目標;
|
定義目標:運行測試用例;
在此目標中將運行測試用例,運行在窗口中進行還是在控制臺進行取決于所用classname,"junit.textui.TestRunner"表示在控制臺進行,如果改為"junit.ui.TestRunner"則可以在窗口中運行
taskname="junit" failonerror="true"> |
5. 最后,在項目所在目錄執行Ant target_name,比如:運行測試用例可以使用Ant runtests;
3.3.2 使用Junit測試EJB
3.3.2.1安裝JUnit和JunitEE
你需要將junit.jar和junitee.jar加入到J2EE應用程序的CLASSPATH中。一般地,將jar文件拷貝到服務器的根目錄下的lib目錄中就可以了。注意:該目錄下的文件一般不能被動態加入,所以你需要重新啟動應用服務器。
3.3.2.2準備一個EJB例子
你可以在JunitEE安裝目錄的example子目錄下找到一個作為示例的EJB。你要先組織該示例,保證它是可以工作的。否則,注意發生了什么事情。你要編輯build.xml文件按照你計算機的具體情況更新文件中的路徑。然后在build文件所在的目錄下運行“ant”。
《something》
運行該例子的結果是得到名為junitee-example.ear的文件。該文件在名為out的子目錄中。它包含一個簡單的無狀態EJB,一個使用了EJB的應用程序和測試web應用程序。這是一個標準的J2EE企業級的文檔,可以安裝在任何的應用服務器上。
如果你想手工的寫出所有的部分,就刪除example目錄下的components/test-war子目錄,本文下面的部分就是說明如何構建測試。
3.3.2.3創建目錄結構
要創建一個Web應用程序測試,首先你要創建目錄結構。創建下面的子目錄:
example/javasrc/org/infohazard/test/
example/components/test-war/
example/components/test-war/WEB-INF/
本示例將測試用例放置在org.infohazard.test包中。如果你想測試包內的私有方法,則要將測試用例和要測試的class文件放置在同一個包中。由于絕大多數的測試發生在public接口層,所以你要考慮是不是要這樣做。
3.3.2.4編寫測試用例
測試用例是標準的JUnit測試用例。對于fixture,你可以使用缺省的JNDI InitialContext來得到EJB的索引,如下:
protected void setUp() throws Exception { Context jndiContext = new InitialContext(); Object einRef = jndiContext.lookup("java:comp/env/ejb/EinsteinEJB"); EinsteinHome home = (EinsteinHome)PortableRemoteObject.narrow(einRef, EinsteinHome.class); this.ein = home.create(); } |
測試方法類似于:
public void testSimpleAddition() throws RemoteException { String result = this.ein.addTwoNumbers("7", "10"); assert(result.equals("17")); } |
這些代碼屬于example/javasrc/org/infoazard/test/EinsteinTest.java
3.3.2.5使用 servlet來運行測試用例
下面是一個包括在junit.htmlui包中的servlet,但是你不可以直接使用它。你必須在你的web應用程序的WEB-INF/classes/下創建一個servlet,這個servlet來源于junit.htmlui.TestServletBase并且增加了下面的細節:
protected ClassLoader getDynamicClassLoader() { return this.getClass().getClassLoader(); } |
使用這個servlet,我們使用動態的類載入器欺騙應用服務器,這樣不必使用缺省的類載入。如果我們不這樣做,每次改變的測試類文件都要重新啟動web應用服務器。
如果你的應用程序服務器不能動態地重新載入修改過的class文件,這一步對你來說是沒有幫助的。你不得不重新啟動服務器,因為TestServletBase被聲明為abstract類型。
3.3.2.6創建UI表格
<p> You may type in the name of a test suite: <br/> <form action="TestServlet" method="get" name="youTypeItForm"> <input type="text" name="suite" size=60 /> <input type="submit" value="Run" /> </form> </p> <hr/> <p> You may pick one or more of the following test suites: <br/> <form action="TestServlet" method="get" name="youPickItForm"> <select name="suite" size="2" multiple> <option value="org.infohazard.test.EinsteinTest"> org.infohazard.test.EinsteinTest </option> <option value="some.other.Test"> some.other.Test </option> </select> <input type="submit" value="Run" /> </form> </p> |
將此保存為example/components/test-war/index.html文件。
3.3.2.7 創建web.xml配置描述器
Web應用程序必須有一個配置描述器,它提供了ejb-ref映射,使得"java:comp/env/ejb/EinsteinEJB" JNDI lookup生效,and so that the TestServlet gets mapped to some sort of URI.下面是web.xml的示例:
<?xml version="1.0"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> <web-app> <display-name> Einstein Unit Tester Web Application </display-name> <servlet> <servlet-name>JUnitEETestServlet</servlet-name> <description>JUnitEE test framework</description> <servlet-class>org.infohazard.servlet.TestServlet</servlet-class> </servlet> <ejb-ref> <ejb-ref-name>ejb/EinsteinEJB</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>org.infohazard.ejb.einstein.EinsteinHome</home> <remote>org.infohazard.ejb.einstein.Einstein</remote> </ejb-ref> <servlet-mapping> <servlet-name>JUnitEETestServlet</servlet-name> <url-pattern>/TestServlet</url-pattern> </servlet-mapping> </web-app> |
保存成:example/components/test-war/WEB-INF/web.xml。
3.4 單元測試完畢后,程序員將測試用例交與項目經理,由項目經理進行單元測試的檢查,完成單元測試報告,對測試的情況進行總結說明。
注意:以上所述僅是測試J2EE的一種方式而已,我們還可以結合HttpUnit和Jakarta Cataus來進行J2EE項目的單元測試。這些完全借助于工具的方式還是有一定的缺陷,所以在去年的時候我根據XP中的Mock Object理論的一些指導,在HttpUnit的基礎上編寫了一套完全脫離J2EE運行環境(比如Weblogic等)的單元測試套件,以及壓力測試和功能測試套件,在這一組工具的支持下才算作好了J2EE項目的單元測試。
文章來源于領測軟件測試網 http://www.kjueaiud.com/