運(yùn)行測(cè)試,發(fā)現(xiàn)兩個(gè)錯(cuò)誤:
1) test: testFileModifyDateBasic (F) line: 140 c:unittestunittest.cpp
assertion failed
- Unexpected exception caught
2) test: testFileModifyDateEqual (F) line: 150 c:unittestunittest.cpp
assertion failed
- Unexpected exception caught
調(diào)試發(fā)現(xiàn),原來我的setFileModifyDate中,文件的打開方式為GENERIC_READ,只有讀權(quán)限,自然不能寫。把這個(gè)替換為 GENERIC_READ | GENERIC_WRITE,再運(yùn)行,一切OK!
其實(shí)上面的測(cè)試以及實(shí)現(xiàn)代碼還有一些問題,譬如說,測(cè)試用例分得還不夠細(xì),有些測(cè)試可以繼續(xù)細(xì)分為幾個(gè)函數(shù),這樣一旦遇到測(cè)試錯(cuò)誤,你可以很精確的知道錯(cuò)誤的位置(因?yàn)閽伋霎惓ee(cuò)誤是不能知道行數(shù)的)。不過用來說明怎樣進(jìn)行測(cè)試驅(qū)動(dòng)開發(fā)應(yīng)該是足夠了。
VI. 測(cè)試集
CPPUNIT_NS::TestCaller testCase1( "testCtorAndGetName", MyTestCase::testCtorAndGetName );
CPPUNIT_NS::TestCaller testCase2( "testGetFileSize", MyTestCase::testGetFileSize );
CPPUNIT_NS::TestCaller testCase3( "testFileExist", MyTestCase::testFileExist );
CPPUNIT_NS::TestCaller testCase4( "testFileModifyDateBasic", MyTestCase::testFileModifyDateBasic );
CPPUNIT_NS::TestCaller testCase5( "testFileModifyDateEqual", MyTestCase::testFileModifyDateEqual );
這段代碼雖然還不夠觸目驚心,但是讓程序員來做這個(gè),的確是太浪費(fèi)了。CppUnit為我們提供了一些機(jī)制來避免這樣的浪費(fèi)。我們可以修改我們的測(cè)試代碼為:
class MyTestCase:public CPPUNIT_NS::TestFixture
{
std::string mFileNameExist;
std::string mFileNameNotExist;
std::string mTestFolder;
enum DUMMY
{
FILE_SIZE = 1011
};
CPPUNIT_TEST_SUITE( MyTestCase );
CPPUNIT_TEST( testCtorAndGetName );
CPPUNIT_TEST( testGetFileSize );
CPPUNIT_TEST( testFileExist );
CPPUNIT_TEST( testFileModifyDateBasic );
CPPUNIT_TEST( testFileModifyDateEqual );
CPPUNIT_TEST_SUITE_END();
public:
virtual void setUp()
{
mTestFolder = "c:justfortest";
mFileNameExist = mTestFolder + "exist.dat";
mFileNameNotExist = mTestFolder + "notexist.dat";
if( GetFileAttributes( mTestFolder.c_str() ) != INVALID_FILE_ATTRIBUTES )
throw std::exception( "test folder already exists" );
if( ! CreateDirectory( mTestFolder.c_str() ,NULL ) )
throw std::exception( "cannot create folder" );
HANDLE file = CreateFile( mFileNameExist.c_str(), GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_NEW, 0, NULL );
if( file == INVALID_HANDLE_VALUE )
throw std::exception( "cannot create file" );
char buffer[FILE_SIZE];
DWORD bytesWritten;
if( !WriteFile( file, buffer, FILE_SIZE, &bytesWritten, NULL ) ||
bytesWritten != FILE_SIZE )
{
CloseHandle( file );
throw std::exception( "cannot write file" );
}
CloseHandle( file );
}
virtual void tearDown()
{
if( ! DeleteFile( mFileNameExist.c_str() ) )
throw std::exception( "cannot delete file" );
if( ! RemoveDirectory( mTestFolder.c_str() ) )
throw std::exception( "cannot remove folder" );
}
void testCtorAndGetName()
{
FileStatus status( mFileNameExist );
CPPUNIT_ASSERT_EQUAL( status.getFileName(), mFileNameExist );
}
void testGetFileSize()
{
FileStatus exist( mFileNameExist );
CPPUNIT_ASSERT_EQUAL( exist.getFileSize(), (DWORD)FILE_SIZE );
FileStatus notExist( mFileNameNotExist );
CPPUNIT_ASSERT_THROW( notExist.getFileSize(), FileStatusError );
}
void testFileExist()
{
FileStatus exist( mFileNameExist );
CPPUNIT_ASSERT( exist.fileExist() );
FileStatus notExist( mFileNameNotExist );
CPPUNIT_ASSERT( ! notExist.fileExist() );
}
void testFileModifyDateBasic()
{
FILETIME fileTime;
GetSystemTimeAsFileTime( &fileTime );
FileStatus exist( mFileNameExist );
CPPUNIT_ASSERT_NO_THROW( exist.getFileModifyDate() );
CPPUNIT_ASSERT_NO_THROW( exist.setFileModifyDate( &fileTime ) );
FileStatus notExist( mFileNameNotExist );
CPPUNIT_ASSERT_THROW( notExist.getFileModifyDate(), FileStatusError );
CPPUNIT_ASSERT_THROW( notExist.setFileModifyDate( &fileTime ), FileStatusError );
}
void testFileModifyDateEqual()
{
FILETIME fileTime;
GetSystemTimeAsFileTime( &fileTime );
FileStatus exist( mFileNameExist );
CPPUNIT_ASSERT_NO_THROW( exist.setFileModifyDate( &fileTime ) );
FILETIME get = exist.getFileModifyDate();
CPPUNIT_ASSERT( CompareFileTime( &get, &fileTime ) == 0 );
}
};
CPPUNIT_TEST_SUITE_REGISTRATION( MyTestCase );
int main()
{
CPPUNIT_NS::TestResult r;
CPPUNIT_NS::TestResultCollector result;
r.addListener( &result );
CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()->run( &r );
CPPUNIT_NS::TextOutputter out( &result, std::cout );
out.write();
return 0;
}
這里的
CPPUNIT_TEST_SUITE( MyTestCase );
CPPUNIT_TEST( testCtorAndGetName );
CPPUNIT_TEST( testGetFileSize );
CPPUNIT_TEST( testFileExist );
CPPUNIT_TEST( testFileModifyDateBasic );
CPPUNIT_TEST( testFileModifyDateEqual );
CPPUNIT_TEST_SUITE_END();
重要的內(nèi)容其實(shí)是定義了一個(gè)函數(shù)suite,這個(gè)函數(shù)返回了一個(gè)包含了所有CPPUNIT_TEST定義的測(cè)試用例的一個(gè)測(cè)試集。CPPUNIT_TEST_SUITE_REGISTRATION通過靜態(tài)注冊(cè)把這個(gè)測(cè)試集注冊(cè)到全局的測(cè)試樹中,后通過CPPUNIT_NS::TestFactoryRegistry::getRegistry(). makeTest()生成一個(gè)包含所有測(cè)試用例的測(cè)試并且運(yùn)行。具體的內(nèi)部運(yùn)行機(jī)制請(qǐng)參考CppUnit代碼簡(jiǎn)介。
VII. 小節(jié)
這篇文章簡(jiǎn)要的介紹了CppUnit和測(cè)試驅(qū)動(dòng)開發(fā)的基本概念,雖然CppUnit還有很多別的功能,譬如說基于GUI的測(cè)試環(huán)境以及和編譯器Post Build相連接的測(cè)試輸出,以及對(duì)于測(cè)試系統(tǒng)的擴(kuò)展等,但是基本上掌握了本文中的內(nèi)容可以進(jìn)行測(cè)試驅(qū)動(dòng)的開發(fā)了。
此外,測(cè)試驅(qū)動(dòng)開發(fā)還可以檢驗(yàn)需求的錯(cuò)誤。其實(shí)我選用GetFileTime和 SetFileTime作為例子是因?yàn)椋行┫到y(tǒng)上,SetFileTime所設(shè)置的時(shí)間是有一定的精度的,譬如說按秒,按天,...,因此你設(shè)置了一個(gè)時(shí)間后,可能get回來的時(shí)間和它不同。這其實(shí)是一個(gè)需求的錯(cuò)誤。當(dāng)然由于我的系統(tǒng)上沒有這個(gè)問題,所以我也不無病呻吟了。具體可以參考MSDN中對(duì)于這兩個(gè)函數(shù)的介紹。