Showing posts with label unit test. Show all posts
Showing posts with label unit test. Show all posts

Tuesday, May 23, 2006

Programming as a Specialist Doing

William 在〈軟體的庖丁解牛能力〉中引述了 Peopleware 第 29 章的話:

工作流程愈是獲得改善,工作內容就愈艱難……一、沒有被替除掉的工作,就是更加知識密集、需要更多技術與經驗……二、經過改善的工作流程,讓你能夠面對更艱難挑戰,而你也會面對這些挑戰……

當工作流程獲得實際改善,我們也同時需要更多更有能力、更有經驗的員工。

William 後來特地在〈工作流程愈是獲得改善,工作內容就愈艱難〉附上了這段話的原文,有興趣可以自行參閱。

這裡我就不談工作流程了,只談談作為一門專業,程式設計有哪些值得我們關注的小細節:

Coding Style

這裡不捲入哪種 Coding Style 較好的爭論。只強調一點,選擇特定的 Coding Style ,是為了要把程式的邏輯結構強調出來,使其看起來條理分明。並不是為了排版的好看與否。

這方面議題,就我所知,寫得最好的,出現在《Code Complete》的第十八章。裡面列出各種 coding style ,由程式的縮排一直到註解的寫法,都有各種例子,並以程式的「邏輯結構」被凸顯的情形,逐一進行比較。

Editor

好的文字編輯工具是很重要的,諸如:可以讓人自訂的 Syntax Highlighting、自訂字型,自訂背景顏色、自動縮排(auto-indent)、多檔管理、Project based 的 Find/Replace/Regular Expression、Auto-Completion, 甚至 Code Folding, Refactoring 的支援等,都要仔細評估,找套合用的。

作為 coding 的 Editor ,新版的 UltraEdit 用起來很順手,這裡強烈推薦。

此外,作為跨平台及 open source 軟體, CodeBlocks 是一套非常符合我胃口的 C/C++ IDE ,雖然還有些不足的地方,但其改善的速度也是令人印象深刻。

Font

專業的程式設計師,不會放過任何可以改善其 coding 方式的機會,連 coding 用的字型,也要挑剔一下:我們選用的字型,要能方便長期閱讀 code ,一些易發生混淆的字元,諸如大寫的 O 和數字 0 間;數字 1 、大寫 I ,及小寫 L 間等,也要能很明確區分。甚至括號在顯示時也要作特別的強調。

好的 coding 字型,還要允許我們清晰地在同一個畫面閱讀更多行的 code 。這裡強烈推薦在 Proggy fonts 中,選一套來用。此外,〈Programming Fonts〉中也有相關的介紹。

Once and Only Once

Once and Only Once 是要我們剔除重複的程式片段,重複的邏輯要合併,重複的敘述要改寫,多餘的變數要刪除。重複不但顯得累贅,在修改時還容易發生遺漏、不一致的情形。要真正作好這點,其實就是要極力找出軟體系統的模式,其精神是「尋找模式,並反覆利用這些模式」

Unit Test Framework

如果還沒想清楚如何測試某段 code ,那就不要去編寫它。把測試及比對結果的工作,交給電腦來執行,是 Unit Test Framework 最大的用意。花些時間,找個所用語言及編程環境適用的 Unit Test Framework 是非常划算的投資。如果你在找 Embedded Systems 的 Unit Test Framework ,也許可以參閱拙作〈An Array Implementation of Queue〉中的例子。

Documentation

講起 Documentation ,就不得不提提高德納先生極為推崇的 Literate Programming 。這是種以寫文件為主,coding 為輔的程式設計方式。其背後的想法是基於,程式主要是寫給人看的,而非只為了給電腦執行。Literate Programming 的 code 穿插在文件段落間,要餵給電腦前,再用工具把 code 抽取出來,交給編譯器處理。

不過 Literate Programming 也只有高德納那樣的學院派高手,才能玩得盡興。像我這種在街上混吃的,還是較適合寫 code 為主,文件為輔的方式。這種作法的精神是,code 本身就要寫得能讓專業程式員很容易閱讀,然後再加上意圖性的註解,使其趨於完善。

doxygen 這樣的工具,讓我們在註解時,使用標準化的標記,然後再由程式源碼中,抽出特定註解,作為說明文件。

Version Control

想起學生時代,最常使用的 version control 方式,就是在 project 目錄下建立 /v0, /v1, /v2... 的子目錄,或者乾脆將整個 project 給它 zip 起來,然後在壓縮檔名附加版號及日期。這方法用於個人開發、幾千行以內的小規模程式,還算堪用。

後來接觸了 open source 的東西,所以 CVS 用了好一陣子。現在也不能免俗地,改用 Subversion 。如果你還沒選定適用的 Version Control Tools ,這裡推薦 Subversion,其 Windows 下的 Client, TortoiseSVN 當然也不容錯過。

Issue Tracking

在 team work 下,bug 或 feature 的追蹤,變得繁雜起來。此時,選套合用的 issue tracking tools 可以省掉很多人工。

TracSubversion 能作很好的搭配。對於小規模的應用,已經足夠了。

Saturday, May 20, 2006

An Array Implementation of Queue

Embedded System 程式開發, queue 是很常用的資料結構: UART 在接收及傳輸資料時,通常各需要一個 character queue;在處理 keypad 的按鍵輸入時需要一個 key queue ;task 間的溝通,也可能要用到某種 event queue 。

除了提供一個這樣的 queue 的實作外,在〈A Unit Testing Toy〉中曾經提出的 ToyUnit ,也剛好藉這次,展示其實際的應用:

Architecture

這麼簡單的程式,我們只要分成下列三個 source files 就可以了:

  1. Queue.h -> interface
  2. Queue.c -> implementation
  3. Queue_test.c -> unit test

Writing Test Code First

在真正實作 queue 時,我們可以先利用先前提過的 ToyUnit 來為它寫 test code 。

Queue_test.c 程式片段如下:

 8: #include "Queue.h"
 9: #include "ToyUnit.h"
10: 
11: enum {
12:     BUF_SIZE= 3
13: };
14: 
15: char buf[BUF_SIZE];
16: 
17: int main()
18: {
19:     Queue q;
20: 
21:     Q_init( &q, buf, BUF_SIZE );
22:     TU_ASSERT("03", Q_empty(&q));
23:     TU_ASSERT("04", !Q_full(&q));
24:     TU_ASSERT("05", Q_size(&q) == 0);
25: 
26:     Q_put(&q, 'a');
27:     TU_ASSERT("12", Q_first(&q)=='a');
28:     TU_ASSERT("13", !Q_empty(&q));
29:     TU_ASSERT("14", !Q_full(&q));
30:     TU_ASSERT("15", Q_size(&q) == 1);
31:     TU_ASSERT("16", Q_last(&q)=='a');
32: 
33:     Q_put(&q, 'b');
34:     TU_ASSERT("22", Q_first(&q)=='a');
35:     TU_ASSERT("23", !Q_empty(&q));
36:     TU_ASSERT("24", !Q_full(&q));
37:     TU_ASSERT("25", Q_size(&q) == 2);
38:     TU_ASSERT("26", Q_last(&q)=='b');
39: 
40:     Q_put(&q, 'c');
41:     TU_ASSERT("32", Q_first(&q)=='a');
42:     TU_ASSERT("33", !Q_empty(&q));
43:     TU_ASSERT("34", Q_full(&q));
44:     TU_ASSERT("35", Q_size(&q) == 3);
45:     TU_ASSERT("36", Q_last(&q)=='c');
46: 
47:     TU_ASSERT("41", Q_get(&q)=='a');
48:     TU_ASSERT("42", Q_first(&q)=='b');
49:     TU_ASSERT("43", !Q_empty(&q));
50:     TU_ASSERT("44", !Q_full(&q));
51:     TU_ASSERT("45", Q_size(&q) == 2);
52:     TU_ASSERT("46", Q_last(&q)=='c');
53: 
54:     TU_ASSERT("51", Q_get(&q)=='b');
55:     TU_ASSERT("52", Q_first(&q)=='c');
56:     TU_ASSERT("53", !Q_empty(&q));
57:     TU_ASSERT("54", !Q_full(&q));
58:     TU_ASSERT("55", Q_size(&q) == 1);
59:     TU_ASSERT("56", Q_last(&q)=='c');
60: 
61:     TU_ASSERT("61", Q_get(&q)=='c');
62:     TU_ASSERT("63", Q_empty(&q));
63:     TU_ASSERT("64", !Q_full(&q));
64:     TU_ASSERT("65", Q_size(&q) == 0);
65: 
66:     Q_put(&q, 'd');
67:     TU_ASSERT("72", Q_first(&q)=='d');
68:     TU_ASSERT("73", !Q_empty(&q));
69:     TU_ASSERT("74", !Q_full(&q));
70:     TU_ASSERT("75", Q_size(&q) == 1);
71:     TU_ASSERT("76", Q_last(&q)=='d');
72: 
73:     TU_ASSERT("81", Q_get(&q)=='d');
74:     TU_ASSERT("83", Q_empty(&q));
75:     TU_ASSERT("84", !Q_full(&q));
76:     TU_ASSERT("85", Q_size(&q) == 0);
77: 
78:     TU_ASSERT("91", Q_unget(&q)=='d');
79:     TU_ASSERT("92", Q_first(&q)=='d');
80:     TU_ASSERT("93", !Q_empty(&q));
81:     TU_ASSERT("94", !Q_full(&q));
82:     TU_ASSERT("95", Q_size(&q) == 1);
83:     TU_ASSERT("96", Q_last(&q)=='d');
84: 
85:     Q_clear(&q);
86:     TU_ASSERT("103", Q_empty(&q));
87:     TU_ASSERT("104", !Q_full(&q));
88:     TU_ASSERT("105", Q_size(&q) == 0);
89: 
90:     TU_RESULT();
91: 
92:     return 0;
93: }

算一算,這裡總共寫了 52 個測試。

Compiling -> 一大堆錯誤 -> failure

當然啦,因為根本還沒真正開始寫這個 queue :p

為了記憶體的使用是可預期的,這個 queue 不使用動態記憶體分配,改而讓 client 在 initialize queue 時自己提供 buffer,通常這個 buffer 是一個靜態的 array。

Extracts the Interface

根據先前那個無法編譯過關的 unit test code ,我們已經知道怎麼使用這個 queue 了,從中可以很直覺地抽出 queue 的介面。

Queue.h 程式片段如下:

12: #include <stdlib.h>
13: #include "platform.h"
14: 
15: 
16: typedef char QueueItem; ///< the item type in a queue
17: 
18: typedef struct {
19:     Index first;    ///< index of the first (front) item of a queue.
20:     Index end;      ///< index of the end (last+1) of a queue.
21:     size_t count;   ///< the number of items in a queue.
22:     QueueItem* buf; ///< a pointer that indicates the buffer of a queue.
23:     size_t buf_size;///< buffer size.
24: } Queue;
25: 
26: 
27: void Q_init( Queue* q, QueueItem* buf, size_t buf_size );
28: void Q_clear( Queue* );
29: 
30: void Q_put( Queue*, QueueItem );
31: QueueItem Q_get( Queue* );
32: 
33: QueueItem Q_unget( Queue* );
34: 
35: QueueItem Q_first( const Queue* );
36: QueueItem Q_last( const Queue* );
37: 
38: size_t Q_size( const Queue* );
39: bool Q_empty( const Queue* );
40: bool Q_full( const Queue* );

在一連串的往返,敲定介面的過程中,Compiling 終於過了。

但是 Linking 時又出錯,應該不令人意外吧。 :)

Done the Implementation

現在就來為 queue 補上血肉。如果實作沒出錯,期望看到的 unit test 執行結果如下:

./Queue_test
....................................................
Tests [Pass-Fail]: [52-0]

每 pass 一個 test ,console 畫面上就會打一個點,否則會印出 fail 的訊息。

畫面上有 52 個點,看起來乾乾淨淨、清清爽爽,真讓人覺得愉快!

TU_RESULT(.) 會為我們秀出簡單的測試總結:這個例子顯示,共有 52 個 tests , pass 52 個, fail 0 個。

這裡再次顯現這類 UnitTest framework 的設計哲學:

  1. 讓電腦幫我們比對「預期的執行結果」及「實際的執行結果」間是否一致。
  2. 如果測試沒出錯,就不要秀一堆有的沒的東西,擾亂視覺。
  3. 執行測試變得簡單、直覺,就會鼓勵我們多測試,這是良性的循環。

Queue.c 完整的實作如下:

  1: /**
  2:  * @file queue.c
  3:  *      Implementes a queue module, and that uses a circular array.
  4:  * @author Jiang Yu-Kuan
  5:  * @date 2006/05/07 (initial version)
  6:  * @date 2006/05/18 (last revision)
  7:  * @version 2.0
  8:  */
  9: #include "Queue.h"
 10: #include <assert.h>
 11: 
 12: 
 13: /** Initilizes a queue.
 14:  * @param[in,out] q the queue to be initialized.
 15:  * @param[in] buf the buffer to store items.
 16:  * @param[in] buf_size the buffer size.
 17:  */
 18: void Q_init( Queue* q, QueueItem* buf, size_t buf_size )
 19: {
 20:     q->first= 0;
 21:     q->end= 0;
 22:     q->count= 0;
 23:     q->buf= buf;
 24:     q->buf_size= buf_size;
 25: }
 26: 
 27: 
 28: /** Clear the queue */
 29: void Q_clear( Queue* q )
 30: {
 31:     q->first= 0;
 32:     q->end= 0;
 33:     q->count= 0;
 34: }
 35: 
 36: 
 37: /** Puts an item to the end of a queue.
 38:  * @param[in,out] q the queue to add an item.
 39:  * @param[in] i the added item.
 40:  */
 41: void Q_put( Queue* q, QueueItem i )
 42: {
 43:     assert (!Q_full(q));
 44: 
 45:     ++q->count;
 46:     q->buf[q->end]= i;
 47:     q->end= (q->end+1) % q->buf_size;
 48: }
 49: 
 50: 
 51: /** Gets the first item of a queue.
 52:  * @param[in,out] q the queue to get an item.
 53:  * @return the gotton item
 54:  */
 55: QueueItem Q_get( Queue* q )
 56: {
 57:     QueueItem i;
 58:     assert (!Q_empty(q));
 59: 
 60:     --q->count;
 61:     i= q->buf[q->first];
 62:     q->first= (q->first+1) % q->buf_size;
 63:     return i;
 64: }
 65: 
 66: 
 67: /** Rolls back a "get" operation.
 68:  * This cannot work correctly next to 'clear' operation.
 69:  * @return the previous character
 70:  */
 71: QueueItem Q_unget( Queue* q )
 72: {
 73:     ++q->count;
 74:     if (q->first == 0)
 75:         q->first= q->buf_size - 1;
 76:     else
 77:         --q->first;
 78:     return q->buf[q->first];
 79: }
 80: 
 81: 
 82: /** Peeks the first item of a queue.
 83:  * @param[in] q the queue to get an item.
 84:  * @return the peeked item
 85:  */
 86: QueueItem Q_first( const Queue* q )
 87: {
 88:     assert (!Q_empty(q));
 89: 
 90:     return q->buf[q->first];
 91: }
 92: 
 93: 
 94: /** Peeks the last item of a queue.
 95:  * @param[in] q the queue to get an item.
 96:  * @return the peeked item
 97:  */
 98: QueueItem Q_last( const Queue* q )
 99: {
100:     Index i;
101:     assert (!Q_empty(q));
102: 
103:     if (q->end == 0)
104:         i= q->buf_size - 1;
105:     else
106:         i= q->end - 1;
107:     return q->buf[i];
108: }
109: 
110: 
111: /** Gets the size of a queue at the moment. */
112: size_t Q_size( const Queue* q )
113: {
114:     return q->count;
115: }
116: 
117: 
118: /** Determines if a queue is empty. */
119: bool Q_empty( const Queue* q )
120: {
121:     return q->count == 0;
122: }
123: 
124: 
125: /** Determines if an queue is full */
126: bool Q_full( const Queue* q )
127: {
128:     return q->count == q->buf_size;
129: }

Afterword

前面三個步驟,通常我並不會很明確地去區隔它們,而是多次往返、反覆,確保這三個部份能維持同步。

實際上,我習慣先把最基本的 interface 及 implementation 完成,再寫對應的 unit test,然後一個一個擴充介面、單元測試、及實作。

Unit test 主要目的有:

  1. 最基本的,它是強大的 bug 偵測器,我們要確保程式能通過測試,以證明它目前為止,還沒出錯。
  2. 日後如果還發生 bug ,我們就為它補上新的 test 來捕抓這個新的 bug 。
  3. 如果 module 有作任何修改,例如新增介面,改變演算法,調整結構等,我們寫的 unit test code 更可以發揮它的作用,讓我們在修改時無後顧之憂,因為 unit test 會捕捉這個過程所衍生的錯誤。
  4. 很重要的一點,unit test 還可以用來幫助我們確認模組的介面。

Sunday, March 05, 2006

A Unit Testing Toy

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: }

嘻!它被拿來自己測試自己,也剛好當成使用範例 :)