初出: 2007/04/28
最新: 2007/09/14
[3. D言語各論] に戻る
HelloWorld というクラスを作ることになりました。
module helloworld;
class HelloWorld
{
}
"Hello" という文字列を返す hello メソッドと、 "World" という文字列を返す world メソッドを実装しなきゃいけないそうです。 つまり、以下のようなテストコードが通るクラスを作るのが目標です。
module helloworld;
class HelloWorld
{
unittest
{
auto obj = new HelloWorld;
assert( obj.hello() == "Hello" );
assert( obj.world() == "World" );
}
}
Dでは、unittest というブロックの中にテストコードを書きます。あるいは…
module helloworld;
class HelloWorld
{
unittest
{
auto obj = new HelloWorld;
assert( obj.hello() == "Hello" );
}
unittest
{
auto obj = new HelloWorld;
assert( obj.world() == "World" );
}
}
適当に分けて書いてもいいです。もしくは
module helloworld;
class HelloWorld
{
}
unittest
{
auto obj = new HelloWorld;
assert( obj.hello() == "Hello" );
assert( obj.world() == "World" );
}
クラスの外でもunittestは書けます。普通の関数/テンプレートなどのテストや、 モジュール全体のテストみたいなものは外に書く方が自然かもしれません。 DMD付属の標準ライブラリのソースを見てみると、 unittestブロックが実際に使われている例がたくさん見つかると思います。
とりあえずテスト通らない例として無茶苦茶な実装しておきます。
module helloworld;
class HelloWorld
{
string hello() { return "World"; }
string world() { return "Hello"; }
unittest
{
auto obj = new HelloWorld;
assert( obj.hello() == "Hello" );
assert( obj.world() == "World" );
}
}
これをテストするには、てきとうに空のmain関数を書いたソースを1個用意して
// testrunner.d
void main() {}
-unittest オプション付きで、さっきのモジュールと一緒にビルドします。
dmd -unittest testrunner.d helloworld.d
そして実行
> testrunner Error: AssertError Failure helloworld(11)
怒られました。helloworldモジュールの11行目でテスト失敗してるそうです。
dmd の -unittest オプションは、「mainを実行する前に、プログラム内の全unittestブロックを実行するように コンパイルする」という意味のオプションです。ここではunittestだけを実行するために空のmain関数を別途用意 しました。普通にプロジェクトをビルドするときに-unittestをつけるようにして、 「全部テスト通らないと起動しない」プログラムを作っちゃうこともできます。
string hello() { return "Horld"; }
string world() { return "Wello"; }
ちゃんと直せば
> testrunner
>
何もエラーが出なくなります。OK。ちょっと寂しいのでtestrunnerのmainでなんかメッセージ出した方が楽しいかもですね。
さてさて、仕様変更です。HelloWorld クラスには、"GoodBye" という文字列を返す goodbye メソッドも 必要ということになりました。さてさてメソッドを追加しましょう。
module helloworld;
class HelloWorld
{
string hello() { return "Hello"; }
string world() { return "World"; }
string goodbye() { return "GoodBey"; }
unittest
{
auto obj = new HelloWorld;
assert( obj.hello() == "Hello" );
assert( obj.world() == "World" );
}
}
テストテスト。
> testrunner
>
無事テストも通って…あれ?なにかおかしいですね。そう、goodbyeメソッドがテストされてません。 「単体テストされてない部分が残ってる」、これはいけません。
こういうテスト漏れを検出するには、「カバレッジ解析」が有効です。-cov オプションをつけます。
dmd -cov -unittest tester.d helloworld.d
> testrunner >
こう実行すると、
> ls *.lst helloworld.lst testrunner.lst
二つ新しいファイルができてます。中身はこんなの。
|module helloworld; | |class HelloWorld |{ 1| string hello() { return "Hello"; } 1| string world() { return "World"; } 0000000| string goodbye() { return "GoodBey"; } | | unittest | { 1| auto obj = new HelloWorld; 1| assert( obj.hello() == "Hello" ); 1| assert( obj.world() == "World" ); | } |} helloworld.d is 83% covered
左側に、「ソースの各行が何回実行されたか」の回数が表示されます。goodbyeメソッドの実行回数が0回、 つまり1度も実行されていない、つまり、1度もテストされていない ことがわかります。 カバレッジ解析によって、こういう風に、ちゃんと単体テストでクラス全体をカバーできているかを検査できます。
unittest
{
auto obj = new HelloWorld;
assert( obj.hello() == "Hello" );
assert( obj.world() == "World" );
assert( obj.goodbye() == "GoodBye" );
}
}
さて、あわててテストを追加…
> testrunner Error: AssertError Failure helloworld(14)
怒られました。無事バグが検出できたようです。GoodBeyじゃなくてGoodByeって返さないといけなかったのです。
D言語でのテストは、全て assert 文で書くのが基本です。
assert( bool式 );
と書くと、bool式がfalseになっちゃてたときにエラーになります。
assert( bool式, "めっせーじ" );
と書くと、エラーの時に"めっせーじ"が表示されて多少わかりやすくなります。
assert( オブジェクト ); assert( オブジェクト, "めっせーじ" );
と書くと、オブジェクトがnullでないことと、 オブジェクトがちゃんと クラス不変条件 を満たしていることが チェックされます。おかしくなってたらエラーです。