ステップ1 扱えないお金
これもコインメックだけの機能です。お題はこうなっています。
ステップ1 扱えないお金
- 想定外のもの(硬貨:1円玉、5円玉。お札:千円札以外のお札)が投入された場合は、投入金額に加算せず、それをそのまま釣り銭としてユーザに出力する。
ステップ1 0: 受け入れ可能な金種
このステップを進むためには、想定内のモノとは何かという定義が必要です。扱えるお金は何かがハッキリすれば、扱えないお金も判別できるというわけです。「○○、△△、□□以外の場合は、××する」という場合、まず「○○、△△、□□」をまとめて識別できるようになっているか(なっていなければそこから作る)、と考えるのがセオリーです。
メンバー変数にint型の配列として受け入れ可能な金種を定義し、Is受け入れ可能な金種()メソッドを実装します。テストケースを順番にRED→GREENにしていきますが、以下には最後の状態だけを示します。
[TestCase(1, false)] // 製品コードは return false; だけの仮実装
[TestCase(10, true)] // int配列を定義し、10だけをセット。メソッドはここで完成。
[TestCase(50, true)] // int配列に50を追記。以下、100,500,1000で繰り返す。
[TestCase(100, true)]
[TestCase(500, true)]
[TestCase(1000, true)]
public void Is受け入れ可能な金種Test(int 金種, bool 期待値) {
Assert.AreEqual(期待値, コインメック.Is受け入れ可能な金種(金種));
}
コインメッククラス
private static readonly int[] 受け入れ可能な金種
= new int[] { 10, 50, 100, 500, 1000, };
internal static bool Is受け入れ可能な金種(int 金種) {
return 受け入れ可能な金種.Contains(金種);
}
ステップ1 1: 受け入れられない金種は返却する
これでこのステップをクリアする準備はできました。ところで、「釣り銭としてユーザに出力する」というのはどういうことでしょう?とりあえず、お金を投入する()メソッドの戻り値を返却金額としておきましょう。
このテストケースには、預り金は変化しないというアサートを入れないと、製品コードのif文が書けません。
public void 扱えないお金を投入するTest() {
コインメック cm = new コインメック();
var 払戻金 = cm.お金を投入する(5);
Assert.AreEqual(5, 払戻金);
Assert.AreEqual(0, cm.預り金);
}
コインメッククラス
public int お金を投入する(int coin) { //void→intに変えた
//TODO: 戻り値が返金だというのは、分かりにくい!
if(Is受け入れ可能な金種(coin)){
預り金 += coin;
return 0;
}
return coin;
}
コインメックのクラス図
ここまでで、コインメッククラスは次の図のようになりました。
コインメックのクラス図(ステップ1終了時点)
状態として預り金を持っています。状態を変更するメソッドが1つと、状態に関係しないメソッドが1つ。十分にシンプルなクラスで、責務も投入されたお金を預かることだけです。よって、これ以上の分割を考える必要はまだ無いでしょう。この先は少なくとも、預り金を減らす方向のメソッドが追加されることになるでしょう。
ステップ2 ジュースの管理
このステップでは、商品ラックを作ることになります。お題は次のようになっています。
ステップ2 ジュースの管理
- 値段と名前の属性からなるジュースを1種類格納できる。初期状態で、コーラ(値段:120円、名前"コーラ")を5本格納している。
- 格納されているジュースの情報(値段と名前と在庫)を取得できる。
ステップ2 0: 「コーラ」を識別する
商品ラックには「コーラ」を格納するということですから、まずその「コーラ」を用意しなければなりません。「コーラ」の1缶1缶を識別する必要はなさそうですから、商品の種類としての「コーラ」が表現できればよさそうです。自販機が扱う商品全般をジュースクラスとし、「コーラ」はジュースクラスの1インスタンスとすればよいでしょう。
ジュースクラスのインスタンスは、名前Nameと値段Priceだけを固定で持っていればよいです。在庫の数は、ジュースクラスのインスタンス(商品の種類)が知っている必要はなく、商品ラックが分かっていれば良い事です。
コーラインスタンスの取得と、それがNameとPriceプロパティを持っていることを、1つのテストケースで書いてしまいましょう。この程度であれば、まとめてやってしまっても、混乱したり間違えたりすることはないでしょう。
ジュースクラスからコーラインスタンスを得る
[TestCase]
public void コーラTest() {
ジュース cola = ジュース.コーラ;
Assert.AreEqual("コーラ", cola.Name);
Assert.AreEqual(120, cola.Price);
}
ジュースクラス
public class ジュース {
public string Name { get; private set; }
public int Price { get; private set; }
private ジュース() {
// (void)
}
public static ジュース コーラ {
get {
return new ジュース(){ Name = "コーラ", Price = 120, };
}
}
}
ステップ2 1,2: 商品ラックにコーラを格納する
コーラの在庫を商品ラックに直接持たせてもよいのですが、後で複数種類のジュースを扱うことになる事は分かりきっているので、ビンも用意してしまいましょう。商品ラックはビンを持っており、ビンにジュースを格納するという形にします。
このステップ2の1(初期状態でコーラ5本を格納している)と2(格納されているジュースの情報を取得する)は、まとめてテストケースを書きます。
商品ラックのビンには初期状態でコーラが5本格納されている
[TestCase]
public void ConstructorTest_初期状態でコーラが5本() {
商品ラック rack = new 商品ラック();
Assert.AreEqual(5, rack.ビン.在庫数);
Assert.AreEqual("コーラ", rack.ビン.商品.Name);
// すぐに「ビン」は複数になるはずだが、とりあえずこれで。
}
ビンクラスと商品ラッククラス
internal class ビン {
public ジュース 商品 { get; internal set; }
public int 在庫数 { get; internal set; }
}
internal class 商品ラック {
public ビン ビン{ get; private set; }
public 商品ラック() {
ビン = new ビン() { 商品 = ジュース.コーラ, 在庫数 = 5, };
}
}
