2007年6月23日 星期六

Self Unit Test Framework

自我測試架構
適用語言: OOP language

相信大家都知道軟體測試的重要,也明白軟體測試有助於開發者及早發現問題。
軟體測試之中有相當多的面向,而今天我要提的,是 Unit Test 這個部分。

在 OOP 的語言之中,每個元件應該有其一獨特且專一個功能 (如此才能彰顯 OOP 的優勢)
也就是說,在寫 OOP 的案子時,到最後應該會像是在玩樂高一般。把每個元件兜到它應該出現的位置。案子也就成了。

但,這只是個理想。

實際作做案子就會知道,程式一定有 bug ,而要如何 debug 就成了每個設計師的惡夢。而debug 中最最另人頭疼的,就是不知道 bug 在那。
Unit Test 就是一個很有效且快速的技術幫助設計師了解且找出問題的所在。
在表面上,做 Unit Test 看起來會多花許多工夫。不過,在案子越來越龐大時, Unit Test 可以快速的指出問題的癥結,也可以讓 PM 更明確的掌握案子的進度。
一般來說,使用 Unit Test 技術可以讓案子穩定的有進展。

在使用 Unit test 之前要了解幾件事。
1.你的物件是要做什麼的。
2.有那些 boundary 存在?
3.錯誤情況的處理為何?
4.任何你的物件會遇到的極端狀況

再依上述的認知做以下的 Test
1. 正常的 input 資料是否有正常的 output (要先知道正確的結果)
2.在 boundary 時資料是否也是正常運作 lower/upper bound 都要做
3.不正常的 input 時,是否能發現,且做出正確的回應?
4.大量被使用時,是否能成功的運作?

在做測試時,你要用最嚴苛的心去測你的程式。這才是正真對你自己仁慈。

每個物件都要有一個自己的測試程式,每做一次修改(不管多小的修改),就要跑一次,並得到一份報告。

如果物件被整合到其它物件,其它的物件也要做 Unit test
最懶也是最好的方法就是寫支小 script 一次跑完所有的 Tests
(好像很花時間?其實不然,因為它可能會在很短的時間內幫你找出本來可能要花一個月才找出來的 bug)


以下是小弟寫的 Unit Test FrameWork (可輕易的改寫成 Java 版的 :-) )
每個要被測試的物件,只要多重繼承 SelfTestUnit
然後實作下面兩個 functions
virtual bool doSelfTest() {return false;};
把你對這個物件的測試寫在裏面,並回報是否測試成功

virtual const char *myClassName(){return "Please Set ClassName Here";};
指定這個物件的名字 (for report)

而在測試程式之中寫:
Capataz cap;
cap.addSelfTester(new 被測試的物件(1));
cap.addSelfTester(new 被測試的物件(2));
cap.addSelfTester(new 被測試的物件(3));
...
cap.doTest();

就可以了...




File: selftest.h

#ifndef __TICK_CPP_SELF_TEST_FRAMWORK__
#define __TICK_CPP_SELF_TEST_FRAMWORK__

#include <vector>

class SelfTestUnit {
public :
virtual bool doSelfTest() {return false;};
virtual const char *myClassName(){return "Please Set ClassName Here";};
};

class Capataz {
public :
//Capataz(char *file);
//~Capataz();
bool addSelfTester(SelfTestUnit *);
bool addSelfTester(SelfTestUnit &);
bool delSelfTester(SelfTestUnit *);
bool delSelfTester(SelfTestUnit &);
bool doTest();
private:
std::vector < SelfTestUnit * > _tester;
void testReport(SelfTestUnit *,bool );
void allPassReport(bool);
};


#ifdef SELFPROFILE
#include <stdlib.h>
#include <stdlio.h>
extern "C" void __cyg_profile_func_enter(void *func,void *caller) __attribute__((__no_instrument_function__));
extern "C" void __cyg_profile_func_exit(void *func,void *caller) __attribute__((__no_instrument_function__));
#endif //SELFPROFILE

#endif // __TICK_CPP_SELF_TEST_FRAMWORK__




File: selftest.cpp

#include <iostream>
#include "selftest.h"


bool Capataz::addSelfTester (SelfTestUnit * tester) {
if (tester == NULL) {
std::cout << "Try to register a NULL pointer" << std::endl;
return false;
}
_tester.push_back (tester);
return true;
}

bool Capataz::addSelfTester (SelfTestUnit & tester) {
return addSelfTester (&tester);
}

bool Capataz::delSelfTester (SelfTestUnit * tester) {
std::vector < SelfTestUnit * >::iterator pos;
if (tester == NULL) {
std::cout << "Try to remove a NULL pointer" << std::endl;
return false;
}
for (pos = _tester.begin (); pos < _tester.end (); pos++) {
if (tester == *pos) {
_tester.erase (pos);
return true;
}
}
return false;
}

bool Capataz::delSelfTester (SelfTestUnit & tester) {
return delSelfTester (&tester);
}

bool Capataz::doTest () {
bool allpass = true;

std::vector < SelfTestUnit * >::iterator pos;
if (_tester.size () == 0) {
std::cout << "There are no tester yet!!" << std::endl;
return allpass;
}

bool btmp;

for (pos = _tester.begin (); pos < _tester.end (); pos++) {
std::cout << "Do Testing:" << (*pos)->myClassName () << std::endl;
btmp = (*pos)->doSelfTest ();
allpass = allpass && btmp;
testReport ((*pos), btmp);
}
allPassReport (allpass);
return allpass;
}


void Capataz::testReport (SelfTestUnit * stu, bool rep) {
if (rep) {
std::cout << "Test Report: " << stu->myClassName () << "--->OK!!" << std::endl;
} else {
std::cout << "Test Report: " << stu->myClassName () << "--->Failed!!" << std::endl;
}
}

void Capataz::allPassReport (bool rep) {
std::cout << std::endl;
if (rep) {
std::cout << "All Passed? Yes" << std::endl;
} else {
std::cout << "All Passed? No" << std::endl;
}
}

沒有留言: