SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

C#で始めるテスト駆動開発入門

状態を持つクラスをテストファーストする

C#で始めるテスト駆動開発入門(10)

ダウンロード CsTdd10.zip (122.5 KB)

状態を持つクラスをテストファーストする戦略

 クラスが状態を持っていないのならば、そのメソッドは素直にテストファーストで作れます。そのスペックは、第1回で行ったようにメソッドのシグネチャと反応を網羅するだけで済みます。

 クラスが状態を持つ場合、その状態を持つメンバー変数は一般には隠蔽しますから、スペック(外部設計)にそのメンバー変数は登場しません。つまり、クラスが持つ状態やその状態に依存するメソッドをテストするのは難しいのです。そこで筆者は、次のような戦略で対処しています。

  1. スペック上、状態へのアクセスが必要な場合:
    例えば、TextBoxコントロールのTextプロパティは、(直接ではないにしても)状態の読み書きをスペックとして可能にしています。このような場合は、そのプロパティだけをテストファーストで作ることができます。
  2. スペック上、状態の変化が観測可能な場合:
    状態を変更するメソッドやプロパティと、状態を観測できるメソッドやプロパティが分かれている場合です。例えば、StackクラスのPushメソッドとPeekメソッドなどです。このような場合は、変更と観測のペアをまとめてテストファーストで作ります。
  3. スペック上、状態が観測できない場合:
    そのような場合でも、状態が観測できないとテストできないときがあります。例えば「インスタンス化時の時刻を記憶し、そのミリ秒の上2桁を返すプロパティ(乱数の代用)」といったような場合です。このようなときには#ifディレクティブを使って、テストのときだけスコープを変えたり、プローブ注4を製品コードに挿入したりします(具体例は後述)。そのほか、リフレクションを利用してprivateメンバーにアクセスすることも可能ですが、手間が掛かるので、製品コードがよほど読みづらくならない限り筆者は使っていません。

 また、クラスの状態には、独立した状態と従属した状態があります。例えば、TextBoxコントロールのTextプロパティは、他の状態に影響されません。対して、Countプロパティは、Textプロパティに従属しています。Textプロパティを変更すると、Countプロパティが変化します。このようなときは、まず独立した状態をテストファーストで作り、その後で従属した状態に取り掛かるようにします。逆の順序では作れませんし、いっぺんに作ろうとすると、考慮すべきことが複雑になるので困難さが増してしまいます。

注4 プローブ(probe)

 筆者の命名。探針。状態を探るためのメソッドを製品コードに挿入する技法。製品コードが煩雑になるため、必要なときに限って使います。なお、テストのための変更を製品に加えることは、ソフトウェアに限ったことではありません。ハードウェアの開発でも、例えば試験装置に固定するための「出っ張り」を機械部品に付加したり、テスト用の電気信号を取り出すためのピン(端子)を電子回路に追加したりといったことが行われています。テストできない(=スペックを満たしていると確認できない)モノは出荷できませんから。

補足:スペック上は状態が観測できない場合の例

 先述した「インスタンス化時の時刻を記憶し、そのミリ秒の上2桁を返すプロパティ(乱数の代用)」をテストファーストする例です。

 まず、スコープを変えてしまう方法から。テストコードと製品コードを示します。

テストコード: スコープを変えてしまう方法
[TestClass]
public class スコープを変えるサンプルTest
{
#if DEBUG
  [TestMethod]
  public void InstanceTest()
  {
    var o = new CsTdd08App.スコープを変えるサンプル();

    // アサート:インスタンス化時に何か日時がセットされているはず
    // (デバッグビルド時はアクセス可能)
    Assert.IsTrue(DateTime.MinValue < o._startTime);
  }

  [TestMethod]
  public void RandomNumberTest()
  {
    var o = new CsTdd08App.スコープを変えるサンプル();

    // 値をセット(デバッグビルド時はアクセス可能)
    o._startTime = new DateTime(2014, 10, 10, 1, 23, 45, 678);

    // アサート:セットした値を使った結果になるはず
    Assert.AreEqual<int>(67, o.RandomNumber);
  }
#endif
}
製品コード: スコープを変えてしまう方法
public class スコープを変えるサンプル
{
  // このメンバー変数は、リリースビルド時にprivateになる
#if DEBUG
  public
#else
  private 
#endif
    DateTime _startTime = DateTime.Now;

  public int RandomNumber
  {
    get
    {
      return _startTime.Millisecond / 10;
    }
  }
}

 上のスコープを変える方法は、製品コードがご覧のとおりに読みづらくなります。次に示すプローブによる方法は、これに比べると読みやすいです。テスト用であることを示すため、「test_StartTime」という変なプロパティ名にしています。

テストコード: プローブを使う方法
[TestClass]
public class プローブを使うサンプルTest
{
#if DEBUG
  [TestMethod]
  public void InstanceTest()
  {
    var o = new CsTdd08App.プローブを使うサンプル();

    // アサート:インスタンス化時に何か日時がセットされているはず
    // プローブを使ってDateTime.MinValue以外の値になっていることを確認する
    Assert.IsTrue(DateTime.MinValue < o.test_StartTime);
  }

  [TestMethod]
  public void RandomNumberTest()
  {
    var o = new CsTdd08App.プローブを使うサンプル();

    // プローブから値をセット
    o.test_StartTime = new DateTime(2014, 10, 10, 1, 23, 45, 678);

    // アサート:プローブからセットした値を使った結果になるはず
    Assert.AreEqual<int>(67, o.RandomNumber);
  }
#endif
}
製品コード: プローブを使う方法
  public class プローブを使うサンプル
  {
    private DateTime _startTime = DateTime.Now;

    public int RandomNumber
    {
      get
      {
        return _startTime.Millisecond / 10;
      }
    }

#if DEBUG
    #region テスト時だけ利用するコード
    // このように下端に固めておくと、製品コードを読むときの邪魔にならない
    public DateTime test_StartTime
    {
      get { return _startTime; }
      set { _startTime = value; }
    }
    #endregion
#endif
  }

 上のプローブの例は、メンバー変数にアクセスするだけの簡単なものです。その他に、例えば任意の場所で例外を出したり、コード中の特定箇所を実行した回数をカウント注5したりなどと、さまざまな目的に利用できます。

注5 コード中の特定箇所を実行した回数をカウントする

 例えば、ソートするメソッドを作るとき、想定したアルゴリズムで動作しているかを確認するために、データを比較した回数を検査しなければならない場合があります。とりあえずソートできるメソッドをテストファーストで作り、次いで、「想定しているアルゴリズムにこのデータを与えたならばX回の比較で済むはず」というテストを書いてREDにし、アルゴリズムを作り込むわけです。

次のページ
例題:FizzBuzzアプリのビューモデル

この記事は参考になりましたか?

C#で始めるテスト駆動開発入門連載記事一覧

もっと読む

この記事の著者

biac(ばいあっく)

HONDA R&Dで自動車の設計をやっていた機械屋さんが、技術の進化スピードに魅かれてプログラマーに。以来30年ほど、より良いコードをどうやったら作れるか、模索の人生。わんくま同盟の勉強会(名古屋)で、よく喋ってたりする。2014/10~2019/6 Microsoft MVP (Windows Devel...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8238 2014/11/12 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー