在QA中,主要有兩種測試
單元測試:驗證我們系統中的所有邏輯單元的驗證行為(并不考慮其他單元的相互關系,比如其他的可以打成樁函數等。)
系統測試(集成測試)各個單元之間的相互關系,檢測系統運行行為。
單元測試用例設計
在開發過程中,程序員通常用調試器來測試他們的程序,但是很少有人去單步調試程序,不會檢測每個可能的變量值,這樣我們就要借助一些工具來完成。就是我們所說的“單元測試框架”來測試我們的程序。
我們來測試一個簡單的c程序
BOOL addition(int a, int b)
{
return (a + b);
}
我們的用例必須借助其他的c函數來完成驗證所有的可能性,返回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);
}
我們看到,測試所有的可能性需要
正數+負數, 0+0, 負數+0, 正數+0,正數+正數,負數+正數,負數+負數
每個cases比較了加的結果和期望值,如果不通過就False,如果都通過就返回True
行為上可以設計下面的例子:
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(1, 2), 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);
}
但是這樣當代碼變化時用例就得跟著相應的變化,或者去加一個新的case
XP(極限編程)推薦就是在編寫代碼之前先寫測試用例。就是測試驅動開發。
CPPUnit
CPPUnit
各Case應該被寫在類里面從TestCase 導出。這個類對我們所有基本功能進行測試, 在Test Suite(測試用例集合)登記等等
例如, 我們寫了一個功能在磁盤存放一些數據的小模塊。 這個模塊(類名DiskData) 有主要二功能: 裝載和保存數據到文件里面:
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;
};
現在, 什么編碼方式并不重要, 因為最重要事是我們必須肯定它必須做, 是這個類應該做: 正確地裝載和存放數據到文件。
為了做這個驗證,我們去創造一個新的測試集,包括二個測試用例: 一個裝載數據和另為存儲數據。
使用 CPPUnit
你能在這里http://cppunit.sourceforge.net/得到最新的CPPUnit 版本, 你能發現所有的庫 , 文獻, 例子和其它有趣的材料。(我下載了版本為1.8.0 并且這個頒布工作良好)
在Win32里, 你能在VC++ 之下(6.0 和以后版本)使用CPPUnit , 但是當CPPUnit 使用ANSI C++, 有少量接口時針對其它環境象C++Builder。
在CPPUnit發布版本里面,所有建造庫的步驟和信息,可以在INSTALL-WIN32.txt文件找到,。當所有二進制文件被構建之后, 你就能寫你自己的測試集了。
想在VC中寫自己的測試程序,可以按照以下步驟:
建立一個MFC的對話框(或文檔視圖結構)
允許時間類型信息,Alt+F7 --> C/C++ --> C++ language --> Enable RTTI
把Cppunit\inlude放到include目錄:Tools - Options - Directories - Include.
用cppunitd.lib (靜態連接) 或者cppunitd_dll.lib (動態鏈接),testrunnerd.lib來鏈接你的程序。
如果動態鏈接,就要把testrunnerd.dll 拷到應用程序目錄來運行。
Ok,看一下測試用例的類的定義吧。
#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>
#include <cppunit/extensions/HelperMacros.h>
#include "DiskData.h"
class DiskDataTestCase : public CppUnit::TestCase
{
CPPUNIT_TEST_SUITE(DiskDataTestCase);
CPPUNIT_TEST(loadTest);
CPPUNIT_TEST(storeTest);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
protected:
void loadTest();
void storeTest();
private:
DiskData *fixture;
};
#endif
首先, 必須包含TestCase.h和HelperMacros.h. 第一步,我們的從我們的Testcase基類配生的新類。第二,用一些宏使我們的定義的更方便,如 CPPUNIT_TEST_SUITE (開始測試定義), CPPUNIT_TEST (定義一個測試用例) 或 CPPUNIT_TEST_SUITE_END (結束一個測試集).
我們的類(DiskDataTestCase)有重載了兩個方法setUp()和tearDown(). 一個開始,一個結束測試。
測試過程如下
啟動程序
點擊“Run”
調用Call setUp()方法: 構建我們的測試對象fixture
調用第一個測試方法
調用tearDown() 方法,清除對象
調用Call setUp()方法: 構建我們的測試對象fixture
調用第一個測試方法
調用Call setUp()方法: 構建我們的測試對象fixture
...
就像下面的形式:
#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
}
編寫測試用例
一旦我們知道我們要測什么之后,我們就可以寫測試用例了。我們能夠執行所有的我們需要的操作:使用普通庫函數,第三方庫,win32api庫函數,或簡單使用c++內部操作
有時候,我們需要調用外部輔助文件或者數據庫,比較外部文件和內部數據是否一致。
每發現一個錯誤時9比如發現內部數據和外部數據不同我們就創建一個異常,使用 CPPUNIT_FAIL(message) 來顯示異常信息。
檢測一個條件就使用
CPPUNIT_ASSERT(condition):如果為false就拋出異常
CPPUNIT_ASSERT_MESSAGE(message, condition): 如果為false就拋出制定的信息。
CPPUNIT_ASSERT_EQUAL(expected,current): 檢測期望值
CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,current): 當比較值不相等時候拋出的制定的信息。
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,current,delta): 帶精度的比較
下面是測試loadTest的例子,
//
// These are correct values stored in auxiliar file
//
#define AUX_FILENAME "ok_data.dat"
#define FILE_NUMBER 19
#define FILE_STRING "this is correct text stored in auxiliar file"
void DiskDataTestCase::loadTest()
{
// convert from relative to absolute path
TCHAR absoluteFilename[MAX_PATH];
DWORD size = MAX_PATH;
strcpy(absoluteFilename, AUX_FILENAME);
CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &size) );
// executes action
CPPUNIT_ASSERT( fixture->load(absoluteFilename) );
// ...and check results with assertions
LPDATA loadedData = fixture->getData();
CPPUNIT_ASSERT(loadedData != NULL);
CPPUNIT_ASSERT_EQUAL(FILE_NUMBER, loadedData->number);
CPPUNIT_ASSERT( 0 == strcmp(FILE_STRING,
fixture->getData()->string) );
}
在這個case我們得到四個可能的錯誤:
load method's return value
getData method's return value
number structure member's value
string structure member's value
第二個用例也是相似的。但是困難點,我們需要使用已知的數據來填充fixture,把它存在磁盤臨時文件里,然后打開兩個文件(新的和輔助文件),讀并比較內容,兩者如一致就正確
void DiskDataTestCase::storeTest()
{
DATA d;
DWORD tmpSize, auxSize;
BYTE *tmpBuff, *auxBuff;
TCHAR absoluteFilename[MAX_PATH];
DWORD size = MAX_PATH;
// configures structure with known data
d.number = FILE_NUMBER;
strcpy(d.string, FILE_STRING);
// convert from relative to absolute path
strcpy(absoluteFilename, AUX_FILENAME);
CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &size) );
// executes action
fixture->setData(&d);
CPPUNIT_ASSERT( fixture->store("data.tmp") );
// Read both files contents and check results
// ReadAllFileInMemory is an auxiliar function which allocates a buffer
// and save all file content inside it. Caller should release the buffer.
tmpSize = ReadAllFileInMemory("data.tmp", tmpBuff);
auxSize = ReadAllFileInMemory(absoluteFilename, auxBuff);
// files must exist
CPPUNIT_ASSERT_MESSAGE("New file doesn't exists?", tmpSize > 0);
CPPUNIT_ASSERT_MESSAGE("Aux file doesn't exists?", auxSize > 0);
// sizes must be valid
CPPUNIT_ASSERT(tmpSize != 0xFFFFFFFF);
CPPUNIT_ASSERT(auxSize != 0xFFFFFFFF);
// buffers must be valid
CPPUNIT_ASSERT(tmpBuff != NULL);
CPPUNIT_ASSERT(auxBuff != NULL);
// both file's sizes must be the same as DATA's size
CPPUNIT_ASSERT_EQUAL((DWORD) sizeof(DATA), tmpSize);
CPPUNIT_ASSERT_EQUAL(auxSize, tmpSize);
// both files content must be the same
CPPUNIT_ASSERT( 0 == memcmp(tmpBuff, auxBuff, sizeof(DATA)) );
delete [] tmpBuff;
delete [] auxBuff;
::DeleteFile("data.tmp");
}
調用用戶接口
最后,我們看看用一個mfc 對話框(TestRunner.dll)用來說明。
我們需要在我們的初始化函數中做如下初始化
#include <cppunit/ui/mfc/TestRunner.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
BOOL CMy_TestsApp::InitInstance()
{
....
// declare a test runner, fill it with our registered tests and run them
CppUnit::MfcUi::TestRunner runner;
runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() );
runner.run();
return TRUE;
}
只要定義一個test的實例,然后注冊所有用例,在跑case。
延伸閱讀
文章來源于領測軟件測試網 http://www.kjueaiud.com/