5月22日

TDD(テスト駆動開発)

JUnitを使ったTDDの練習。

FizzBuzz

FizzBuzzをTDDで作ってみる。

最初にテストコードを書く。
FizzBuzzTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {

	@Test
	public void testSay1() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(1);
		assertThat(s, is("1"));
	}
}

そのテストコードをパスする最小限の実装をする。
FizzBuzz.java

public class FizzBuzz {
	public String say(int i) {
		return "1";
	}
}

テストコードを追加する。
FizzBuzzTest.java

	@Test
	public void testSay2() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(2);
		assertThat(s, is("2"));
	}

そのテストコードをパスする最小限の実装をする。
FizzBuzz.java

public class FizzBuzz {
	public String say(int i) {
		if (i == 2) return "2";
		return "1";
	}
}

テストコードを追加する。
FizzBuzzTest.java

	@Test
	public void testSay3() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(3);
		assertThat(s, is("Fizz"));
	}

テストコードを追加→テストにパスするように実装 を繰り返す。

そのテストコードをパスする最小限の実装をする。
FizzBuzz.java

public class FizzBuzz {
	public String say(int i) {
		if (i == 3) return "Fizz";
		if (i == 2) return "2";
		return "1";
	}
}

テストコードを追加する。
FizzBuzzTest.java

	@Test
	public void testSay4() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(4);
		assertThat(s, is("4"));
	}

数字を返すところは共通化できるのでリファクタリングする。

FizzBuzz.java

public class FizzBuzz {
	public String say(int i) {
		if (i == 3) return "Fizz";
		return String.valueOf(i);
	}
}

テストの追加、実装、リファクタリングを繰り返す。
最終的にはこんな感じのテストコードになる。

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;


public class FizzBuzzTest {

	@Test
	public void testSay1() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(1);
		assertThat(s, is("1"));
	}

	@Test
	public void testSay2() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(2);
		assertThat(s, is("2"));
	}

	@Test
	public void testSay3() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(3);
		assertThat(s, is("Fizz"));
	}

	@Test
	public void testSay4() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(4);
		assertThat(s, is("4"));
	}

	@Test
	public void testSay5() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(5);
		assertThat(s, is("Buzz"));
	}

	@Test
	public void testSay6() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(6);
		assertThat(s, is("Fizz"));
	}

	@Test
	public void testSay78() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(7);
		assertThat(s, is("7"));
		s = fb.say(8);
		assertThat(s, is("8"));
	}

	@Test
	public void testSay10() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(10);
		assertThat(s, is("Buzz"));
	}

	@Test
	public void testSay15() {
		FizzBuzz fb = new FizzBuzz();
		String s = fb.say(15);
		assertThat(s, is("Fizz Buzz"));
	}
}

すべてのテストにパスするようになれば完成。
FizzBuzz.java


public class FizzBuzz {
	public String say(int i) {
		if (i % 15 == 0) return "Fizz Buzz";
		if (i % 3 == 0) return "Fizz";
		if (i % 5 == 0) return "Buzz";
		return String.valueOf(i);
	}
}

世界のナベアツ

世界のナベアツをTDDで作ってみる。

最初にテストコードを書く。
NabeatsuTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class NabeatsuTest {

	@Test
	public void testSay1() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(1);
		assertThat(s, is("1"));
	}
}

そのテストコードをパスする最小限の実装をする。
Nabeatsu.java

public class Nabeatsu{
	public String say(int i) {
		return "1";
	}
}

FizzBuzzと同様にテストコードを追加する。

NabeatsuTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;


public class NabeatsuTest {

	@Test
	public void testSay() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(1);
		assertThat(s, is("1"));
		s = fb.say(2);
		assertThat(s, is("2"));
	}

	@Test
	public void testSay3() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(3);
		assertThat(s, is("さぁーん"));
	}
}

テストにパスするコードを書く。
Nabeatsu.java


public class Nabeatsu {
	public String say(int i) {
		if (i == 3) return "さぁーん";
		return String.valueOf(i);
	}
}

最終的なテストコードはこんな感じ。

NabeatsuTest.java

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import org.junit.Test;


public class NabeatsuTest {

	@Test
	public void testSay() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(1);
		assertThat(s, is("1"));
		s = fb.say(2);
		assertThat(s, is("2"));
	}

	@Test
	public void testSay3() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(3);
		assertThat(s, is("さぁーん"));
	}

	@Test
	public void testSay6() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(6);
		assertThat(s, is("さぁーん"));
	}

	@Test
	public void testSay13() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(13);
		assertThat(s, is("さぁーん"));
	}

	@Test
	public void testSay23() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(23);
		assertThat(s, is("さぁーん"));
	}

	@Test
	public void testSay31() {
		Nabeatsu fb = new Nabeatsu();
		String s = fb.say(31);
		assertThat(s, is("さぁーん"));
	}
}

すべてのテストにパスするコード。
Nabeatsu.java


public class Nabeatsu {
	public String say(int i) {
		String s = String.valueOf(i);
		if (s.contains("3")) return "さぁーん";
		if (i % 3 == 0) return "さぁーん";
		return String.valueOf(i);
	}
}

自動販売機を作る

TDDBCの例題で自動販売機があったので、この例題でTDDしてみる。
http://devtesting.jp/tddbc/?TDDBC%E5%A4%A7%E9%98%AA

まずは自動販売機のクラスを作る。
VendingMachine.java

public class VendingMachine {
}

お金を投入するメソッドを追加する。戻り値はお釣りの金額を返す。
VendingMachine.java

public class VendingMachine {
	public int throwInto(int yen) {
		return 0;
	}
}

テストコードを追加する。
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));
	}
}

テストにパスしたので、さらにテストコードを追加。
投入済みの金額を取得する getTotal() メソッドのテストを追加。
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));
	}
}

VendingMachine.java


public class VendingMachine {
	public int throwInto(int yen) {
		return 0;
	}

	public int getTotal() {
		return 0; // 0 → 10 にすればテストにパスする。
	}
}

さらにテストコードを追加。
お金を連続投入する。

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));
	}
}

連続投入したら投入済みの金額が合計されるようにする。
VendingMachine.java


public class VendingMachine {
	private int total;

	public int throwInto(int yen) {
		total += yen;
		return 0;
	}

	public int getTotal() {
		return total;
	}
}

受け入れないお金(1円・5円・1000円以外のお札)は、そのまま返却するテストを追加。
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));
	}
}

テストにパスするように実装を追加する。
VendingMachine.java


public class VendingMachine {
	private int total;

	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;
	}
}

返却ボタンを実装する。メソッド名を refund() とする。
まずはテストコードを書く。
VendingMachineTest.java

	@Test
	public void testThrowIntoRefund() {
		VendingMachine vm = new VendingMachine();
		int change = vm.throwInto(10);
		assertThat(change, is(0));
		int total = vm.getTotal();
		assertThat(total, is(0));
		int refund = vm.refund();
		assertThat(refund, is(10));
	}

refund() メソッドを追加する。
VendingMachine.java


public class VendingMachine {
	private int total;

	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() {
		return 0; // 0 → 10 に修正すればテストにパスする
	}
}

返却のテストを追加する。
VendingMachineTest.java

	@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));
	}

テストにパスするように実装を修正する。
VendingMachine.java


public class VendingMachine {
	private int total;

	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;
	}
}

ジュースの管理をできるようにする。
ジュースの名前と値段と在庫数を取得できるようにする。
VendingMachineTest.java

	@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));
	}

テストにパスするコードを書く。
VendingMachine.java


public class VendingMachine {
	private int total;

	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 5;
	}
}