特集

.NET開発者のための開発プロセス入門(後編)

.NET開発でアジャイルを導入するための実践テクニック

福井 厚 & 小井土 亨
2004/12/22
Page1 Page2 Page3 Page4

●ステップ1:テスト用コードを先に書く

 顧客の注文を処理するので、まずは顧客クラスのテスト・コード作成から始める。テスト・コード用のプロジェクト(PO.Test)にtestCustomerクラスを追加し、以下のような単体テストのためのコードを記述する。

 この単体テストは、顧客の名前を得るための、顧客クラスのNameプロパティをテストするためのものだ。

using System;
using NUnit.Framework;
using PO.Core;

namespace PO.Test
{
  [TestFixture]
  public class testCustomer
  {
    Customer target;

    [SetUp]
    public void SetUp()
    {
      target = new Customer("1000", "user1");
    }

    [Test]
    public void GetCustomerNameTest()
    {
      Assert.AreEqual("user1", target.Name, "顧客の名前の取得");
    }
  }
}
顧客クラスのテスト・コード例
テスト・ファーストではテスト・コードを実装コードよりも先に記述する。

 では、testCustomerクラスに付けられたTestFixture属性によって、このクラスがテスト用メソッドを含むクラスであることをNUnitに伝えている。

 JUnitなどほかのxUnitテスト・フレームワークがTestCaseクラスから継承しなければならないのに対して、NUnitではこのように.NET Frameworkの属性をうまく利用することで継承を用いなくてもよくなっている。

 TestFixture属性を指定したクラスには若干の制約があるので注意が必要だ。その制約とは、クラスのアクセシビリティがpublicでなければならないこと、デフォルト・コンストラクタを持たなければならないこと、NUnitが何回もコンストラクタを呼び出す可能性があるためコンストラクタはスタティック変数を変更するなどの副作用を持たないようにすること、などである。

 のメソッドに付けられたSetUp属性は、指定したメソッドがSetUpメソッドであることをNUnitに伝える。SetUpメソッドとは各テスト用メソッドが呼び出される前に必ず呼び出されるメソッドである。これと対をなすTearDownメソッドもある(TearDown属性を記述する)。こちらはテスト用メソッドの呼び出し後に毎回呼び出されるので、クリーンアップ用のコードを記述するのに利用できる。

 SetUpメソッドでは、のようにテスト対象クラスのインスタンスを初期化する処理などを行うとテスト・コードの重複を防ぐことができる。

     target = new Customer("1000", "user1");

 ここで、まだテスト対象のクラス(この場合にはCustomerクラス)を記述していないことに注意してほしい。テスト・ファーストではテスト対象となるクラスのインターフェイスを考えながらテスト用のコードを記述するので、自然に利用する側の視点でコードを記述することになる。このテスト・コードは、後でクライアント用コードを記述するときの参考としても役に立つ。

 では、Test属性によって、このGetCustomerNameTestメソッドが実際のテスト・コードであることをNUnitに伝えている。JUnitでは「test」で始まるメソッドをリフレクションで発見してテスト・コードであることを判断しているが、NUnitでは属性を利用することでメソッド名を自由に付けることができる。

 では、NUnitが提供するAssert.AreEqualメソッドでテストを記述している。

     Assert.AreEqual("user1", target.Name, "顧客の名前の取得");

 このようにAssert(表明)を使ってテストを記述する。Assert.AreEqualメソッドのパラメータには、期待される値、実行結果の値、テストが失敗したときに表示されるメッセージを指定する。ここではユーザー名として期待する値(user1)がNameプロパティから得られれば、このテストは成功となる。Assertメソッドにはほかにも多くのメソッドがあるので、詳しくはNUnitのドキュメントを参照してほしい。

●ステップ2:取りあえずの顧客クラスを作成

 このままではコンパイルが通らないので、取りあえずテスト対象の顧客クラス(Customer)を実装しよう。次のようにして、PO.CoreプロジェクトにCustomerクラスを追加する。

using System;

namespace PO.Core
{
  public class Customer
  {
    public Customer(string customerId, string name)
    {
    }

    public string Name
    {
      get{ return null; }
    }
  }
}
顧客クラスのコード例
コンパイルを通すために取りあえずの実装を行う。

 ビルドが成功したらテストを実行し、テストが失敗することを確認する。ここでは、失敗させることが重要だ。テストの失敗を確認することでテスト用コードが正しく実行されていることが分かるからである。

 テスト・コードが失敗すると、次のような出力が得られる。

TestCase 'PO.Test.testCustomer.GetCustomerNameTest' failed: 顧客の名前の取得
    expected:<"user1">
    but was:<(null)>

 テストの実行はNUnitで提供されているNUnit-GUI.EXEやNUnit-Console.EXEを実行する方法が一般的だが、TestDriven.NETをインストールするとVisual StudioのIDEのコンテキスト・メニューから実行でき便利だ。

●ステップ3:テストをパスする顧客クラスを実装

 次に、テストをパスさせるためにCustomerクラスにコードを追加しよう。

 この例では単純にコンストラクタで渡されたパラメータをメンバとして保存しておいて、Nameプロパティでその値を返すだけでよい。修正したらテストがパスすることを確認する。テスト・コードが成功すると、次のように出力される。

1 succeeded, 0 failed, 0 skipped, took 0.01 seconds.

 実際には、単純なプロパティのセッター(setアクセサ)・ゲッター(getアクセサ)用コードまでテストで確認する必要はない。以上は、あくまで説明のための例だと思ってほしい。

●ステップ4:商品クラスのテストと実装

 次は注文明細に登録する商品クラスを実装するためのテストを書こう。

 PO.TestプロジェクトにtestProductクラスを追加する。ここでも先にテストを書く。商品クラスはまだ記述していないが、テスト・コードを書く時点で商品クラスはUnitPriceプロパティで単価を返すことを想定している。

[TestFixture]
public class testProduct
{
  ……中略……
  [Test]
  public void GetUnitPriceTest()
  {
    Product target = new Product("1001", "商品1", 100);
    Assert.AreEqual(100, target.UnitPrice, "単価の取得");
  }
  ……中略……

}
商品クラス(Productクラス)をテストするtestProductクラス
テストを書く時点でUnitPriceプロパティが単価を返すことを想定している。

 PO.CoreプロジェクトにProductクラスを追加し、UnitPriceプロパティを追加する。テストを実行し、すべてのテストがパスすることを確認する。

●ステップ5:注文明細クラスのテストと実装

 続いては、注文明細クラス用のテストを記述する。PO.TestプロジェクトにtestListItemクラスを追加する。ここではアイテムの金額を計算するSumAmountメソッドを想定してテスト・コードを記述している。

[TestFixture]
public class testLineItem
{
  ……中略……
  [Test]
  public void SumAmountTest()
  {
    Product pen = new Product("001", "ボールペン", 100);
    LineItem target = new LineItem(pen, 2);
    Assert.AreEqual(200, target.SumAmount(), "アイテムの金額");
  }
  ……中略……
}
注文明細クラス(ListItemクラス)をテストするtestLineItemクラス
LineItemクラスにこれから実装するSumAmountメソッドのテスト部分。

 PO.CoreプロジェクトにLineItemクラスを追加し、テストをパスするようにコードを記述する。

public class Customer
{
  ……中略……
  public decimal SumAmount()
  {
    return this._product.UnitPrice * this._itemCount;
  }
  ……中略……
}
注文明細クラス(LineItemクラス)の実装の一部

●ステップ6:注文クラスのテストと実装

 テストがパスしたら、次は注文クラス用のテストを記述しよう。PO.TestプロジェクトにtestOrderクラスを追加する。

[TestFixture]
public class testOrder
{
  Order target;

  [SetUp]
  public void SetUp()
  {
    Customer customer = new Customer("1000", "user1");
    target = new Order(new DateTime(2004,12,1), customer);
  }

  [Test]
  public void SubtotalTest()
  {
    target.AddItem(
      new LineItem(new Product("100","鉛筆", 30), 5));
    target.AddItem(
      new LineItem(new Product("101", "消しゴム", 50), 2));

    Assert.AreEqual(250, target.Subtotal(), "注文の合計金額");
  }
}
注文クラス(Orderクラス)をテストするtestOrderクラス

 PO.CoreプロジェクトにOrderクラスを追加し、テストにパスするようにSubtotalメソッドの実装を行う。

public class Order
{
  ……中略……
  public decimal Subtotal()
  {
    decimal subtotal = 0;
    foreach(LineItem item in lineItems)
    {
      subtotal += item.SumAmount();
    }
    return subtotal;
  }
  ……中略……
}
注文クラス(Orderクラス)の実装の一部

 注文明細の一部を削除しても合計金額が正しいかどうかのテストを追加してみる。

public class testOrder
{
  ……中略……
  [Test]
  public void RemoveAndSubtotalTest()
  {
    target.AddItem(
      new LineItem(new Product("100", "鉛筆", 30), 5));
    target.AddItem(
      new LineItem(new Product("101","消しゴム", 50), 2));
    target.RemoveItem("100");

    Assert.AreEqual(100, target.Subtotal(), "注文の合計金額");
  }
  ……中略……
}
注文明細から注文を削除するRemoveItemメソッドのテストをtestOrderクラスに追加する

 OrderクラスにRemoveItemメソッドを追加し、テストがパスするように実装する。

 このようにテスト・ファーストでは、少しずつテストを書き、少しずつ実装する手順を繰り返す。ペア・プログラミングを行うとテストばかりを多く書きすぎたり、テストを書かずに実装したりする間違いをパートナーから指摘してもらえるので、より効果的である。

 以上の単体テストの自動化とテスト・ファーストのプラクティスが実践できたら、その次に、Mockオブジェクトの利用とリファクタリングを行おう。


 INDEX
  [特集] .NET開発者のための開発プロセス入門(前編)
    1.アジャイル開発プロセスが誕生するまで
    2.アジャイル型開発プロセスとは何か?
    3..NET開発者がアジャイル開発プロセスを導入できない理由
    4.アジャイル開発プロセスの導入
  [特集] .NET開発者のための開発プロセス入門(後編)
    1.1人からでも導入可能なローカル・ライトウェイト開発プロセス
  2.単体テストの自動化とテスト・ファースト
    3.Mockオブジェクトとリファクタリング
    4.数名のチームで導入できるローカル・ライトウェイト開発プロセス
 


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 記事ランキング

本日 月間