はじめに
CodeZineでの僕のデビュー記事『Cで実現する「ぷちオブジェクト指向」』、おかげさまでなかなか好評だったようです。まだまだCは現役だと実感しました。
前回に引き続きCのお話です。テストをよりどころに実装をすすめ、信頼できるコードを書くためのプラクティス「テスト駆動開発」(TDD:Test Driven Development)を、Visual C++ 2005 Express EditionとUnit Test Framework: CUnitで行います。
対象読者
そこそこのコードは書けるようになったけれど、どうも詰めが甘い/くだらないバグに出くわす/あっちを直すとこっちが壊れ、ぐだぐだになってしまう…そんな症状に悩まされている脱ビギナを目指すプログラマ。
テスト、してますか?
「プログラムは思ったとおりには動かない、作ったとおりに動く」
思ったとおりに作ってないと思ったとおりに動いてくれません。思ったとおりに動いているかを検証するのがテストです。
テスト、してますか?
そりゃもちろんやってるでしょうよ。正しく動かないライブラリやアプリケーションをお客様にお渡しするわけには参りませんからね。
では、どうやってテストしてますか?
ビギナはもちろんのこと、かなり熟練したプロでもよくやるのがprintfの埋め込みです。
printfによるお手軽テスト
例えば、前回のCで実現する「ぷちオブジェクト指向」のサンプルCar
を例にとれば、
#include <stdio.h> #include "Car.h" int main() { Car c; c = car_new(50, 2); /* 50[L], 2[L/km] */ printf("distance = %d[km], remain = %d[L]\n", car_distance(c), car_remain(c)); car_refuel(c, 40); /* 40[L]給油 */ printf("distance = %d[km], remain = %d[L]\n", car_distance(c), car_remain(c)); car_drive(c, 5); /* 5[km]走行 */ printf("distance = %d[km], remain = %d[L]\n", car_distance(c), car_remain(c)); … car_delete(b); return 0; }
このようにコードの要所要所にprintf
を挿入し、変数の内容や関数の戻り値を出力します。これをコンパイルし実行すると、
distance = 0[km], remain = 0[L] distance = 0[km], remain = 40[L] distance = 5[km], remain = 30[L] …
が得られ、思ったとおりに動いているかを確認/検証できますし、何より動いてる様子が目に見えるのが励みになります。
けどもこのprintf
埋め込み、小さなコードをちょいちょいとテストするにはお手軽でよろしいのですが、テスト対象/テスト項目が増えてくるにつれやってられなくなります。printf
埋め込みはデータの出力が行われるだけであり、結果の確認/検証はあなたがやんなきゃなりません。コードに追加/変更を施してコンパイル/実行のたびに数百行にも及ぶ出力を目視チェックでは、しまいにうんざりします。
assertを活用しよう!
そこでオススメなのがassert
マクロです。assert
の使い方はいたって簡単。まずコードの先頭に#include <assert.h>
を追加します。次に検証したい部分に
assert( 正しく動いていればtrueとなるはずの式 );
を埋め込みます。上記のprintf
埋め込みによるテストコードをassert
で書き直すと、
#include <assert.h> #include <stdio.h> #include "Car.h" int main() { Car c; c = car_new(50, 2); /* 50[L], 2[L/km] */ assert( car_distance(c) == 0 ); assert( car_remain(c) == 0 ); car_refuel(c, 40); /* 40[L]給油 */ assert( car_distance(c) == 0 ); assert( car_remain(c) == 40 ); car_drive(c, 5); /* 5[km]走行 */ assert( car_distance(c) == 5 ); assert( car_remain(c) == 30 ); car_delete(c); return 0; }
これをコンパイルし実行すると、assert
に与えた式がtrueであるならそのままその行を通過しますが、もしそこがfalse、すなわち期待どおりの結果でなかった場合、assert
に与えた式/ファイル名/行番号を出力してプログラムが停止します。
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
printf
埋め込みテストは結果の検証を人の目に頼るのに対し、assert
マクロを使えばコード自身が検証してくれます。そのためテストの実行とその検証に要する時間はごくわずか、ほとんど瞬時に完了します。
このほとんど瞬時にテスト完了が重要なのです。テストが瞬時に行われるのであればテストが煩わしくありません。ちょっとコードをいじるたびにテストできます。テスト項目が数百に及んだとしてもたいした時間はかかりませんから、テスト項目(つまりassert
行)をがんがん書き加え、ちょっとコードを書くたびに実行し、正しく動くことを確認しながら実装を進めることができます。
そうなるとテストが実装完了後の動作確認だけでなく、テストを使って実装するという開発スタイルを採ることができます。
つまり、関数群のインターフェイスであるヘッダができあがった時点で「まずテストを書き、そのテストにパスするようにコードを書く」を繰り返すのです。「正しく書かれていればこう動くはずだ」がテストの中にもれなく表現されているならば、そのテストにパスすれば実装完了、ついでにテストも完了というわけです。テストによって開発を進める「テスト駆動開発(Test Driven Development)」です。
UnitTestFramework CUnitの使い方(1/2)
assert
マクロによるテストはprintf
埋め込みと目視チェックに比べればテスト効率が格段に向上するのですが、何せ単純なだけに使い勝手の悪い部分も少なくありません。どうにも困るのがエラー一つで停止すること。テスト項目が山ほどあったとき、assert
の順序を考えて適宜並び替えておかないと、エラー行で停止するためにそのエラーとは関係のないテストが行われず、先に進めないのです。エラーがあろうがなかろうがとにかく全テストを実行し、エラーとなったテスト項目だけを一気に出力してくれればいいのだけど。
そこでUnitTestFrameworkの一つ、CUnitを使ってみましょう。CUnitはCコードのテストのためのCで書かれたライブラリです。
ダウンロードとインストール
CUnitはSourceForgeからダウンロードでき、2007年3月時点での最新版は2.1-0です。「CUnit-2.1-0-src.zip」をダウンロードし、適当なディレクトリに展開してください。
Visual C++ 2005 Express Edition(他のVisual Studioでも同様)を起動し、ディレクトリ「CUnit-2.1-0\VC8」にあるソリューション「CUnit.sln」を開きます。次にソリューション構成[Release - Static Lib]を選択し、プロジェクト「libcunit」のプロパティを開き、[構成プロパティ|C/C++|コード生成|ランタイム ライブラリ]を[マルチスレッド DLL (/MD)]に変更しておきます。
この設定でビルドすればライブラリ「CUnit-2.1-0\VC8\Release - Static Lib\libcunit.lib」が生成されます。
ライブラリ「libcunit.lib」とヘッダ群「CUnit-2.1-0\CUnit\Headers\*.h」を、それぞれ例えば「\usr\lib」と「usr\include\cunit」にコピーしておきます。