前回の授業でTDDのテストがエラーになる状態で終了しているので、まずはテストを実行してみる。
コーラの在庫を調べるテスト testJuiceStock() でエラーになっている。
VendingMachineの実装を修正してテストにパスするようにする。
0を返すところを5を返すようにするだけ。
public int getStock(String string) { return 5; }
次は、「ステップ3 購入」に進む。
「投入金額、在庫の点で、コーラが購入できるかどうかを取得できる。」
このテストを作成する。
@Test public void testCanBuy() { VendingMachine vm = new VendingMachine(); boolean b = vm.canBuy("コーラ"); assertThat(b, is(false)); }
クイックフィックスでcanBuy()メソッドを作成する。
public boolean canBuy(String string) { return false; }
テストにパスするので、200円を入れたら買えるというテストを追加する。
@Test public void testCanBuy() { VendingMachine vm = new VendingMachine(); boolean b = vm.canBuy("コーラ"); assertThat(b, is(false)); vm.putMoney(100); vm.putMoney(100); b = vm.canBuy("コーラ"); assertThat(b, is(true)); }
テストにパスするよう修正する。
public boolean canBuy(String string) { if (total >= 120) return true; return false; }
「ジュース値段以上の投入金額が投入されている条件下で購入操作を行うと、ジュースの在庫を減らし、売り上げ金額を増やす。」の部分をテストコードで表現する。
売上金額を増やすのはあとで追加する。
@Test public void testBuy() { VendingMachine vm = new VendingMachine(); vm.putMoney(500); Juice j = vm.buy("コーラ"); assertThat(j.getName(), is("コーラ")); int v = vm.getStock("コーラ"); assertThat(v, is(4)); }
在庫を減らすところをきちんと実装するために、Javaのコレクションフレームワークを使用する。
ここでは、重複したオブジェクトを使用できるListを使用する。
コーラの在庫を保持するためのListを用意して、コンストラクタで5個のコーラを追加する。
public class VendingMachine { private int total; private List<Juice> colas = new ArrayList<>(); public VendingMachine() { for (int i = 0; i < 5; i++) { Juice c = new Juice(); c.setName("コーラ"); c.setPrice(120); colas.add(c); } }
コーラの在庫をListで表現したので、getStock()メソッドの戻り値は、Listに格納された数を返すようにする。
public int getStock(String string) { return colas.size(); }
buy()メソッドでは、コーラの在庫からコーラを1本取得して返すようにする。
public Juice buy(String string) { Juice cola = colas.remove(0); return cola; }
在庫を減らす実装ができたので、次は売上金額を増やすところを実装する。
まずは、1本のコーラを購入したら売上金額が120円となるテストを書く。
@Test public void testBuy() { VendingMachine vm = new VendingMachine(); vm.putMoney(500); assertThat(v, is(0)); Juice j = vm.buy("コーラ"); assertThat(j.getName(), is("コーラ")); int v = vm.getStock("コーラ"); assertThat(v, is(4)); v = vm.getAmountSold(); assertThat(v, is(120)); }
クイックフィックスでgetAmountSold()メソッドを追加する。
public int getAmountSold() { return 0; }
戻り値を120にすればテストにパスする。
public int getAmountSold() { return 120; }
購入前は売上高0円であることをテストに追加する。
@Test public void 購入() { VendingMachine vm = new VendingMachine(); vm.putMoney(500); int v = vm.getAmountSold(); assertThat(v, is(0)); Juice j = vm.buy("コーラ"); assertThat(j.getName(), is("コーラ")); v = vm.getStock("コーラ"); assertThat(v, is(4)); v = vm.getAmountSold(); assertThat(v, is(120)); }
売上高をきちんと返せるようにVendingMachineクラスを修正する。
売上高は、インスタンス変数として保持するのが適当に思われるので、amountSoldメンバを追加する。
getAmountSold()では、その値を返すようにする。
public class VendingMachine { private int total; private int amountSold; private List<Juice> colas = new ArrayList<>(); (略) public int getAmountSold() { return amountSold; }
amountSold の値は、購入操作を行ったところで更新する。
public Juice buy(String string) { Juice cola = colas.remove(0); amountSold += cola.getPrice(); return cola; }
これでテストにパスするようになる。
次は「投入金額が足りない場合もしくは在庫がない場合、購入操作を行っても何もしない。」のテストを追加する。
@Test public void 買えない場合() { VendingMachine vm = new VendingMachine(); Juice j = vm.buy("コーラ"); assertNull(j); }
購入可能かどうかを調べているcanBuy()メソッドを使用する。
public Juice buy(String string) { if (!canBuy(string)) return null; Juice cola = colas.remove(0); amountSold += cola.getPrice(); return cola; }
お金を不足していると買えなくなるので、テストにパスする。
在庫切れのテストを追加する。
@Test public void 買えない場合() { VendingMachine vm = new VendingMachine(); Juice j = vm.buy("コーラ"); assertNull(j); vm.putMoney(1000); for (int i = 0; i < 5; i++) { j = vm.buy("コーラ"); } j = vm.buy("コーラ"); assertNull(j); }
現状では在庫のチェックを行っていないので、テストがエラーになる。
canBuy()メソッドで在庫の有無を確認することで、テストにパスするようになる。
canBuy()メソッドのように、複数の条件をチェックする場合は、NG条件でfalseを返すコードを書いて、最後までNG条件にあたらかった場合にtrueを返すようにすると、コードが読みやすくなる。
public boolean canBuy(String string) { if (colas.size() == 0) return false; if (total < 120) return false; return true; }
売上高を取得できるようにする。
テストを追加する。
@Test public void 売上高と払い戻し() { VendingMachine vm = new VendingMachine(); vm.putMoney(1000); Juice j = vm.buy("コーラ"); j = vm.buy("コーラ"); int v = vm.getAmountSold(); assertThat(v, is(240)); }
buy()メソッドで売上高を計上するようにする。
public Juice buy(String string) { if (!canBuy(string)) return null; Juice cola = colas.remove(0); amountSold += cola.getPrice(); return cola; }
お釣りの払い戻し金額を確認するテストを追加する。
@Test public void 売上高と払い戻し() { VendingMachine vm = new VendingMachine(); vm.putMoney(1000); Juice j = vm.buy("コーラ"); j = vm.buy("コーラ"); int v = vm.getAmountSold(); assertThat(v, is(240)); v = vm.refund(); assertThat(v, is(760)); }
ステップ4 機能拡張
ジュースを3種類管理できるようにする。
在庫にレッドブル(値段:200円、名前”レッドブル”)5本を追加する。
在庫に水(値段:100円、名前”水”)5本を追加する。
@Test public void ジュースの種類を取得() { VendingMachine vm = new VendingMachine(); Juice[] j = vm.getJuiceVariety(); assertThat(j.length, is(3)); assertThat(j[0].getName(), is("コーラ")); assertThat(j[0].getPrice(), is(120)); assertThat(j[1].getName(), is("レッドブル")); assertThat(j[1].getPrice(), is(200)); assertThat(j[2].getName(), is("水")); assertThat(j[2].getPrice(), is(100)); }
テストにパスするように実装を修正する。
きれいなコードを書こうとせずに、とりあえずはベタでコードにパスできるようにする。
テストにパスしたら、コードを改善する。(リファクタリング)
public Juice[] getJuiceVariety() { Juice[] j = new Juice[3]; Juice cola = new Juice(); cola.setName("コーラ"); cola.setPrice(120); j[0] = cola; Juice r = new Juice(); r.setName("レッドブル"); r.setPrice(200); j[1] = r; Juice w = new Juice(); w.setName("水"); w.setPrice(100); j[2] = w; return j; }
レッドブルを買えるようにする。
@Test public void レッドブルを買う() { VendingMachine vm = new VendingMachine(); vm.putMoney(500); Juice j = vm.buy("レッドブル"); assertThat(j.getName(), is("レッドブル")); }
コーラと同じように、在庫を管理するListを用意する。
コンストラクタ内で初期化する。
public class VendingMachine { private int total; private int amountSold; private List<Juice> colas = new ArrayList<>(); private List<Juice> redbulls = new ArrayList<>(); private List<Juice> waters = new ArrayList<>(); public VendingMachine() { for (int i = 0; i < 5; i++) { Juice j = new Juice(); j.setName("コーラ"); j.setPrice(120); colas.add(j); j = new Juice(); j.setName("レッドブル"); j.setPrice(200); redbulls.add(j); j = new Juice(); j.setName("水"); j.setPrice(100); waters.add(j); } }
購入の動作は、引数で品名を渡されているので、品名をもとにListを区別する。
public Juice buy(String name) { if (!canBuy(name)) return null; if (name.equals("コーラ")) { Juice cola = colas.remove(0); amountSold += cola.getPrice(); total -= cola.getPrice(); return cola; } if (name.equals("レッドブル")) { Juice r = redbulls.remove(0); amountSold += r.getPrice(); total -= r.getPrice(); return r; } if (name.equals("水")) { Juice w = waters.remove(0); amountSold += w.getPrice(); total -= w.getPrice(); return w; } return null; }
エラーになるテストを作る。
現状はコーラの値段で買えるかどうかを判断しているので、レッドブルと水では適切に判断できない。
それを確認するテストを作成する。
@Test public void レッドブルを買う() { VendingMachine vm = new VendingMachine(); vm.putMoney(100); vm.putMoney(10); vm.putMoney(10); Juice j = vm.buy("レッドブル"); assertNull(j); vm.putMoney(100); j = vm.buy("レッドブル"); assertThat(j.getName(), is("レッドブル")); } @Test public void 水を買う() { VendingMachine vm = new VendingMachine(); vm.putMoney(100); Juice j = vm.buy("水"); assertThat(j.getName(), is("水")); }
120円入れるとレッドブルを買えてしまう。
100円入れても水を買えない。