.NET Tools
NUnit入門 Test Firstのススメ

2.何はさておき、最初にテストを書こう

(株)ピーデー
川俣 晶
2002/02/23
Page1 Page2 Page3 Page4 Page5

 NUnitにおけるテストとは、クラス・ライブラリに含まれるメソッドなどを呼び出し、結果をチェックするメソッドの集合体である。テストを実行するためのユーザー・インターフェイスはNUnit側で用意しているので、プログラマーが特に作り込む必要はなく、ただ呼び出してチェックするコードだけを書けばよい。テスト・メソッドは、最初の4文字はTestで始めると決められている。その代わり、テスト・メソッドと通常のメソッドを区別するために、何か特別な属性などを書く必要はない。ともかく、Testで始まる名前のメソッドをどんどん追加していけば、NUnitによってそれらのメソッドがすべて自動的に呼び出されるのである。

 というわけで、以下のようにテスト用のメソッドを追加してみた。

 1: using System;
 2: using NUnit.Framework;
 3: using MyGame;
 4:
 5: #if !NOTEST
 6:
 7: namespace MyGame
 8: {
 9:   public class HighScoreTest : TestCase
10:   {
11:     public HighScoreTest(String name) : base(name)
12:     {
13:     }
14:     protected override void SetUp()
15:     {
16:     }
17:     public static ITest Suite
18:     {
19:       get
20:       {
21:         return new TestSuite(typeof(HighScoreTest));
22:       }
23:     }
24:     public void TestConstructor()
25:     {
26:       HighScore highScore = new HighScore();
27:       Assert( "highScore.name == \"NoName\"", highScore.name == "NoName" );
28:       Assert( "highScore.point == 0", highScore.point == 0 );
29:     }
30:     public void TestSetScore()
31:     {
32:       HighScore highScore = new HighScore();
33:       highScore.setScore( "Taro", 1234 );
34:       Assert( "highScore.name == \"Taro\"", highScore.name == "Taro" );
35:       Assert( "highScore.point == 1234", highScore.point == 1234 );
36:
37:       highScore.setScore( "Jiro", 1233 );
38:       Assert( "highScore.name == \"Taro\"", highScore.name == "Taro" );
39:       Assert( "highScore.point == 1234", highScore.point == 1234 );
40:
41:       highScore.setScore( "Saburo", 1235 );
42:       Assert( "highScore.name == \"Saburo\"", highScore.name == "Saburo" );
43:       Assert( "highScore.point == 1235", highScore.point == 1235 );
44:     }
45:     public void TestIfHighest()
46:     {
47:       HighScore highScore = new HighScore();
48:       highScore.setScore( "Taro", 1234 );
49:       Assert( "highScore.isHighest(1235)", highScore.isHighest(1235) );
50:       Assert( "!highScore.isHighest(1234)", !highScore.isHighest(1234) );
51:       Assert( "!highScore.isHighest(1233)", !highScore.isHighest(1233) );
52:     }
53:   }
54: }
55:
56: #endif
メソッドを追加したHiScoreTestクラスのソース・コード
テストのためのメソッドを追加している。テスト・メソッドは最初の4文字はTestで始めると決められている。

 24行目からのTestConstructorは、コンストラクタをテストするものだ。まず、26行目でテストの対象となるHighScoreクラスのインスタンスを生成する。次の27〜28行目にあるAssertは、条件をチェックする機能を持つ。これにはいろいろなバリエーションがあるが、ここで使っているのは、第1引数に説明文字列、第2引数にbool型の条件判断式を指定したメソッドである。説明文字列としては、判定に使用した式をそのまま書いておくのが手っ取り早いので、そのようにしているが、もっと詳しい説明を書いてもよい。紛らわしいときや分かりにくいときには、もっと丁寧に書いておくべきだろう。27行目では、メンバnameの初期値はNoNameであるべきこと、そして28行目ではメンバpointの初期値は0であるべきことが示されている。

26: HighScore highScore = new HighScore();
27: Assert( "highScore.name == \"NoName\"", highScore.name == "NoName" );
28: Assert( "highScore.point == 0", highScore.point == 0 );

 もう1つ注目すべきことは、この時点で、テスト対象がHighScoreという名前のクラスであること、そして、文字型のnameと整数型のpointをメンバに持つことが確定されていることだ。つまり、テストを書くためにはあいまいさを取り除かなければならず、矛盾があってはならない。“Test First”を実践している場合、設計段階であいまいさや矛盾が入り込んでいても、この段階でそれを発見できる。コーディングが進んでから気付いて、せっかく書いたコードが無駄になる危険を減らすことができるのである。また、コーディングしていて正しい仕様が分からなくなったときも、テストのコードを見て疑問を晴らすことができる。人間の言葉はとかくあいまいになりがちだが、テスト・メソッドはプログラム言語で書くので、あいまいさが入り込みにくいのである。

 同じように2つのテスト・メソッドが続いている。30行目のTestSetScoreメソッドは、主にsetScoreメソッドの結果を試すものだ。ここでは、条件を変えてsetScoreメソッドを3回呼び出している。このうち1回目は、最初に設定した情報はそのまま登録されるべきことを示している。2回目はわざと、1回目より低いポイントを渡しているが、これは「より低いポイントは登録されてはならない」という仕様を確認するためのものだ。そして、逆により高いポイントなら登録されるべきことを、3番目のsetScoreメソッドの呼び出しが確認している。

 最後(45行目)のTestIfHighestメソッドは、あるポイントがハイスコアか否かを調べてbool値を返すisHighestメソッドを確認している。ここでは、仮に設定したハイスコアに対して、大きいとき、同じとき、小さいときのそれぞれのケースで、正しいbool値を返しているかを確認している。

 以上のテスト・メソッドをパスすれば、ハイスコア・クラスは正常に機能していると考えてよいだろう。もちろん、もっと厳密に試そうとすれば、やるべきことは山ほどあるが、すべて書くことは現実的ではない。プログラマーから見て特に重要なことを重点的にテスト対象に選ぶのがよい。

空のクラス・メソッドを書く

 さて、テスト・メソッドが書けたとはいえ、これだけではコンパイルを通らない。何しろ呼び出し先のクラスがまだ存在しないのだから仕方がない。そこで、呼び出し先クラスを作成して、コンパイルできるようにしよう。

 まず、HighScoreクラスをプロジェクトに追加する。

[新しい項目の追加]ダイアログ
HighScoreTest.csと同様に、HighScore.csを追加する。

 その中に、コンパイル・エラーが出ないようにするための最低限度のコードだけを書き込む。以下のような感じだろうか。

 1: using System;
 2:
 3: namespace MyGame
 4: {
 5:   public class HighScore
 6:   {
 7:     public string name;
 8:     public int point;
 9:     public void setScore( string name, int point )
10:     {
11:     }
12:     public bool isHighest( int point )
13:     {
14:       return false;
15:     }
16:   }
17: }
HighScoreクラスのソース・コード
コンパイル・エラーが出ないようにするための最低限度のコードだけを書き込んである。ハイスコアを保持するためのプログラム・コードはまだ実装されていない。

 くどくど説明を要することは何もないだろう。7〜8行目のメンバ変数は、アクセサを付けないと気持ちが悪いかもしれないが、当面は放置しておこう。いまのところ、クラスの実装の良しあしは考えないでおく。

 これでエラーなくビルドできるようになった。EXEファイルも生成されたことだろう。これで、とうとう今回のテーマであるNUnitを実行する準備が整ったのである。

 ここで、「ちょっと待て」と思った人もいると思う。まだ何も具体的な実装コードを書き込んでいないのに、NUnitを実行してしまうというのか、と。まさにそのとおり。いまこそが、NUnitを実行すべき最初のときなのである。

関連記事(Insider.NET内)
ソフト開発を成功させる1つの方法
 
関連リンク
NUnitの入手先

関連書籍
XPエクストリーム・プログラミング入門 ソフトウェア開発の究極の手法
(ISBN:4-89471-275-X)
XPエクストリーム・プログラミング導入編 XP実践の手引き
(ISBN:4-89471-491-4)
XPエクストリーム・プログラミング実践記 開発現場からのレポート
(ISBN:4-89471-469-8)
XPエクストリーム・プログラミング実行計画
(ISBN:4-89471-341-1)
 

 INDEX
  [.NET Tools]
  NUnit入門 Test Firstのススメ
     1.まず環境を準備する
   2.何はさておき、最初にテストを書こう
     3.NUnitを実行する
     4.唐突な仕様変更
 
インデックス・ページヘ  「.NET Tools」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間