テスト駆動開発で自動販売機を作る
前回は10円玉の釣り銭を使い果たすテストを用意したところまで。
テストを実行すると失敗するので、テストにパスするよう実装する。
VendingMachineTest.java
@Test public void testChangeStock6() { VendingMachine vm = new VendingMachine(); for (int i = 0; i < 3; i++) { vm.throwInto(100); vm.throwInto(50); vm.purchace(); } int s10 = vm.get10YenStock(); assertThat(s10, is(1)); vm.throwInto(100); vm.throwInto(50); Output o = vm.purchace(); s10 = vm.get10YenStock(); assertThat(s10, is(1)); assertThat(o.getJuiceCount(), is(0)); assertThat(o.getChange(), is(0)); int total = vm.getTotal(); assertThat(total, is(150)); }
お釣りが足りないときは購入できないようにすればいい。ということは、購入可能かどうかを調べているisPurchacable()メソッド内でお釣りが足りるかどうかを確認すればいい。
そこで、お釣りが足りるかどうかを調べる isChangeShorted()メソッドを追加する。お釣りが足りるならfalse、足りないならtrueを返す。
常にfalseを返すように作れば、従来どおりの動作となる。
public boolean isPurchacable() { if (getJuiceStock() == 0) return false; if (total < getJuicePrice()) return false; if (isChangeShorted()) return false; return true; } private boolean isChangeShorted() { return false; }
お釣りが50円よりも少ないときは10円玉のお釣りを出す必要がある。
10円玉のストックとお釣りの金額を比較して、釣り銭が足りるかどうかを調べる。
private boolean isChangeShorted() { int change = total - getJuicePrice(); if (change < 50) { if (stock10yen * 10 < change) return true; } return false; }
100円玉の釣り銭が足りなくなるテストを作る。
VendingMachineTest.java
@Test public void testChangeStock7() { VendingMachine vm = new VendingMachine(); for (int i = 0; i < 2; i++) { vm.throwInto(500); vm.throwInto(10); vm.throwInto(10); vm.purchace(); } int s100 = vm.get100YenStock(); assertThat(s100, is(2)); vm.throwInto(500); Output o = vm.purchace(); s100 = vm.get100YenStock(); assertThat(s100, is(2)); assertThat(o.getJuiceCount(), is(0)); assertThat(o.getChange(), is(0)); }
10円玉の釣り銭不足と同様の実装を追加する。
VendingMachine.java
private boolean isChangeShorted() { int change = total - getJuicePrice(); if (change < 50) { if (stock10yen * 10 < change) return true; } if (change < 500) { if (stock100yen * 100 < change) return true; } return false; }
これを追加すると、testPurchace1000() が通らなくなってしまった!
テストコードを確認してみると、お釣りのチェックをしなければパスするコードを書いていることがわかる。
このテストでは、ジュースの在庫がなくなるのを確認したいので、お釣りが不要になるようにテストコードを修正する。
VendingMachineTest.java
@Test public void testPurchace1000() { VendingMachine vm = new VendingMachine(); vm.throwInto(1000); Output o = vm.purchace(); assertThat(o.getJuiceCount(), is(1)); assertThat(o.getChange(), is(880)); int stock = vm.getJuiceStock(); assertThat(stock, is(4)); for (int i = 0; i < 4; i++) { //vm.throwInto(500); 500円ではなく、釣り銭が出ない120円で購入 vm.throwInto(100); vm.throwInto(10); vm.throwInto(10); o = vm.purchace(); } stock = vm.getJuiceStock(); assertThat(stock, is(0)); vm.throwInto(500); o = vm.purchace(); assertThat(o.getJuiceCount(), is(0)); assertThat(o.getChange(), is(0)); int total = vm.getTotal(); assertThat(total, is(500)); }
50円玉の釣り銭不足になるテストを作りたいが、ジュースの在庫が5個しかないのでできない。
500円玉の釣り銭不足も同様。
ここまでで、テスト駆動開発の実習を終わりにする。
VendingMachineTest.java
import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import org.junit.Test; public class VendingMachineTest { @Test public void testThrowInto() { VendingMachine vm = new VendingMachine(); int change = vm.throwInto(10); assertThat(change, is(0)); int total = vm.getTotal(); assertThat(total, is(10)); change = vm.throwInto(50); assertThat(change, is(0)); total = vm.getTotal(); assertThat(total, is(60)); } @Test public void testThrowIntoNotAccept() { VendingMachine vm = new VendingMachine(); int change = vm.throwInto(1); assertThat(change, is(1)); int total = vm.getTotal(); assertThat(total, is(0)); change = vm.throwInto(5); assertThat(change, is(5)); change = vm.throwInto(2000); assertThat(change, is(2000)); change = vm.throwInto(5000); assertThat(change, is(5000)); change = vm.throwInto(10000); assertThat(change, is(10000)); } @Test public void testThrowIntoRefund() { VendingMachine vm = new VendingMachine(); int change = vm.throwInto(10); assertThat(change, is(0)); int total = vm.getTotal(); assertThat(total, is(10)); int refund = vm.refund(); assertThat(refund, is(10)); total = vm.getTotal(); assertThat(total, is(0)); change = vm.throwInto(100); assertThat(change, is(0)); total = vm.getTotal(); assertThat(total, is(100)); change = vm.throwInto(500); assertThat(change, is(0)); total = vm.getTotal(); assertThat(total, is(600)); refund = vm.refund(); assertThat(refund, is(600)); } @Test public void testGetJuice() { VendingMachine vm = new VendingMachine(); String name = vm.getJuiceName(); assertThat(name, is("コーラ")); int price = vm.getJuicePrice(); assertThat(price, is(120)); int stock = vm.getJuiceStock(); assertThat(stock, is(5)); } @Test public void testIsPurchacable() { VendingMachine vm = new VendingMachine(); boolean b = vm.isPurchacable(); assertThat(b, is(false)); vm.throwInto(500); b = vm.isPurchacable(); assertThat(b, is(true)); } @Test public void testPurchace() { VendingMachine vm = new VendingMachine(); vm.throwInto(100); vm.throwInto(10); vm.throwInto(10); Output o = vm.purchace(); assertThat(o.getJuiceCount(), is(1)); assertThat(o.getChange(), is(0)); int total = vm.getTotal(); assertThat(total, is(0)); } @Test public void testPurchace500() { VendingMachine vm = new VendingMachine(); vm.throwInto(500); Output o = vm.purchace(); assertThat(o.getJuiceCount(), is(1)); assertThat(o.getChange(), is(380)); int total = vm.getTotal(); assertThat(total, is(0)); } @Test public void testPurchace1000() { VendingMachine vm = new VendingMachine(); vm.throwInto(1000); Output o = vm.purchace(); assertThat(o.getJuiceCount(), is(1)); assertThat(o.getChange(), is(880)); int stock = vm.getJuiceStock(); assertThat(stock, is(4)); for (int i = 0; i < 4; i++) { vm.throwInto(100); vm.throwInto(10); vm.throwInto(10); o = vm.purchace(); } stock = vm.getJuiceStock(); assertThat(stock, is(0)); vm.throwInto(500); o = vm.purchace(); assertThat(o.getJuiceCount(), is(0)); assertThat(o.getChange(), is(0)); int total = vm.getTotal(); assertThat(total, is(500)); } @Test public void testSale() { VendingMachine vm = new VendingMachine(); int sale = vm.getSale(); assertThat(sale, is(0)); vm.throwInto(500); vm.purchace(); sale = vm.getSale(); assertThat(sale, is(120)); } @Test public void testChangeStock() { VendingMachine vm = new VendingMachine(); int s10 = vm.get10YenStock(); assertThat(s10, is(10)); int s50 = vm.get50YenStock(); assertThat(s50, is(10)); int s100 = vm.get100YenStock(); assertThat(s100, is(10)); int s500 = vm.get500YenStock(); assertThat(s500, is(10)); vm.throwInto(100); vm.throwInto(50); Output o = vm.purchace(); assertThat(o.getJuiceCount(), is(1)); assertThat(o.getChange(), is(30)); s10 = vm.get10YenStock(); assertThat(s10, is(7)); } @Test public void testChangeStock2() { VendingMachine vm = new VendingMachine(); vm.throwInto(100); vm.throwInto(50); Output o = vm.purchace(); assertThat(o.getJuiceCount(), is(1)); assertThat(o.getChange(), is(30)); int s10 = vm.get10YenStock(); assertThat(s10, is(7)); vm.throwInto(100); vm.throwInto(50); vm.purchace(); s10 = vm.get10YenStock(); assertThat(s10, is(4)); } @Test public void testChangeStock3() { VendingMachine vm = new VendingMachine(); vm.throwInto(100); vm.throwInto(100); vm.purchace(); int s10 = vm.get10YenStock(); assertThat(s10, is(7)); int s50 = vm.get50YenStock(); assertThat(s50, is(9)); } @Test public void testChangeStock4() { VendingMachine vm = new VendingMachine(); vm.throwInto(500); vm.purchace(); int s10 = vm.get10YenStock(); assertThat(s10, is(7)); int s50 = vm.get50YenStock(); assertThat(s50, is(9)); int s100 = vm.get100YenStock(); assertThat(s100, is(7)); } @Test public void testChangeStock5() { VendingMachine vm = new VendingMachine(); vm.throwInto(1000); vm.purchace(); int s10 = vm.get10YenStock(); assertThat(s10, is(7)); int s50 = vm.get50YenStock(); assertThat(s50, is(9)); int s100 = vm.get100YenStock(); assertThat(s100, is(7)); int s500 = vm.get500YenStock(); assertThat(s500, is(9)); } @Test public void testChangeStock6() { VendingMachine vm = new VendingMachine(); for (int i = 0; i < 3; i++) { vm.throwInto(100); vm.throwInto(50); vm.purchace(); } int s10 = vm.get10YenStock(); assertThat(s10, is(1)); vm.throwInto(100); vm.throwInto(50); Output o = vm.purchace(); s10 = vm.get10YenStock(); assertThat(s10, is(1)); assertThat(o.getJuiceCount(), is(0)); assertThat(o.getChange(), is(0)); int total = vm.getTotal(); assertThat(total, is(150)); } @Test public void testChangeStock7() { VendingMachine vm = new VendingMachine(); for (int i = 0; i < 2; i++) { vm.throwInto(500); vm.throwInto(10); vm.throwInto(10); vm.purchace(); } int s100 = vm.get100YenStock(); assertThat(s100, is(2)); vm.throwInto(500); Output o = vm.purchace(); s100 = vm.get100YenStock(); assertThat(s100, is(2)); assertThat(o.getJuiceCount(), is(0)); assertThat(o.getChange(), is(0)); } }
VendingMachine.java
public class VendingMachine { private int total = 0; private int stock = 5; private int sale = 0; private int stock10yen = 10; private int stock50yen = 10; private int stock100yen = 10; private int stock500yen = 10; public int throwInto(int yen) { if (yen == 1) return 1; if (yen == 5) return 5; if (yen == 2000) return 2000; if (yen == 5000) return 5000; if (yen == 10000) return 10000; total += yen; return 0; } public int getTotal() { return total; } public int refund() { int refund = total; total = 0; return refund; } public String getJuiceName() { return "コーラ"; } public int getJuicePrice() { return 120; } public int getJuiceStock() { return stock; } public boolean isPurchacable() { if (getJuiceStock() == 0) return false; if (total < getJuicePrice()) return false; if (isChangeShorted()) return false; return true; } private boolean isChangeShorted() { int change = total - getJuicePrice(); if (change < 50) { if (stock10yen * 10 < change) return true; } if (change < 500) { if (stock100yen * 100 < change) return true; } return false; } public Output purchace() { if (!isPurchacable()) { return new Output(0, 0); } total -= getJuicePrice(); int change = total; decreaseStock(change); total -= change; stock--; sale += getJuicePrice(); return new Output(1, change); } private void decreaseStock(int change) { if (change >= 500) { change -= 500; stock500yen--; } if (change >= 100) { int c = change / 100; change -= c * 100; stock100yen -= c; } if (change >= 50) { change -= 50; stock50yen--; } stock10yen -= change / 10; } public int getSale() { return sale; } public int get10YenStock() { return stock10yen; } public int get50YenStock() { return stock50yen; } public int get100YenStock() { return stock100yen; } public int get500YenStock() { return stock500yen; } }
スレッド
スレッドは、並行処理を行う仕組み。
Javaでは言語仕様で並行処理をサポートしている。
Threadクラスを継承してクラスを作成する。そのクラスのインスタンスを生成し、start() メソッドを呼ぶとスレッドが起動される。スレッドが起動されると、run() メソッドが呼び出される。
Threadを使ったサンプル
MyThread.java
public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Threadの実行中" + i); } } }
MyThreadTest.java
public class MyThreadTest { public static void main(String[] args) { Thread t = new MyThread(); t.start(); for (int i = 0; i < 5; i++) { System.out.println("mainの実行中" + i); } } }
Runnableインタフェースを使用する方法もある。
MyRunnable.java
public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Runnableの実行中" + i); } } }
MyRunnableTest.java
public class MyRunnableTest { public static void main(String[] args) { Runnable r = new MyRunnable(); Thread t = new Thread(r); t.start(); for (int i = 0; i < 5; i++) { System.out.println("mainの実行中" + i); } } }
スレッドのライフサイクル
スレッドのライフサイクルは重要!(試験問題に出す!)
SleepThread.java
public class SleepThread extends Thread { @Override public void run() { try { System.out.println(getName() + " sleep前"); Thread.sleep(3000); System.out.println(getName() + " sleep後"); } catch (InterruptedException e) { e.printStackTrace(); } } }
SleepThreadTest.java
public class SleepThreadTest { public static void main(String[] args) { SleepThread t = new SleepThread(); t.start(); System.out.println("mainのEND"); } }
複数のスレッドを同時に起動するサンプル。
SleepThreadTest2.java
public class SleepThreadTest2 { public static void main(String[] args) { for (int i = 0; i < 3; i++) { SleepThread t = new SleepThread(); t.start(); } System.out.println("mainのEND"); } }