ステップ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つ。十分にシンプルなクラスで、責務も投入されたお金を預かることだけです。よって、これ以上の分割を考える必要はまだ無いでしょう。この先は少なくとも、預り金を減らす方向のメソッドが追加されることになるでしょう。
ステップ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(格納されているジュースの情報を取得する)は、まとめてテストケースを書きます。
[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, }; } }