VS 2008単体テスト機能でテスト駆動開発/NUnitからの移行特集:Visual Studio 2008単体テスト機能徹底活用(後編)(1/3 ページ)

VS 2008 Pro版に搭載されている単体テスト機能を使ってテスト・ファーストを実践。NUnitからのテスト移行についても考察。

» 2008年12月19日 00時00分 公開
[尾崎義尚,]
特集:Visual Studio 2008単体テスト機能徹底活用
Insider.NET

 

「特集:Visual Studio 2008単体テスト機能徹底活用」のインデックス

連載目次

 本稿の前編では、Visual Studio 2008 Professional Edition(以下、VS 2008)に搭載されるようになった単体テスト機能の機能項目を網羅的に解説した。

 後編となる今回では、この単体テスト機能を使ったテスト駆動開発の手順について紹介する。また後半では、NUnitからのテスト・コードの移行の手順についても解説する。

テスト駆動開発に挑戦

 前編ではあまり触れなかったが、単体テストはアジャイル開発における「テストファースト」や「テスト駆動開発」と呼ばれる開発手法の普及に合わせて大きく注目されるようになった。

 テスト駆動開発では、最初に(システム本体ではなく)テスト・コードを記述する。テスト・コードの記述がすべて終わると、次にテストが失敗するように本体のコードを実装する。ここで1度自動テストを実施して、実際にテストが失敗するのを確認する。

 そして次に、テストが成功するようにコードを実装して、テストを実施する。きちんと仕様どおりに動くコードが記述できたら、次にコードをきれいにするためにリファクタリングを実施する。リファクタリングが終わると、修正後のコードが正しく動作することを確認するために再度テストを実施する。

 ここまでの流れをテスト駆動開発では、「レッド/グリーン/リファクタリング」と呼ぶ。多くのテスト・ツールでは、テストの失敗がレッドで、テストをすべてパスした状態がグリーンで示されるためだ。

テスト駆動開発のサイクル
レッド、グリーン、リファクタリングの流れで開発を行う。

 では、VS 2008を使って実際にテスト駆動開発を行ってみよう。

■1. 仕様確認

 テスト駆動開発は、単純にテストを先に記述することではなく、テスト・コードの記述により、あらかじめ検討した仕様の実装も検討する。

 ここでは商品(Goods)クラスを作成して、購入数で割引率の異なる商品単価を取得するGetUnitPriceメソッドを作っていく。商品単価は10個以上購入で5%割引、30個以上で10%割引、50個以上で15%割引とする。また、単価の端数は切り捨てるものとする。

■2. テスト・コードの作成 〜 テストの実施(レッド)

 最初に行うのはテスト・コードの作成だが、テスト対象となるクラスをあらかじめ作っておくとテスト・コードの作成が楽である。ここでは、まずクラス・ライブラリを作っておく。

 これには、テンプレートとして「クラス ライブラリ」を使用したプロジェクトを作成する。「Class1.cs」が作成されるので、ファイル名を「Goods.cs」に変更しておこう。確認ダイアログで、[Yes]をクリックすると、ソース・コード内の「Class1」も、「Goods」に変更される。

ファイル名変更時の確認ダイアログ
[Yes]をクリックすると、クラス名が変更される。

 次にコード・エディタ上でクラスを右クリックして、単体テスト・プロジェクトを作成する。以下にその手順を示す。


クラス内で右クリックして[単体テストの作成]を実行



テスト用のプロジェクトの名前を入力

単体テスト・プロジェクトを作成する
ソース・コードを右クリックして、単体テストを作成すると、単体テスト・プロジェクトとテスト・クラスが作成される。

 以上により、テスト・クラスが表示され、(Goodsクラスの)コンストラクタ用のテスト・コードが作成される。コンストラクタのテストが必要ならここに記述すればよいし、必要なければメソッドを削除すればよい。

 次に、単体テストのコードを記述していく。テスト・メソッドを作成して、コードを記述するが、この時点では本体のコード(実装予定のGetUnitPriceメソッド)を実装していないため、当然ながらIntelliSenseにはそのメソッドは表示されない。

テスト・コードを記述する
この時点では、本体のコードが実装されていないため、IntelliSenseにメソッドが表示されない。

 テスト・コードを書き上げると、存在していないメソッドが赤字で表示される。そこにカーソルを移動し、修正候補部分をクリックすると「メソッド・スタブ」を生成できる。


赤字のGetUnitPrice部分にカーソルを移動



クリックしてメソッド・スタブ生成の項目を選択


メソッド・スタブを生成する
テスト・コードの修正候補部分をクリックして、メソッド・スタブを生成する。

 メソッド・スタブの生成を実行すると、Goodsクラス側にGetUnitPriceメソッドが生成される。

public int GetUnitPrice(int p)
{
  throw new NotImplementedException();
}

メソッド・スタブの生成により作成されたGetUnitPriceメソッド(C#)

 ここまでできたら、単体テスト・メソッドを作成していく。ここでは、次のような境界値のテストを作成していく(単純化のため、割引なしの商品単価は120円としている)。

[TestMethod()]
public void GetUnitPriceTest9()
{
  Goods goodsInstance = new Goods();
  int actual = goodsInstance.GetUnitPrice(9);
  Assert.AreEqual<int>(120, actual, "9個では割引がない");
}

[TestMethod()]
public void GetUnitPriceTest10()
{
  Goods goodsInstance = new Goods();
  int actual = goodsInstance.GetUnitPrice(10);
  Assert.AreEqual<int>(System.Convert.ToInt32(120 * 0.95),
                        actual, "10個の購入で5%割引");
}

[TestMethod()]
public void GetUnitPriceTest29()
{
  Goods goodsInstance = new Goods();
  int actual = goodsInstance.GetUnitPrice(29);
  Assert.AreEqual<int>(System.Convert.ToInt32(120 * 0.95),
                        actual,"29個では、5%割引");
}

[TestMethod()]
public void GetUnitPriceTest30()
{
  Goods goodsInstance = new Goods();
  int actual = goodsInstance.GetUnitPrice(30);
  Assert.AreEqual<int>(System.Convert.ToInt32(120 * 0.90),
                        actual, "30個では、10%割引");
}

[TestMethod()]
public void GetUnitPriceTest49()
{
  Goods goodsInstance = new Goods();
  int actual = goodsInstance.GetUnitPrice(49);
  Assert.AreEqual<int>(System.Convert.ToInt32(120 * 0.90),
                        actual, "49個では、10%割引");
}

[TestMethod()]
public void GetUnitPriceTest50()
{
  Goods goodsInstance = new Goods();
  int actual = goodsInstance.GetUnitPrice(50);
  Assert.AreEqual<int>(System.Convert.ToInt32(120 * 0.85),
                        actual, "50個では、15%割引");
}

GetUnitPriceメソッドに対するテスト・コード(C#)
境界値の単価が正しく取得できることを確認している。

 テスト・コードを作成するときに意識すべきことは次の点だ。

  • 最小限のテスト・コードを記述する
  • 仕様のみをテストする
  • 実装を想像してテスト・コードを作成する

 仕様にないことまで確認するテスト・コードを記述する必要はない。逆に、テストする必要があるのであれば、仕様を追加する必要がある。つまり、最初に挙げた仕様を確認するために最低限必要なテストのみをここに記述したことになる。

 さて、それではテストを実施してみよう。本体側の実装が終わっていないので、当然すべてのテストは失敗する。

テストの実施結果
すべてのテストに失敗することが分かる。

 これで「テストの実施(レッド)」が完了したことになる。

■3. コードの作成 〜 テストの実施(グリーン)

 さて、レッドの段階が完了すると、次に本体のコードの作成に入ることになる。コードの実装は通常のコーディングとまったく変わらない。仕様に従って素直に実装したのが以下のコードである。

public int GetUnitPrice(int quantity)
{
  // 50個以上で15%割引
  if (quantity >= 50)
  {
    return Convert.ToInt32(120 * 0.85);
  }

  // 30個以上で10%割引
  if (quantity >= 30)
  {
    return Convert.ToInt32(120 * 0.90);
  }

  // 10個以上で5%割引
  if (quantity >= 10)
  {
    return Convert.ToInt32(120 * 0.95);
  }

  return 120;
}

GetUnitPriceメソッドの実装(C#)
仕様どおり素直に実装している。

 この状態で再度テストを実施してみよう。

本体コード実装後のテスト実施結果
すべてのテストがパスしたグリーンの状態であることが分かる。

 これですべてのテストが成功したグリーンの状態になった。

■4. リファクタリング 〜 テストの実施(グリーン)

 すべての仕様を満たし、テストがグリーンの状態になったら次はリファクタリングである。

 リファクタリングとは、動作はそのままでソース・コードを“きれい”にする作業である。テスト駆動開発では、「重複したコードを排除する」ことが1つの大きな目標として挙げられている。

 C#には(開発言語としてC#を選択してVS 2008のプロジェクトを作成した場合)、リファクタリングを行うための「リファクタ機能」が用意されていて、コード・エディタ上の右クリック・メニューから実行することができる。

C#のリファクタ機能
右クリック・メニューから[リファクタ]を選択することで、機能を一覧表示できる。

 Visual Basicには、リファクタ機能が用意されていないが、Refactor! for Visual Basic 2008という製品が無料で提供されている。これをインストールすれば、C#と同じように右クリック・メニューで実行することができるが、こちらは実行可能なメニューのみが表示される。

Refactor! for Visual Basic 2008のメニュー
実行可能なメニューのみが表示される。

 リファクタリングが完了すると、テストがすべて正しく動作することを再度確認する。すでにテストはパスしているので、リファクタリングによるデグレード(新たなバグの作り込み)はすぐに判明する。

リファクタリング後のテストの実施
すべてのテストにパスすると、リファクタリングが成功したことになる。

 以上で簡単ではあるが、テスト駆動開発の一連の流れを見てきた。この後は、機能が追加されるたびにテスト・コードの作成→確認→実装という、同じ流れをたどることになる。

       1|2|3 次のページへ

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。