Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

CUnitによるテスト駆動開発

テストの習慣を身につけましょう!

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2007/03/13 00:00

JavaやC#などのオブジェクト指向言語の世界ではテスト駆動開発がポピュラーな手法となりました。テスト駆動開発はオブジェクト指向言語だけのものではありません。Visual C++ 2005 Express Edition 環境下で、CによるUnit Test Framework: CUnitを使ってのテスト駆動開発の手順を紹介します。

目次

はじめに

 CodeZineでの僕のデビュー記事『Cで実現する「ぷちオブジェクト指向」』、おかげさまでなかなか好評だったようです。まだまだCは現役だと実感しました。

 前回に引き続きCのお話です。テストをよりどころに実装をすすめ、信頼できるコードを書くためのプラクティス「テスト駆動開発」(TDD:Test Driven Development)を、Visual C++ 2005 Express EditionとUnit Test Framework: CUnitで行います。

対象読者

 そこそこのコードは書けるようになったけれど、どうも詰めが甘い/くだらないバグに出くわす/あっちを直すとこっちが壊れ、ぐだぐだになってしまう…そんな症状に悩まされている脱ビギナを目指すプログラマ。

テスト、してますか?

 「プログラムは思ったとおりには動かない、作ったとおりに動く」

 思ったとおりに作ってないと思ったとおりに動いてくれません。思ったとおりに動いているかを検証するのがテストです。

 テスト、してますか?

 そりゃもちろんやってるでしょうよ。正しく動かないライブラリやアプリケーションをお客様にお渡しするわけには参りませんからね。

 では、どうやってテストしてますか?

 ビギナはもちろんのこと、かなり熟練したプロでもよくやるのがprintfの埋め込みです。

printfによるお手軽テスト

 例えば、前回のCで実現する「ぷちオブジェクト指向」のサンプルCarを例にとれば、

car_test.c
#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で書き直すと、

simulate.c
#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に与えた式/ファイル名/行番号を出力してプログラムが停止します。

Visual C++ 2005ではこのような出力が得られます
 Assertion failed: car_distance(c) == 5, file .\simulate.c, line 16
 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)]に変更しておきます。

libcunitプロパティ
libcunitプロパティ

 この設定でビルドすればライブラリ「CUnit-2.1-0\VC8\Release - Static Lib\libcunit.lib」が生成されます。

 ライブラリ「libcunit.lib」とヘッダ群「CUnit-2.1-0\CUnit\Headers\*.h」を、それぞれ例えば「\usr\lib」と「usr\include\cunit」にコピーしておきます。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • επιστημη(エピステーメー)

    C++に首まで浸かったプログラマ。 Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"り わんくま同盟でたまにセッションスピーカやったり 中国茶淹れてにわか茶人を気取ってたり、 あと Facebook とか。 著書: - STL標準...

All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5