2012年12月19日 星期三

check: C語言的unit test

在寫程式的時候,我們不斷的重複著修改、測試、修改、測試...的流程。顯而易見的,我們要作不少重複的測試。在理想的情況下,當然是希望親愛的電腦可以替我們代勞:只要我說「請跑測試吧」,電腦就會進行測試,並且告訴我們哪個測試沒有通過。其實,這樣的好事不是天方夜譚,而是一個應該要應用到每個你心愛的程式上面的作業流程!

其中一種測試的方法,把整個程式切割成許多可以測試的小單位,然後依據這些小單位的功能進行一連串的測試。這樣的方式被稱作unit test。

在很多情況下,會有好心人士提供一套針對某個程式語言的unit test軟體。(如果沒有的話,考慮一下,寫一個吧!)。像是C#有NUnit, Java有JUnit, Haskell有HUnit...等。C語言的話,有check這個程式庫可以使用。

以fedora來說,可以用下面的方法來安裝check:

yum install check
或者是看看自己的Linux distribution有沒有替你打包好。沒有的話Google找一下應該就有了(google check c)安裝完後,就可以開始使用了!基本上來說,在含有行unit test的程式碼中加上:
#include 
附帶一提,check.h中也包含簡短的使用說明,(個人認為相當的實用)請務必去看一下。
要編譯的時候,使用
gcc ... -lcheck
來進行編譯。

講了那麼多,那要怎麼利用check來寫unit test呢?直接看範例好了。如果想要執行下面的範例,請記得

gcc test.c -lcheck

#include <check.h>
#include <stdio.h>

/*
 * 利用暴力法
 * 計算start, start + step, start + step * 2 ... end的總和
 */
int series_sum_ref(int start, int end, int step)
{
  int s = 0;
  for(; start <= end; start += step)
    {
      s += start;
    }
  return s;
}

/*
 * 利用公式
 * 計算start, start + step, start + step * 2 ... end的總和
 */
int series_sum_formula(int start, int end, int step)
{
  int n = (end - start) / step + 1;
  return n * (2 * start + (n - 1) * step) / 2;
}

/*
 * 所有的Unit test都要用START_TEST和END_TEST來包裹起來
 */
START_TEST(test_series_ref)
{
  fail_if(series_sum_ref(1, 10, 1) != 55);
  fail_if(series_sum_ref(1, 10, 2) != 25);
  fail_if(series_sum_ref(3, 5, 2) != 8);
}
END_TEST

START_TEST(test_series_formula)
{
  int a, b, s;
  for(a = -20; a <= -10; a++)
    {
      for(b = 0; b <= 10; b++)
        {
          for(s = 1; s <= 3; s++)
            {
              int r = series_sum_ref(a, b, s);
              int f = series_sum_formula(a, b, s);
              if(r != f)
                {
                  printf("\nFail at a = %d, b = %d, s = %d\nr = %d, f = %d\n", a, b, s, r, f);
                  fail("Test failed: the series sum from reference and formula are different.");
                }
            }
        }
    }
}
END_TEST

/*
 * check的架構:
 * 可以設定許多組Suite, 每組Suite都有許多的TCase, 
 * 每個Tcase又有許多test
 * 總之呢,這是一個階層式的架構。
 *
 * Suite1
 * __ TCase1
 * _____ Test1
 * _____ Test2
 * ...
 * __ TCase2
 * ...
 * __ TCase3
 * ...
 * Suite2
 * __ TCase2-1
 * ...
 */
int main()
{
  TCase* series_sum_case = tcase_create("Arith. Series");
  tcase_add_test(series_sum_case, test_series_ref);
  tcase_add_test(series_sum_case, test_series_formula);

  Suite* suite = suite_create("Main Suite");
  suite_add_tcase(suite, series_sum_case);
  
  SRunner* runner = srunner_create(suite);
  srunner_run_all(runner, CK_VERBOSE);
  
  int fail_count = srunner_ntests_failed(runner);
  srunner_free(runner);
}

其實check有比這裡介紹的還要多些的功能。欲知詳情,可以看check.h喔!

另外,因為check是一個C語言相容的unit test framework,所以也可以用來測試任何C語言相容函數喔!(這包括Fortran, 組合語言等)

沒有留言:

張貼留言