CodingFirst

C言語、Perl、JavaScript、最近はPythonも。出来上がったものより、プログラムを書くことが好き。あと、スイーツ。

スポンサーサイト

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

MinUnitでテストしたその後

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

前にxUnit系のテストとしてMinUnitとそれを改造したMacroUnitについて書いた。
あれから半年以上経って、試行錯誤した結果、もうちょっと違うのがいいなーと思い
今回、munitというのを作った。
前に書いた記事→http://d.hatena.ne.jp/yukioc/20100604/1275657911

xUnit系テストを、CUnit、MinUnit、MacroUnitと経て使ってきたけど、
共通して思うのは書いたコードを関数レベルで試すのに向いてるなということ。
それとテストを書きやすい関数を書く癖がついて、汎用的に、依存関係を少なく
設計する事に繋がるので良いなぁとも思った。

しかし、後からテストを追加する、品質向上的な使い方には向いてないと考えてる。
バグが見つかったらその環境でデバッグするんだけど、
わざわざxUnitで再現環境を作ってデバッグしない。
修正したら既存のテストを実行するけど、
その際に今回用のテストを追加するのは一手間余分に感じる。
やってはみたけど、そんな不具合に限ってxUnitでテストしづらくて
時間がかかったりした。たぶん、xUnitは不向きで、
別の方法があるんだと思う。
良いアイデアないけど、たぶんそう。

ところで、MacroUnitは、CUnit相当の事ができてソースは
短いのでベストと思ってたんだけど、使ってるとしっくりこない。
どうもsuiteがいけない。
suiteが入ることでテストに計画性を考えだすが、
自分用のツールなんてそもそも無計画なのでモヤっと。

そういうわけで、実装を見直した。一応、名前もmunitに変えた。
http://gist.github.com/236001#file_munit.c

#ifndef __munit_h__
#define __munit_h__
#include 
#define mu_failed(file,line,expr) printf( "%s:%u: failed assertion `%s'\n",file,line,expr)
#define mu_tested(test,passed) printf( "Test: %-10s ... %s\n",test,(passed)?"passed":"FAILED")
#define mu_assert(expr) do{mu_nassert++;if(!(expr)){++mu_nfail;mu_failed(__FILE__,__LINE__,#expr);}}while(0)
#define mu_run_test(test) do{int f=mu_nfail;++mu_ntest;test();mu_tested(#test,(f==mu_nfail));}while(0)
#define mu_show_failures() do{printf("### Failed %d of %d asserts (%d tests).\n",mu_nfail,mu_nassert,mu_ntest);}while(0)
extern int mu_nfail;
extern int mu_ntest;
extern int mu_nassert;
#endif /* __munit_h__ */

int mu_nfail=0;
int mu_ntest=0;
int mu_nassert=0;

static void test_foo(){
  int foo=7;
  mu_assert(foo==7);
  mu_assert(foo==0||foo==7);
}

static void test_bar() {
  int bar=4,fail=4;
  mu_assert(bar==4);
  mu_assert(fail==5);
}

static void test_hoge() {
  int hoge=7;
  mu_assert(hoge==7);
}

static void test_fuga() {
  int fuga=7;
  mu_assert(fuga==7);
}

int main(int argc, char **argv) {
  mu_run_test(test_foo);
  mu_run_test(test_bar);
  mu_run_test(test_hoge);
  mu_run_test(test_fuga);
  mu_show_failures();
  return mu_nfail==0;
}

実行結果。

$ gcc munit.c && ./a.out 
Test: test_foo   ... passed
munit.c:27: failed assertion `fail==5'
Test: test_bar   ... FAILED
Test: test_hoge  ... passed
Test: test_fuga  ... passed
### Failed 1 of 6 asserts (4 tests).
スポンサーサイト

MinUnitでテストしてみる

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

C言語でお手軽にテストしたい(その4)

CUnit はちと面倒だったので、もっとお手軽なのを探したら、MinUnit を発見。試してみる。

JTN002 - MinUnit -- a minimal unit testing framework for C

特徴は、3行。

だから、ソースも簡単に読めるし、試すのも簡単。サイトにある minunit.h minunit_example.c をコピペなどして作成したら

$ gcc minunit_example.c && ./a.out
error, bar != 5
Tests run: 2

おしまい。。

用途によってはこれでいい。

でも、自分の用途としては少し足りないかな。

setup, teardown とかはなくていいけど、suiteとかtestとかのグルーピングは欲しくて、あと、printfももう少し情報欲しい。

MinUnitは行がすくないし、早速、アレンジしてみる。

#ifndef __macrounit_h__
#define __macrounit_h__
#define mu_prn_failed(file,line,expr) printf( "%s:%u: failed assertion `%s'\n",file,line,expr)
#define mu_prn_test(test,passed) printf( "  Test: %s ... %s\n",test,(passed)?"passed":"FAILED")
#define mu_prn_suite(suite) printf( "Suite: %s\n",suite)

#define mu_assert(file,line,expr) do { ++mu_asserts; if (!(expr)) { ++mu_failures; mu_prn_failed(file,line,#expr); }} while (0)
#define MU_ASSERT(expr) mu_assert(__FILE__,__LINE__,expr)

#define mu_run_test(test) do { int f=mu_failures; ++mu_tests; test(); mu_prn_test(#test,(f==mu_failures)); } while (0)
#define mu_run_suite(suite) do { ++mu_suites; mu_prn_suite(#suite); suite(); } while(0)
#define mu_show_failures() do { printf("### Failed %d of %d asserts (%d suites, %d tests).\n",mu_failures,mu_asserts,mu_suites,mu_tests); } while(0)
extern int mu_suites;
extern int mu_tests;
extern int mu_failures;
extern int mu_asserts;
#endif /* __macrounit_h__ */

ちょっと行増やし過ぎかな。。

MinUnitからの改造なので、MacroUnitと名前を微妙にかえといたけど、マクロ名とかは同じ感じのままにした。とりあえず、サンプル。

#include 
#include "macrounit.h"
int mu_suites = 0;
int mu_tests = 0;
int mu_asserts = 0;
int mu_failures = 0;

static void test_foo() {
        int foo = 7;
        MU_ASSERT(foo == 7);
}

static void test_bar() {
        int bar = 4;
        MU_ASSERT(bar == 4);
        MU_ASSERT(bar == 5);
}

static void test_hoge() {
        int hoge = 7;
        MU_ASSERT(hoge == 7);
}

static void test_fuga() {
        int fuga = 7;
        MU_ASSERT(fuga == 7);
}

static void suite_one() {
        mu_run_test(test_foo);
        mu_run_test(test_bar);
}

static void suite_two() {
        mu_run_test(test_hoge);
        mu_run_test(test_fuga);
}

int main(int argc, char **argv) {
        mu_run_suite(suite_one);
        mu_run_suite(suite_two);
        mu_show_failures();
        return mu_failures != 0;
}

で、実行。

$ gcc macrounit_example.c && ./a.out 
Suite: suite_one
  Test: test_foo ... passed
macrounit_example.c:20: failed assertion `bar == 5'
  Test: test_bar ... FAILED
Suite: suite_two
  Test: test_hoge ... passed
  Test: test_fuga ... passed
### Failed 1 of 5 asserts (2 suites, 4 tests).

まーまーいいんじゃないかな。

エラーの表示を assert()風にしたので、vimなどを使ってる際、便利だろう。

それにしてもな。。minunitと同じく、グローバル変数をどこかで定義しなきゃならんのは美しくない。

まぁ、こんくらいはいいとするか。


CUnitでテストしてみる(その3)

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

CUnitでテストコードを書く。

テストコード。

test.c

CUnitでテストコードを書く。

テストコード。

test.c:
#include <stdio.h>
#include "CUnit/Basic.h"

FILE* hoge_stdout;
extern int hoge_main(int argc, char*argv[]);
extern int hoge;

int ts1_init(void)
{
  hoge_stdout = tmpfile();
  return 0;
}
int ts1_cleanup(void)
{
  hoge_stdout = stdout;
  return 0;
}

void case1(void)
{
  int argc=1;
  char* argv[1] = { "hoge.c" };
  int ret;
  ret=hoge_main(argc,argv);
  CU_ASSERT(hoge==10);
  CU_ASSERT(ret==0);
}

CU_ErrorCode ts1_add(void)
{
    CU_TestInfo cases[] = {
        { "case1" , case1 },
        CU_TEST_INFO_NULL
    };
    CU_SuiteInfo suites[] = {
        { "test of suite1", ts1_init, ts1_cleanup, cases },
        CU_SUITE_INFO_NULL
    };
    return CU_register_suites( suites );
}

int main(int argc, char *argv[])
{
  unsigned int fails;
  if (CUE_SUCCESS != CU_initialize_registry()) return CU_get_error();
  if (CUE_SUCCESS != ts1_add() ) return CU_get_error();
  CU_basic_set_mode(CU_BRM_VERBOSE);
  CU_basic_run_tests();
  fails = CU_get_number_of_failures();
  CU_cleanup_registry();
#if 0
  if ( fails > 0 ) {
    fprintf( stderr, "Error: number of failures = %d\n", fails );
    return 1;
  }
#endif
  return CU_get_error();
}

コンパイル。

$ gcc -DTEST -ftest-coverage -fprofile-arcs hoge.c test.c -LCUnit -lcunit
hoge.c:6:1: warning: "stdout" redefined
In file included from hoge.c:1:
/usr/include/stdio.h:241:1: warning: this is the location of the previous definition

うん。ちょっと強引に書きすぎたとこが Warningでてるが、

テストに影響ないので放置する。

$ ./a.out


     CUnit - A Unit testing framework for C - Version 2.1-0
     http://cunit.sourceforge.net/


Suite: test of suite1
  Test: case1 ... passed

--Run Summary: Type      Total     Ran  Passed  Failed
               suites        1       1     n/a       0
               tests         1       1       1       0
               asserts       2       2       2       0

$ gcov hoge.c
File 'hoge.c'
Lines executed:54.55% of 11
hoge.c:creating 'hoge.c.gcov'

55%か。テストケースを追加してカバレッジを上げる。

#include &#60;stdio.h>
 #include "CUnit/Basic.h"
 
 FILE* hoge_stdout;
 extern int hoge_main(int argc, char*argv[]);
 extern int hoge;
 
 int ts1_init(void)
 {
   hoge=10;
   hoge_stdout = tmpfile();
   return 0;
 }
 int ts1_cleanup(void)
 {
   hoge=10;
   hoge_stdout = stdout;
   return 0;
 }
 
 void case1(void)
 {
   int argc=1;
   char* argv[1] = { "hoge.c" };
   int ret;
   ret=hoge_main(argc,argv);
   CU_ASSERT(hoge==10);
   CU_ASSERT(ret==0);
 }
+void case2(void)
+{
+  int argc=2;
+  char* argv[2] = { "hoge.c", "10" };
+  int ret;
+  ret=hoge_main(argc,argv);
+  CU_ASSERT(hoge==20);
+  CU_ASSERT(ret==0);
+  hoge=10;
+}
+void case3(void)
+{
+  int argc=3;
+  char* argv[3] = { "hoge.c", "10", "20" };
+  int ret=hoge_main(argc,argv);
+  CU_ASSERT(hoge==10);
+  CU_ASSERT(ret==1);
+}
 
 CU_ErrorCode ts1_add(void)
 {
     CU_TestInfo cases[] = {
         { "case1" , case1 },
+        { "case2" , case2 },
+        { "case3" , case3 },
         CU_TEST_INFO_NULL
     };
     CU_SuiteInfo suites[] = {
         { "test of suite1", ts1_init, ts1_cleanup, cases },
         CU_SUITE_INFO_NULL
     };
     return CU_register_suites( suites );
 }
 
 int main(int argc, char *argv[])
 {
   unsigned int fails;
   if (CUE_SUCCESS != CU_initialize_registry()) return CU_get_error();
   if (CUE_SUCCESS != ts1_add() ) return CU_get_error();
   CU_basic_set_mode(CU_BRM_VERBOSE);
   CU_basic_run_tests();
   fails = CU_get_number_of_failures();
   CU_cleanup_registry();
 #if 0
   if ( fails > 0 ) {
     fprintf( stderr, "Error: number of failures = %d\n", fails );
     return 1;
   }
 #endif
   return CU_get_error();
 }

テスト。

$ rm *.g* a.out
$ gcc -DTEST -ftest-coverage -fprofile-arcs hoge.c test.c -LCUnit -lcunit
hoge.c:6:1: warning: "stdout" redefined
In file included from hoge.c:1:
/usr/include/stdio.h:241:1: warning: this is the location of the previous definition
$ ./a.out


     CUnit - A Unit testing framework for C - Version 2.1-0
     http://cunit.sourceforge.net/


Suite: test of suite1
  Test: case1 ... passed
  Test: case2 ... passed
  Test: case3 ... passed

--Run Summary: Type      Total     Ran  Passed  Failed
               suites        1       1     n/a       0
               tests         3       3       3       0
               asserts       6       6       6       0
$ gcov hoge.c
File 'hoge.c'
Lines executed:100.00% of 11
hoge.c:creating 'hoge.c.gcov'

$ cat hoge.c.gcov
        -:    0:Source:hoge.c
        -:    0:Graph:hoge.gcno
        -:    0:Data:hoge.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include &#60;stdio.h>
        -:    2:#include &#60;stdlib.h>
        -:    3:#ifdef TEST
        -:    4:extern FILE* hoge_stdout;
        -:    5:#define main hoge_main
        -:    6:#define stdout hoge_stdout
        -:    7:#endif
        -:    8:
        -:    9:int hoge=10;
        -:   10:int help(void)
        1:   11:{
        1:   12:  fprintf(stdout,"hello\n.");
        1:   13:  return 0;
        -:   14:}
        -:   15:int main(int argc, char*argv[])
        3:   16:{
        3:   17:  switch(argc){
        -:   18:  case 1:
        1:   19:    return help();
        -:   20:  case 2:
        1:   21:    hoge += atoi(argv[1]);
        1:   22:    fprintf(stdout,"hoge=%d\n",hoge);
        1:   23:    return 0;
        -:   24:  default:
        1:   25:    fprintf(stdout,"error\n");
        1:   26:    return 1;
        -:   27:  }
        -:   28:}

よし。100%達成。

まとめ。

assert主体でテスト書くのでわかりやすくていいな。テストしてみるとテストしづらい事に気づいてテストしやすいコードを書く癖がつく。その際、シンプルに、他モジュールに依存しないようにする事が多く、良い設計に繋がるように思う。

ただなぁ...。ライブラリビルドするのがめんどくさいなぁ。MinUnit みたいな方が小回りが聞いていいかも知んないな。。


実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)
(2006/07)
岸 哲夫

商品詳細を見る

CUnitでテストしてみる(その2)

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

前回、CUnitをビルドした。今回はプログラムを書いてgcovでラインカバレッジを測るところ>

とりえず、なんでもいいけどプログラムを書く。

#include 
#include <>
#ifdef TEST
extern FILE* hoge_stdout;
#define main hoge_main
#define stdout
#endif

int hoge=10;
int help(void)
{
  fprintf(stdout,"hello>
  return 0;
}
int main(int argc, 
{
  switch(argc){
  case 1:
    return help();
  case 2:
    hoge += atoi(argv[1]);
    fprintf(stdout,"hog>
    return 0;
  default:
    fprintf(stdout,"err>
    return 1;
  }
}

gccでコンパイル。オプションに -ftest-coverage -fprofile-arcs を付けとく。

a.outを実行後、gcovでa.outを分析してラインカバレッジをみる。

$ gcc -ftest-coverage -fprofi>
$ ./a.out 10
hoge=20
$ gcov hoge.c
File 'hoge.c
Lines executed:45.45>
hoge.c:creating 'hoge.c.gc>

45% らしい。ソースコードを確認してみる。

$ cat hoge.c.gcov
        -:    0:Source:hoge.c
        -:    0:Graph:hoge.gcno
        -:    0:Data:hoge.gcda
        -:    0:Runs:1
        -:    0:Programs:1<>
        -:    1:#include >
        -:    2:#include >
        -:    3:#ifdef TEST<>
        -:    4:extern FILE* hoge_stdout5:#define main>
        -:    6:#define stdo>
        -:    7:#endif
        -:    8: 
        -:    9:int hoge<>
        -:   10:int help(<>
    #####:   11:{
    #####:   12:  fprintf(stdout,"hello\n.");
    #####:   13:  return 0;
        -:   14:}
        -:   15:int main(<>
        1:   16:
        1:   17:  sw>
        -:   18:  case
    #####:   19:    return help();
        -:   20:  case 2:
        1:   21:    hoge += atoi(argv[1]
        1:   22:    >
        1:   23:    >
        -:   24:  default:
    #####:   25:    fprintf(stdout,"error\n");
    #####:   26:    return 1;
        -:   27:  }
        -:   28:}

##### の行が通過していないパス。

とりあえず、ラインカバレッジが測れるようになった。


実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)
(2006/07)
岸 哲夫

商品詳細を見る

CUnitでテストしてみる(その1)

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

cのプログラムをCUnitとgcovを使ってテストしてみる(準備編)。

まず、テストの方針。テストといってもいろいろあるけど、テストコードは簡単&単純なルールで記述できて、テストのカバレッジがぱっと分かりやすい事。

テストコードは、日頃デバッグで使ってるassert()に似た感じがいいなと思ってたら、丁度そういうフレームワークとしてxUnitがあって、そのC言語での実装で定番そうなのがCUnitだった。

テストカバレッジの方は、上げる事には正直、モチベーションは高くなくて、ざっくりラインカバレッジを測ろうというわけで gcov

では、環境準備。ツールとして事前に gcc/gcov を用意しておく。あと必要なのが CUnit。CUnitは下記のサイトから入手する。

C Unit Testing Framework | Get C Unit Testing Framework at SourceForge.net

ここでは、Ver. 2.1.0 (2006/5/23) 版を使う。3年前リリースと安定的ではあるけど、モチベーションはちと下がるかも。多少、バグがあるので、CVSからチェックアウトした方がいいかも知れない。

とりあえず、

$ tar xvfz CUnit-2.1-0-src.tar.gz
$ pushd CUnit-2.1-0
$ ./configure
$ make
$ popd

と、無事、CUnitのライブラリのビルドが完了。make install はしないでおく。する必要ないっしょ。

$ mkdir CUnit
$ cp CUnit-2.1-0/CUnit/Headers/*.h CUnit/
$ cp CUnit-2.1-0/CUnit/Sources/.libs/libcunit.a CUnit/

こんな感じでヘッダファイルとライブラリを CUnitディレクトリ下におく。

ひとまず、ここまで。

参考リンク)

xUnit - Wikipedia


実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)
(2006/07)
岸 哲夫

商品詳細を見る

 | HOME | 

Search

Recent Entries

Foot Print



Categories

Monthly

Recent Comments

Recent Trackbacks

Profile

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。