SHOEISHA iD

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

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

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

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

TDDしにくいモノは切り離せ! ~ 現在日時の扱い方

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

ダウンロード CsTdd06.zip (10.7 KB)

基本: 現在時刻の取得を別にする

 制御できないDateTimeOffset.Nowを、とにかく切り離してみましょう。実装を、DateTimeOffset型の引数を受け取るメソッドにすれば、一応は分離することができます。

テストコード: ともかく分離してみる
[TestCase]
public void 時刻をフォーマットするTest_時刻の取得をともかく分離してみる() {
  var givenTime = new DateTimeOffset(new DateTime(2012, 8, 8, 14, 3, 0));
  var expected = "14時 03分 00秒";

  var result = Class1.時刻をフォーマットする(givenTime);
  Assert.AreEqual(expected, result);

  //製品コードでは Class1.時刻をフォーマットする(DateTimeOffset.Now) のように使う。
}
製品コード: テスト可能な実装になった!
public static string 時刻をフォーマットする(DateTimeOffset givenTime) {
  return givenTime.ToString("hh時 mm分 ss秒");  //注:これはREDになる
}

 テストができるようになり、バグも見つけられるようになりました。時刻をフォーマットする()メソッドを呼び出している製品コードを、現在時刻に関係なくテストできるのならばこれで十分です。そうでなければ、呼び出している製品コードのテストも、前と同じ問題を抱えてしまうことになります。

 根本的に解決するには、DateTimeOffset.Nowをラップしたメソッドを実装して、完全に分離します。その例をいくつか紹介します。

応用1: 現在時刻取得メソッド + テスト用実装

 まず、DateTimeOffset.Nowをラップしたプロパティを実装します。これはハードウェアに密着しているため通常のテストファーストは困難で、手動テストで動作を確認します。

テストコード: このテストは目視で確認する
[TestCase]
public void Get現在時刻Test_手動でテスト() {
  var nowTime = (new Class2()).現在時刻;
  Console.WriteLine(nowTime);

  Assert.Inconclusive("コンソール出力を目視確認!");
}
製品コード: DateTimeOffset.Nowをラップしたプロパティ
public DateTimeOffset 現在時刻{
  get { return DateTimeOffset.Now; }
}

 この現在時刻プロパティを使って、冒頭のフォーマット済み現在時刻プロパティを書き直すと次のようになります。

製品コード: フォーマット済み現在時刻プロパティを書き直した
public string フォーマット済み現在時刻 {
  get {
    return 現在時刻.ToString("hh時 mm分 ss秒");
  }
}

 このままではテストできないのは、先ほどと変わりません。現在時刻プロパティの中に、テスト用の実装を仕込んでいきます。

 まず、製品コード側でプロジェクトのプロパティを開いて、条件付きコンパイルシンボルを定義します。

製品コード: プロジェクトのプロパティ
条件付きコンパイルシンボル: UNIT_TEST

 そうしたら、現在時刻プロパティのコードを修正します。

製品コード: 現在時刻プロパティにテスト用実装を追加
#if UNIT_TEST
    public DateTimeOffset? テスト用実装_現在時刻 { get; set; }
#endif

    public DateTimeOffset 現在時刻{
      get { 
#if UNIT_TEST
        if (テスト用実装_現在時刻.HasValue)
          return テスト用実装_現在時刻.Value;
#endif
        return DateTimeOffset.Now; 
      }
    }

 このテスト用実装は、テストコードからテスト用実装_現在時刻プロパティに値をセットしてやると、現在時刻プロパティはその値を返すようになっています。セットしなければ、本来のコードが実行されます。なお、今回は省略しましたが、テスト用実装も複雑ならばテストファーストで作り込みます。

注意点:

  • リリースビルドには入らないようにする
  • テストコードからテスト用の値をセットしなかったときは、本来のコードが実行される(従って、手動テストできる)ようにする
  • 静的なコードになってしまう場合は、同時実行時の挙動に注意

 これで、現在時刻を使うフォーマット済み現在時刻プロパティをテストするコードが書けるようになりました。

テストコード: テスト実装を使う
[TestCase]
public void Getフォーマット済み現在時刻Test() {
  var givenTime = new DateTimeOffset(new DateTime(2012, 8, 8, 14, 3, 0));
  var expected = "14時 03分 00秒";

  var target = new Class2() { テスト用実装_現在時刻 = givenTime, };
  var result = target.フォーマット済み現在時刻;

  Assert.AreEqual(expected, result);
}

 なお、ここでは説明の都合で順序を前後させました。きちんとTDDを進めていくなら、次の順序になります。

  1. Get現在時刻Test_手動でテスト()
  2. 現在時刻プロパティ(テスト実装なし)
  3. 現在時刻プロパティ:テスト用実装のテストコード(今回は省略)
  4. 現在時刻プロパティ:テスト用実装
  5. Getフォーマット済み現在時刻Test()
  6. フォーマット済み現在時刻プロパティ

 このようにテスト用の実装を仕込んでしまう方法は、お手軽ではありますが、複雑なものになると対応しきれません。例えば通信ハードウェアに密着した実装を考えてみてください。本来の実装自体が複雑な上に、そこに複雑な応答をシミュレートするテスト用実装を埋め込んだら… きっとスパゲッティコードになってしまうでしょう。本来の実装と、テスト用の実装をきれいに分離する方法が必要になってきます。

次のページ
応用2: 現在時刻プロパティのインターフェースを定義して使い分ける

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

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

もっと読む

この記事の著者

biac(ばいあっく)

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

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/6724 2012/08/24 14:00

イベント

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

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

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

メールバックナンバー