SHOEISHA iD

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

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

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

TDDBC大阪の課題をC#でやってみる ~ クラス設計とTDD

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

  • X ポスト
  • このエントリーをはてなブックマークに追加

ステップ3 購入

 ここが今回の山場になります。コインメッククラスと商品ラッククラスに機能を追加しつつ、両者を連携させるコントローラークラスも作っていくことになります。

ステップ3 購入


  1. 投入金額、在庫の点で、コーラが購入できるかどうかを取得できる。
  2. ジュース値段以上の投入金額が投入されている条件下で購入操作を行うと、ジュースの在庫を減らし、売り上げ金額を増やす。
  3. 投入金額が足りない場合もしくは在庫がない場合、購入操作を行っても何もしない。
  4. 現在の売上金額を取得できる。
  5. 払い戻し操作では現在の投入金額からジュース購入金額を引いた釣り銭を出力する。

ステップ3 1: コーラが購入できるか判定する

在庫のチェック

 まず、在庫のチェックができなければなりません。これは、在庫を持っている商品ラッククラスの役目です。

 すぐにジュースの種類が増えることは分かっているので、それを先取りしたテストケースにしておきます。もしも真面目にやるのであれば、Is在庫有り()メソッドは引数無しで始めて、ジュースの種類が増えたときに引数を追加してください。

テストコード: コーラの在庫があるか
[TestCase]
public void Is在庫有りTest_コーラ() {
  商品ラック rack = new 商品ラック();
  Assert.IsTrue(rack.Is在庫有り(ジュース.コーラ));
}
製品コード: 商品ラッククラス
public bool Is在庫有り(ジュース kind) {
  return ビン.在庫数 > 0;
  //TODO: kindに応じた在庫チェック
}

投入金額が足りているか

 次に、投入金額が足りているかチェックできなければなりません。投入金額はコインメッククラスが知っています。しかし、コインメッククラスがジュースの値段まで知っているようでは、責務過剰です。コインメッククラスはお金のやり取りだけに専念してもらい、投入金額が足りているかどうかの判定は、新しくコントローラークラスを作ってそれにやらせることにしましょう。

 コントローラークラスは投入金額を知る必要がありますから、コインメックと連携しなければなりません。コントローラーをインスタンス化するときにコインメックを引き渡すことにしましょう。ちょっとややこしいので、仮実装→三角測量と進めます。

テストコード: 投入額不足で買えない
[TestCase]
public void Is購入可能Test_投入額不足で買えない() {
  コントローラー ctl = new コントローラー(new コインメック());
  ctl._coinMech.お金を投入する(100); //コーラは120円なので、買えない
  Assert.IsFalse(ctl.Is購入可能(ジュース.コーラ));
}
製品コード: コントローラークラス(仮実装)
internal class コントローラー {

  internal コインメック _coinMech;

  internal コントローラー(コインメック coinMech){
    this._coinMech = coinMech;
  }

  public bool Is購入可能(ジュース kind) {
    return false;
  }
}

 続いて、三角測量です。

テストコード: 投入額で買える
[TestCase]
public void Is購入可能Test_買える() {
  コントローラー ctl = new コントローラー(new コインメック());
  ctl._coinMech.お金を投入する(100);
  ctl._coinMech.お金を投入する(10);
  ctl._coinMech.お金を投入する(10);
  Assert.IsTrue(ctl.Is購入可能(ジュース.コーラ));
}
製品コード: コントローラークラス
public bool Is購入可能(ジュース kind) {
  if (_coinMech.預り金 < kind.Price)
    return false;

  //TODO: 在庫チェック

  return true;
}

 次に、Is購入可能()メソッドに在庫切れの条件を追加したいのですが、在庫を減らす機能をまだ実装していないので、テストできません。これは後に回します。

テストのリファクタリング

 ところで、テストコードにお金を投入する()が何行も連続で出てきます。これは面倒なので、ヘルパーメソッドを書いて、テストコードをリファクタリングしておきましょう。

テストコード: お金をいっぺんに投入するヘルパーメソッド
internal static class コインメックExtension {
  public static int お金を投入する(this コインメック coinMech, 
                                   int coin1, params int[] coins) {
    int 返却金 = 0;
    返却金 += coinMech.お金を投入する(coin1);
    foreach(int m in coins)
      返却金 += coinMech.お金を投入する(m);

    return 返却金;
  }
}
テストコード: 投入額で買える(リファクタリング後)
[TestCase]
public void Is購入可能Test_買える() {
  コントローラー ctl = new コントローラー(new コインメック());
  //ctl._coinMech.お金を投入する(100);
  //ctl._coinMech.お金を投入する(10);
  //ctl._coinMech.お金を投入する(10);
  ctl._coinMech.お金を投入する(100, 10, 10);
  Assert.AreEqual(true, ctl.Is購入可能(ジュース.コーラ));
}

ステップ3 2: コーラを購入する

 購入操作は、お客にジュースを提供し(在庫は減る)、預り金から代金を売上金額に移します。いっぺんに「購入」を作るのは複雑すぎるので、ジュース提供することと、代金を受け取ることに分けて作っていきます。

ジュースを提供する

 これは商品ラッククラスの仕事です。ジュースを提供する()メソッドを呼び出すとジュースが返され、在庫は1減ることになります。なお、ここでもジュースの種類が増えることを見越して、ジュースを提供する()メソッドに引数を与えています。

テストコード: ジュースを提供する
[TestCase]
public void ジュースを提供するTest_コーラ() {
  商品ラック rack = new 商品ラック(); //初期状態でコーラ5本
  Assert.AreEqual(ジュース.コーラ.Name, rack.ジュースを提供する(ジュース.コーラ).Name);
  Assert.AreEqual(4, rack.ビン.在庫数);
}
製品コード: 商品ラッククラス
public ジュース ジュースを提供する(ジュース kind) {
  ビン.在庫数--;
  return kind;
}

ジュースクラスのEquals()メソッドのオーバーライド

 ところで、上のテストケースでNameプロパティの比較にしているのは、ジュースオブジェクト同士の直接の比較ができないからです。ここでちょっと寄り道をして、ジュースクラスのEquals()メソッドなどを実装しておきましょう。

 Equals()メソッドなどをオーバーライドする方法については、次のMSDNのドキュメントを参考にしてください。

 これもテストケースを1つずつRED→GREENにして行きますが、最終的な状態だけを示します。テストケースの並び順は、作っていった順番です。

テストコード: Equals()メソッドのオーバーライド
[TestCase]
public void GetHashCodeTest() {
  int hash1 = ジュース.コーラ.GetHashCode();
  int hash2 = ジュース.コーラ.GetHashCode();
  Assert.AreEqual(hash1, hash2);
}

[TestCase]
public void EqualsTest_等しい() {
  ジュース cola1 = ジュース.コーラ;
  ジュース cola2 = ジュース.コーラ;
  Assert.AreEqual(cola1, cola2);
  Assert.IsTrue(cola1 == cola2);
}

[TestCase]
public void EqualsTest_nullとは等しくない() {
  ジュース cola = ジュース.コーラ;
  Assert.AreNotEqual(cola, null);
  Assert.IsTrue(cola != null);
  Assert.IsTrue(null != cola);
}

[TestCase]
public void EqualsTest_nullとnullは等しい() {
  ジュース cola = null;
  Assert.AreEqual(cola, null);
  Assert.IsTrue(cola == null);
  Assert.IsTrue(null == cola);
}
製品コード: ジュースクラス
public override int GetHashCode() {
  return (Name.GetHashCode() ^ Price.GetHashCode());
}

public override bool Equals(object obj) {
  var j = obj as ジュース;
  return (this == j);
}

public static bool operator ==(ジュース x, ジュース y) {
  if ((object)x == null && (object)y == null)
    return true;

  if ((object)x == null || (object)y == null)
    return false;

  return (x.Name == y.Name && x.Price == y.Price);
}

public static bool operator !=(ジュース x, ジュース y) {
  return !(x == y);
}

ジュースを提供する(再)

 ジュースオブジェクト同士の比較ができるようになったので、さきほどのテストケースを書き直しておきます。

テストコード: ジュースを提供する(再)
[TestCase]
public void ジュースを提供するTest_コーラ() {
  商品ラック rack = new 商品ラック(); //初期状態でコーラ5本
  //Assert.AreEqual(ジュース.コーラ.Name, rack.ジュースを提供する(ジュース.コーラ).Name);
  Assert.AreEqual(ジュース.コーラ, rack.ジュースを提供する(ジュース.コーラ));
  Assert.AreEqual(4, rack.ビン.在庫数);
}

次のページ
ステップ4 機能拡張

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

  • X ポスト
  • このエントリーをはてなブックマークに追加
C#で始めるテスト駆動開発入門連載記事一覧

もっと読む

この記事の著者

biac(ばいあっく)

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

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

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

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/6633 2012/06/29 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング