Entry

単体テストのやり方とか(その1)

2010年05月14日

今日ぼちぼち知人と話していたこと。

彼が勤める会社は,中途で入ってくる人が多いんですけれど,かなり大きな会社でキャリアも積んでいるはずの人が,単体テストひとつできない,とご立腹だったのでした。みんながみんなそうじゃないとは思うんですけどね。自分が作ったもんくらい,ちゃんと動かしてから人前に出せよ,とは思う。

で,あたしが勤めるところもそんな人がいるんでねいかと少し聞いて回ったところ,新人君の中には単体テストの方法をそもそも知らないという人がいました。まぁね……OJT とか言えば聞こえはいいけど,そゆとこだからね……しゃーないか,とも。

つことで,ここではその新人君に書いているつもりで単体テストのやり方をさわりだけ書いておきます。

そういえば,単体テスト(unit testing)というと,xUnit を使うもんだと思っている向きも多いとは思うんですけれど,あゆのは汎用性がないので,何も知らない人がいきなり使うのはいけないんじゃないかと思います。あれは,自前で単体テストができる人が,手助けとして利用するものだと思うわけで……。xUnit がないと単体すらできないってのも,かっこ悪いですしね。

ここでは一応,C++ のプログラムについて書くけれども,OOP な言語だったらどれも同じような手順になるはずです。また,以下はあたしの方法なので,他にもっといい方法があったら,そっちをまねするといいと思います。

単体と実装は,実のところ紙一重なところがあって,最後まで作ってから単体テストに移る方法と,テストしながら実装していく方法があります。あたしゃどちらかというと後者の方法を取っています。テストを書いて実行しながら,実装を進めていく方針です。この方法を突き詰めると,テスト駆動開発(TDD; Test Driven Development)ということになるんだけれども,そんなしゃれたもんじゃありません。

それでは始めましょう。今回は,カウンターのクラスを作ることにします。仕様は次の通り。

  • クラス Counter は内部に int 型の整数を保持し,値の取得とインクリメントとデクリメントのメソッドを持つクラスである。
  • クラス生成時,内部の値は 0 に初期化される。
  • 内部の値を取得するには int get() const メソッドを使う。
  • インクリメントの関数は int inc() とし,インクリメントした後の値を返す。
  • デクリメントの関数は int dec() とし,デクリメントした後の値を返す。
  • オブジェクト間でコピーは行わない。

簡単な仕様なので,いきなり Counter.h とかを作りたくなるけれども,ここは我慢して,まずテスト(プログラム)を書きます。まだ何もないので,main 関数だけ。ここでは,テストプログラムを test.cpp とします。

int
main(int argc, char* argv[]) {
  return 0;
}

こいつをコンパイルして,ちゃんと動くか確認します。いきなりコンパイルもできないようじゃ,しょっぱいので。

ここでおもむろにクラスを宣言しましょう。仕様通りメソッドを追加した Counter クラスを宣言します。ここでは,Counter.h とします。

class Counter {
public:
  Counter() throw ();
  virtual ~Counter() throw ();
private:  // no implement
  Counter(const Counter& other) throw ();
  Counter& operator=(const Counter& rhs) throw ();
public:
  int get() const throw ();
  int inc() throw ();
  int dec() throw ();
private:
  int count_;
}

わざと間違えてますけど,こゆのはいきなり分かりませんよね。とりあえず,ヘッダを作ったので,テストに include することにしましょう。

#include "Counter.h"

int
main(int argc, char* argv[]) {
  return 0;
}

コンパイルします。

C:\home\aian>g++ test.cpp
test.cpp:4: error: new types may not be defined in a return type
test.cpp:4: note: (perhaps a semicolon is missing after the definition of `Count
er')
test.cpp:4: error: extraneous `int' ignored
test.cpp:4: error: `main' must return `int'

えっと,エラーの意味は調べてもらうとして,ここではコンパイルが通らなかったということが重要です。追加したのは,クラス Counter の宣言とその include 文だけですよね。追加した結果エラー になったということは,追加した宣言に間違いがあるということです。よく見ると,クラス宣言の最後のセミコロンが抜けています。つことで,修正する。あぶねいあぶねい。

class Counter {
public:
  Counter() throw ();
  virtual ~Counter() throw ();
private:  // no implement
  Counter(const Counter& other) throw ();
  Counter& operator=(const Counter& rhs) throw ();
public:
  int get() const throw ();
  int inc() throw ();
  int dec() throw ();
private:
  int count_;
};

こうやって,少しずつ動かしながら,開発を進めていくわけです。今回は丁寧に説明しているので,少し粒度が細かいけれど,手順としては(あたしの場合)どの場合でも同じです。

今のところ,クラスの宣言については問題なさげです。それではオブジェクトを生成するのに必要な関数(コンストラクタとデストラクタ)を書きましょう。これもテストを先に書く。main 関数に全部書いてもいいんですけど,数が多くなると整理するのが大変なので,テストごとに関数で分けることにします。

testCreate 関数は,単に Counter 型の変数を定義するだけのテストケースです。これで,「オブジェクトを生成できるか」をテストできます。

#include <iostream>
#include "Counter.h"

void
testCreate() {
  Counter cnt;
  std::cout << "ok" << std::endl;
}

int
main(int argc, char* argv[]) {
  testCreate();
  return 0;
}

コンパイルする。

C:\home\aian>g++ test.cpp
C:\tmp/ccO8ncsw.o:test.cpp:(.text+0xd): undefined reference to `Counter::Counter
()'
C:\tmp/ccO8ncsw.o:test.cpp:(.text+0x18): undefined reference to `Counter::~Count
er()'
collect2: ld returned 1 exit status

リンクエラーが出てしまいました。Counter::Counter() と Counter::~Counter() への参照が定義されていませんよ,というエラー。ま,中身(定義)を書いていないから当たり前ですね。

つことで,中身を書きます。ここでは,Counter.cpp に定義を書くことにします。こんな風に書く。空っぽ。

#include "Counter.h"

Counter::Counter() throw () {
}

Counter::~Counter() throw () {
}

コンパイルします。テストの中身もあるので,実行もしてみます。

C:\home\aian>g++ test.cpp Counter.cpp

C:\home\aian>a
ok

ok と出力されているので,「オブジェクトを作ることができた」ということが確認できます。ここで重要なのが,何をテストしている関数なのか,ということを明確にすることです。例えば,ここでは,ok と出力されているけれども,「追加したコンストラクタとデストラクタが正しい処理を行っているか」まではテストしていません。実際,メンバ変数である count_ には,何も触れていないので,初期化処理において仕様と異なる動きをしています。で,これはそのまま製品として出回れば,バグと呼ばれるもんだったりする。

……と,とりあえず,疲れたので今日はこの辺で。次回から,Counter クラスを作りこんでいくことにします。

なお,通常 IDE を使っているような開発環境では,Counter.cpp のようなテストされるファイルと,test.cpp のようなテストするファイルは,別のプロジェクトとして構成します。製品のソースにテストコードが混じってたらかっこ悪いから(つか,いろいろ有害)。最近の IDE では,テストプロジェクトを自動生成してくれる機能もあるけれど,これも自分で作れる人が手助けとして使う機能なので,単体テストをしたことがない向きは触るもんじゃないと思います。通常のコンソールアプリケーションを作るプロジェクトを作って,そこに main.cpp を入れることにしましょう。

とゆことで,また次回。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN