Unit Testing 的 framework,最早是由 Kent Beck 在〈Simple Smalltalk Testing〉這篇 paper 中提出。後來因為 Java 的流行,及 Java 和 Smalltalk 的相似性,Kent Beck 又完成了一個 Java 版本的 Unit Testing framework -- JUnit 。隨著 Extreme Programming 的熱門,以及 test-driven development 在實務上的重大成功,現在幾乎各種程式語言都有它們的 Unit Testing Framework ,大家都來 Unit 一下,並通稱為 XUnit。
還記得我曾經為一個簡單的 Prolog Interpreter ,在 C++ 下找來了 Unit++ 來作 Unit Test,以下是當時寫的 test suit:
1: /**
2: * @file utest.cpp
3: * @author Jiang Yu-Kuan
4: * @date 2003/12/23
5: */
6: #include <unit++.h>
7: #include "logic.h"
8: using namespace std;
9: using namespace unitpp;
10:
11: namespace
12: {
13: class Test : public suite
14: {
15: ostringstream ost;
16: void mathObject() {
17: Variable v1("X1"), v2("X1");
18: ost.str(""); // clear the containing string
19: ost << v1 << " " << (v1==v2) << " " << v2;
20: assert_eq( "variable check", "X1 1 X1", ost.str() );
21:
22: Constant cs1("cc"), cs2("cc");
23: ost.str("");
24: ost << cs1 << " " << (cs1==cs2) << " " << cs2;
25: assert_eq( "constant check", "cc 1 cc", ost.str() );
26:
27: List l1("(a,b,f(a,g(c)))"), l2("(a,b,f(a,g(c)))");
28: ost.str("");
29: ost << l1 << " " << (l1==l2) << " " << l2;
30: assert_eq( "list check", "(a, b, f(a, g(c))) 1 (a, b, f(a, g(c)))", ost.str() );
31:
32: Compound cp("f(a,b,c,f(Z,g(x)),g(y))");
33: ost.str("");
34: ost << cp;
35: assert_eq( "compound check", "f(a, b, c, f(Z, g(x)), g(y))", ost.str() );
36:
37: ost.str("");
38: ost << isCompound(cp);
39: ost << isList(l1) << isList(v1) << isList(cs1);
40: assert_eq( "isTerm check", "1100", ost.str() );
41: }
42:
43: Substitution s;
44: Compound a, b;
45: void unifyCase1() {
46: ost.str("");
47: ost << a << " " << Unify(a, b, s) << " " << b;
48: assert_eq( "compound/unify check", "f(X11, g(b)) 1 f(a, Y1234)", ost.str() );
49:
50: ost.str("");
51: ost << s;
52: assert_eq( "meat of substitution check", "{ X11/a, Y1234/g(b) }", ost.str() );
53: }
54: void substituteCase1() {
55: Substitute( a, s );
56: ost.str("");
57: ost << a;
58: assert_eq( "substitute check", "f(a, g(b))", ost.str() );
59: }
60:
61: Compound c, d;
62: void unifyCase2() {
63: s.clear();
64: ost.str("");
65: ost << c << " " << Unify(c, d, s) << " " << d;
66: assert_eq( "compound/unify check", "f(X, f(X, Y)) 1 f(g(Y), f(g(a), Z))", ost.str() );
67:
68: ost.str("");
69: ost << s;
70: assert_eq( "meat of substitution check", "{ X/g(a), Y/a, Z/a }", ost.str() );
71: }
72: void substituteCase2() {
73: Substitute( c, s );
74: ost.str("");
75: ost << c;
76: assert_eq( "substitute check", "f(g(a), f(g(a), a))", ost.str() );
77: }
78: public:
79: Test():
80: suite("Unify test suite"),
81: a("f(X11, g(b))"), b("f(a, Y1234)"),
82: c("f(X, f(X, Y))"), d("f(g(Y), f(g(a), Z))")
83: {
84: add("mathObject", testcase(this, "Math Object", &Test::mathObject));
85: add("unifyCase1", testcase(this, "Unify Case1", &Test::unifyCase1));
86: add("substituteCase1", testcase(this, "Substitute Case1", &Test::substituteCase1));
87: add("unifyCase2", testcase(this, "Unify Case2", &Test::unifyCase2));
88: add("substituteCase2", testcase(this, "Substitute Case2", &Test::substituteCase2));
89: suite::main().add("UnifyTestSuite", this);
90: }
91: } * theTest = new Test();
92: }
後來有好一陣子,我工作都是在開發 Embedded System 的程式,大多數的情況下只能用 C ,且使用的是 8 bit 的 MCU ,記憶體是受限的(以 K 為單位在計算)。不用說先前我為開發 C++ 程式找的 Unit++ 不能用,就連許多專為 C 設計的 unit testing framework 也顯得太臃腫了。這可怎麼辦?難道放棄 Unit Test 嗎?這對開發 Embedded System 而言,太冒險了;改成手動 Unit Test 呢?又太累人了!
在看了 MinUnit -- a minimal unit testing framework for C 後,我受到了很大的啟示:framework 只是個工具,重點是「測試」和「自動化」。在融合了 Unit++ 的介面後,我為 C 寫了一個在系統資源受限的情況下,也能適用的 Unit Testing Toy:
1: /**
2: * @file ToyUnit.h
3: * \em ToyUnit -- A toy unit testing framework for C Language.
4: * @attention \em ToyUnit is designed for non-file-system environment,
5: * e.g. the Keil C51. Hence, \em buffer \em flushing (for stdout/stderr)
6: * is not necessary upon the implementing of \c TU_ASSERT()
7: * and \c TU_RESULT().
8: * @warning It is necessary to call flush() while applying
9: * \em ToyUnit with \em buffer I/O platform.
10: * @author Jiang Yu-Kuan
11: * @date 2005/3/9
12: * @version 1.1
13: * @see MinUnit (http://www.jera.com/techinfo/jtns/jtn002.html)
14: * @see ToyUnit_test.c
15: * @todo to extend the framework to fit the more general environment
16: */
17: #ifndef _TOY_UNIT_H_
18: #define _TOY_UNIT_H_
19:
20:
21: #if !defined( NDEBUG )
22:
23: #include <stdio.h>
24:
25: static unsigned int _TU_PASS_RUN= 0; ///< the number of pass test runs
26: static unsigned int _TU_FAIL_RUN= 0; ///< the number of fail test runs
27:
28: /** Asserts that the \a assertion is true.
29: * @param msg a message that is shown if asserting fail
30: * @param assertion a boolean expression
31: */
32: #define TU_ASSERT( msg, assertion ) \
33: do { \
34: if (assertion) { \
35: putchar('.'); \
36: _TU_PASS_RUN++; \
37: } \
38: else { \
39: puts("\n" msg " [test fail]");\
40: _TU_FAIL_RUN++; \
41: } \
42: } while (0)
43:
44: /** Prints the statistics of pass runs and fail runs. */
45: #define TU_RESULT() \
46: printf("\nTests [Pass-Fail]: [%u-%u]", _TU_PASS_RUN, _TU_FAIL_RUN)
47:
48: #else
49:
50: #define TU_ASSERT( msg, assertion ) ((void)0)
51: #define TU_RESULT() ((void)0)
52:
53: #endif // NDEBUG
54:
55:
56: #endif // _TOY_UNIT_H_
57:
58: /** @example ToyUnit_test.c
59: * This is an example of how to use the ToyUnit testing framework.
60: */
使用時只要簡單地 include 上面這個 ToyUnit.h 即可。以下是這個 ToyUnit 的 Unit Test:
1: /**
2: * @file ToyUnit_test.c
3: * An example to show how to use the \em ToyUnit testing framework
4: * @author Jiang Yu-Kuan
5: * @date 2005/3/9
6: * @see ToyUnit.h
7: */
8: #include "ToyUnit.h"
9:
10: int main()
11: {
12: TU_ASSERT("test 1", 1==1);
13: TU_ASSERT("test 2", 1+1!=2); // test fail
14: TU_ASSERT("test 3", 1+1==2);
15: TU_ASSERT("test 4", 1*1==1);
16: TU_ASSERT("test 5", 1-1==0);
17: TU_ASSERT("test 6", 2==2);
18: TU_ASSERT("test 7", 2+1==3);
19: TU_ASSERT("test 8", 1==1);
20: TU_RESULT(); // Tests [Pass-Fail]: [7-1]
21:
22: return 0;
23: }
嘻!它被拿來自己測試自己,也剛好當成使用範例 :)
3 comments:
最近把這篇原本放在 frame tag 的 code 改成良人大密寶裡面,以 code tag 搭配 CSS 的方式來呈現 code 。
在 Firefox 上看起來還不賴,可惜 IE 不支援 CSS 的 max-height 等 attributes ,且其字型呈現上,如字的大小等,也跟 Firefox 差很多,所以在 IE 下看起來效果滿差的。
目前我Unit Test的做法是先把要測試的lib, test case and test main.c用gcc compile成在host端可執行檔, 待測試成功後, 再用arm-elf-gcc 將lib compile成target device要使用的.a
能否請教您是怎樣測試你的 MCU code 呢?
To bFish,
針對支援 GCC 的 embedded 開發環境,用你的方法也就夠了。
否則,還可以在 MCU 的 simulator 執行部份 Unit Tests ,畢竟 C 在不同平台下的行為還是有細微的差異。
對消費級的產品來說,我只會在重要的或易出錯的程式片段作較完整的 Unit Tests 。
其餘較 trivial 的部份,除了撰碼時謹慎些外,剩下的就等功能測試出錯時在來針對可疑點搭建測試碼。
Post a Comment