6月8日

テスト駆動開発でストリームとコレクションを復習する

TextDocumentクラスと、テストケースのTextDocumentTestクラスを作成する。

ファイルを読み込む機能を用意する。
そのために、load()メソッドを作成する。
まずはテストを書く。

TextDocumentTest.java

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

import org.junit.Test;

public class TextDocumentTest {

	@Test
	public void testLoad() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
	}

}

コンパイルエラーを解消するために、TextDocumentクラスにload()メソッドを追加する。
TextDocument.java

public class TextDocument {
	public boolean load(String path) {
		return false;
	}
}

テストを実行するとエラーになるので、load()メソッドの戻り値をtrueに変更する。(Fake)
TextDocument.java

public class TextDocument {
	public boolean load(String path) {
		return true;
	}
}

テストにパスするのでめでたしめでたし。
テストケースに、パスしないテストを追加する。
TextDocumentTest.java

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

import org.junit.Test;

public class TextDocumentTest {

	@Test
	public void testLoadNG() {
		TextDocument td = new TextDocument();
		boolean b = td.load("aaa.txt");
		assertThat(b, is(false));
	}

	@Test
	public void testLoad() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
	}

}

このテストをパスするようにするには、まじめに実装しないといけない。
TextDocument.java

import java.io.File;

public class TextDocument {
	public boolean load(String path) {
		File f = new File(path);
		if (!f.exists()) return false;
		return true;
	}
}

次に、ファイルの行数を取得するメソッドを作ることにする。
hoge.txt は11行なので、テストコードは以下のようになる。
TextDocumentTest.java

	@Test
	public void testLines() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
		int lines = td.getLines();
		assertThat(lines, is(11));
	}

このテストをパスするには、Fakeすればよい。
TextDocument.java

import java.io.File;

public class TextDocument {
	public boolean load(String path) {
		File f = new File(path);
		if (!f.exists()) return false;
		return true;
	}

	public int getLines() {
		return 11;
	}
}

Fakeコードではテストにパスしないようにするために、1行だけのファイル 1.txt を用意し、そのファイルの行数を検査するテストを追加する。
TextDocumentTest.java

	@Test
	public void testLines() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		assertThat(b, is(true));
		int lines = td.getLines();
		assertThat(lines, is(11));
	}

	@Test
	public void testLines1() {
		TextDocument td = new TextDocument();
		boolean b = td.load("1.txt");
		assertThat(b, is(true));
		int lines = td.getLines();
		assertThat(lines, is(1));
	}

このテストをパスするために、TextDocumentクラスをきちんと実装する。
java.util.List を使用してコレクションフレームワークの復習をする。
List の要素として、ファイルの各行を追加する。

TextDocument.java

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TextDocument {
	private List<String> lines;

	public boolean load(String path) {
		lines = new ArrayList<String>();
		File f = new File(path);
		if (!f.exists()) return false;
		try (BufferedReader in = new BufferedReader(new FileReader(path))) {
			while (in.ready()) {
				String s = in.readLine();
				lines.add(s);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return true;
	}

	public int getLines() {
		return lines.size();
	}
}

ファイル読み込みができた人は、以下の機能を追加しましょう!

  1. 指定した行の文字列取得
  2. 指定した行を削除(削除した行の文字列を返す)
  3. 指定した文字列を行として末尾に挿入
  4. 指定した場所に指定した文字列を行として挿入
  5. 出力先ファイル名を指定してファイル出力

上記機能追加ができたら、さらに以下の機能を追加しましょう!

  1. 文字列検索(行番号と、その行の文字列を、Listで返す)
  2. 文字列置換(検索と同じように返す)

指定した行の文字列を取得

まずはテストを書く。

TextDocumentTest.java

	@Test
	public void testGetLine() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		String s = td.getLine(0);
		assertThat(s, is("ab"));
	}

TextDocumentにはgetLine()メソッドがないので、Eclipseに作ってもらう。
TextDocument.java

	public String getLine(int i) {
		return null;
	}

テストを実行するとエラーになるので、テストをパスするように “ab”を返すようにFakeする。
TextDocument.java

	public String getLine(int i) {
		return "ab";
	}

テストを追加する。
TextDocumentTest.java

	@Test
	public void testGetLine() {
		TextDocument td = new TextDocument();
		boolean b = td.load("hoge.txt");
		String s = td.getLine(0);
		assertThat(s, is("ab"));
		s = td.getLine(1);
		assertThat(s, is("abc"));
	}

テストを実行するとエラーになるので、テストにパスするようにコードを修正する。
TextDocument.java

	public String getLine(int i) {
		return lines.get(i);
	}

指定した行を削除

まずはテストを書く。
削除した行の文字列を返す。

TextDocumentTest.java

	@Test
	public void testDelete() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		String s = td.deleteLine(0);
		assertThat(s, is("ab"));
	}

EclipseにdeleteLine()メソッドを作ってもらう。
TextDocument.java

	public String deleteLine(int i) {
		return null;
	}

テストを実行してもパスしないので、”ab”を返すようにFakeする。
TextDocument.java

	public String deleteLine(int i) {
		return "ab";
	}

テストを追加する。
1行目を削除したら、もともと2行目だったものが1行目になるはず。
TextDocumentTest.java

	@Test
	public void testDelete() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		String s = td.deleteLine(0);
		assertThat(s, is("ab"));
		s = td.getLine(0);
		assertThat(s, is("abc"));
	}

TextDocumentのdeleteLine()を修正。

TextDocument.java

	public String deleteLine(int i) {
		return lines.remove(i);
	}

指定した文字列を行として末尾に挿入

まずはテストを書く。
1行追加すれば、行数が増えるはず。

TextDocumentTest.java

	@Test
	public void testAdd() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		int lines = td.getLines();
		assertThat(lines, is(10));
		td.addLine("1234567890");
		lines = td.getLines();
		assertThat(lines, is(11));
	}

EclipseにaddLine()を作ってもらう。
テストするとエラーになるので、テストにパスする実装を追加する。
TextDocument.java

	public void addLine(String s) {
		lines.add(s);
	}

指定した場所に指定した文字列を行として挿入

まずはテストを書く。
どこに追加するのかを引数で渡す。
先頭に1行追加すれば、行数が増えるはず。
先頭行は追加した文字列に一致するはず。
2行目は、もともと1行目だった文字列に一致するはず。

TextDocumentTest.java

	@Test
	public void testAdd1() {
		TextDocument td = new TextDocument();
		td.load("hoge.txt");
		int lines = td.getLines();
		assertThat(lines, is(10));
		td.addLine(0, "1234567890");
		lines = td.getLines();
		assertThat(lines, is(11));
		String s = td.getLine(0);
		assertThat(s, is("1234567890"));
		s = td.getLine(1);
		assertThat(s, is("ab"));
	}

6月5日

スレッドの同期

スレッドを同期しないと、値の代入などが期待通りに動かない場合がある。
DojiSample.java では、countに1をプラスすることを2つのスレッドから同時に行うことで、誤った結果となる礼を示している。

DojiSample.java

package c25;

public class DojiSample {
	Integer count = new Integer(1);
	
	void countUp() {
		int tmpValue = count;
		tmpValue = tmpValue + 1;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		count = tmpValue;
	}
	class CountThread extends Thread {
		public void run() {
			countUp();
			System.out.println(getName() + " count=" + count);
		}
	}
	
	void doSample() {
		new CountThread().start();
		new CountThread().start();
	}

	public static void main(String[] args) {
		new DojiSample().doSample();
	}

}

DojiSample.javaを実行した結果は以下のようになる。

Thread-0 count=2
Thread-1 count=2

count変数に同時に複数のスレッドからアクセスできないようにするために、synchronizedブロックを使用する。
DojiSample.java

	void countUp() {
		synchronized (count) {
			int tmpValue = count;
			tmpValue = tmpValue + 1;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count = tmpValue;
		}
	}

synchronizedブロック内でカウントアップする処理を行うようにすることで、countがきちんとカウントアップされるようになる。

Thread-0 count=2
Thread-1 count=3

練習問題

テキストp.508の問題3を入力して実行してみる。

Renshu3.java

class BarThread extends Thread {
	StringBuffer sb;
	public BarThread(StringBuffer sb) {
		this.sb = sb;
	}
	public void run() {
		addNextChar();
	}
	void addNextChar() {
		char lastChar = sb.charAt(sb.length() - 1);
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		sb.append((char)(lastChar + 1));
		System.out.println(sb.toString());
	}
}

public class Renshu3 {

	public static void main(String[] args) {
		StringBuffer sb = new StringBuffer();
		sb.append("a");
		for (int i = 0; i < 10; i++) {
			BarThread t = new BarThread(sb);
			t.start();
		}
	}

}

実行結果(例)

abbbb
abbbbbbbbbb
abbbbbbbbb
abbbbbbbbb
abbbbbbb
abbbbbbb
abbbbb
abbbb
abbbb
abbbb

addNextChar() メソッドの内部を synchronized(sb) ブロック内に入れることで、期待通りに動作するようになる。

Renshu3.java

	void addNextChar() {
		synchronized (sb) {
			char lastChar = sb.charAt(sb.length() - 1);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			sb.append((char)(lastChar + 1));
			System.out.println(sb.toString());
		}
	}

出力結果は以下のとおり。

ab
abc
abcd
abcde
abcdef
abcdefg
abcdefgh
abcdefghi
abcdefghij
abcdefghijk

例題

Renshu3.java の出力内容を、hoge.txt に出力する。

mainメソッド内でPrintWriterのインスタンスを生成し、BarThreadのコンストラクタに渡すようにする。
BarThreadでは、受け取ったPrintWriterに対して文字列を出力する。

Renshu3.java

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

class BarThread extends Thread {
	StringBuffer sb;
	PrintWriter out;
	public BarThread(StringBuffer sb, PrintWriter out) {
		this.sb = sb;
		this.out = out;
	}
	public void run() {
		addNextChar();
	}
	void addNextChar() {
		synchronized (sb) {
			char lastChar = sb.charAt(sb.length() - 1);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			sb.append((char)(lastChar + 1));
			System.out.println(sb.toString());
			out.println(sb.toString());
		}
	}
}

public class Renshu3 {

	public static void main(String[] args) throws IOException {
		PrintWriter out = new PrintWriter(new FileWriter("hoge.txt"));
		StringBuffer sb = new StringBuffer();
		sb.append("a");
		for (int i = 0; i < 10; i++) {
			BarThread t = new BarThread(sb, out);
			t.start();
		}
	}
}

ファイルには何も書かれていない。
それは、BarThreadがファイルに書き込む前にmainスレッドが終了し、出力先のファイルがクローズされてしまうため。
mainスレッドの終了を Thread.sleep() で遅延させれば書き込みが行われる。

Renshu3.java

public class Renshu3 {

	public static void main(String[] args) throws IOException {
		PrintWriter out = new PrintWriter(new FileWriter("hoge.txt"));
		StringBuffer sb = new StringBuffer();
		sb.append("a");
		for (int i = 0; i < 10; i++) {
			BarThread t = new BarThread(sb, out);
			t.start();
		}
		try {
			Thread.sleep(2000);
			out.close();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

作成した hoge.txt を読み込むためのクラス TextDocument クラスを作る。

TextDocument.java

public class TextDocument {

}

TextDocumentの単体テストクラスを作る。
パッケージエクスプローラでTextDocumentクラスを右クリックし、[新規]-[JUnitテストケース]を選択する。

TextDocumentTest.java

import static org.junit.Assert.*;

import org.junit.Test;

public class TextDocumentTest {

	@Test
	public void test() {
		fail("まだ実装されていません");
	}
}

6月1日

テスト駆動開発で自動販売機を作る

前回は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");
	}

}

5月29日

テスト駆動開発で自動販売機を作る

例題はこちら⇒ TDDBC大阪

売上高を取得できるようにする

VendingMachineTest.java

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

売上高をインスタンス変数で保持する。
VendingMachine.java

public class VendingMachine {
	private int total = 0;
	private int stock = 5;
	private int sale = 0;

購入操作をしたときに売上高を増やし、そのインスタンス変数の値を返すようにする。
VendingMachine.java

	public Output purchace() {
		if (!isPurchacable()) {
			return new Output(0, 0);
		}
		total -= getJuicePrice();
		int change = total;
		total -= change;
		stock--;
		sale += getJuicePrice();
		return new Output(1, change);
	}

	public int getSale() {
		return sale;
	}

釣り銭を管理する

初期状態では、10円、50円、100円、500円の硬貨が、各10枚ずつ用意されている。

VendingMachineTest.java

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

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

	public int get10YenStock() {
		return 10;
	}

	public int get50YenStock() {
		return 10;
	}

	public int get100YenStock() {
		return 10;
	}

	public int get500YenStock() {
		return 10;
	}

150円を投入してジュースを買ったら、10円の釣り銭ストックが減る。
VendingMachineTest.java

	@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();
		s10 = vm.get10YenStock();
		assertThat(s10, is(7));
	}

購入するところで釣り銭のストックを減らす。
1回の購入なら残り7になるので、Fakeコードで実装する。

VendingMachine.java

	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) {
		stock10yen = 7;
	}

テストに2回目の購入を追加して、Fakeコードではテストにパスしないようにする。
VendingMachineTest.java

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

お釣りに出す枚数だけ減らすように実装を修正する。
VendingMachine.java

	private void decreaseStock(int change) {
		stock10yen -= change / 10;
	}

200円を入れて購入したときのテストを用意する。
VendingMachineTest.java

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

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

	private void decreaseStock(int change) {
		if (change >= 50) {
			change -= 50;
			stock50yen--;
		}
		stock10yen -= change / 10;
	}

500円を入れて購入したときのテストを用意する。
VendingMachineTest.java

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

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

	private void decreaseStock(int change) {
		if (change >= 100) {
			int c = change / 100;
			change -= c * 100;
			stock100yen -= c;
		}
		if (change >= 50) {
			change -= 50;
			stock50yen--;
		}
		stock10yen -= change / 10;
	}

1000円を入れて購入したときのテストを用意する。
VendingMachineTest.java

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

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

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

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

5月25日

自動販売機をTDDで実装する

例題
TDDBC大阪

購入できるかどうかを調べるために、isPurchacable() メソッドを追加する。

	@Test
	public void testIsPurchacable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchacable();
		assertThat(b, is(false));
	}

Eclipseでメソッドを生成するとテストをパスするようになる。

	public boolean isPurchacable() {
		return false;
	}

500円を投入すると購入可能になるテストを追加する。

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

自動販売機の購入の動作を実装する

お金を入れてボタンを押すとジュースとおつりが出てくる。

それっぽいテストを書いてみる。
出てくるもの(ジュースとおつり)をまとめて Output クラスとしておく。
VendingMachineTest.java

	@Test
	public void testPurchace() {
		VendingMachine vm = new VendingMachine();
		vm.throwInto(100);
		vm.throwInto(10);
		vm.throwInto(10);
		Output o = vm.purchace();
		assertThat(j, is(1));
		assertThat(change, is(0));
	}

Output.java

public class Output {
	private int juiceCount;
	private int change;

	public Output(int juiceCount, int change) {
		this.juiceCount = juiceCount;
		this.change = change;
	}

	public int getJuiceCount() {
		return juiceCount;
	}

	public int getChange() {
		return change;
	}
}

ここまでのコードは以下のとおり。
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));
	}
}

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

	public boolean isPurchacable() {
		if (getJuiceStock() == 0) return false;
		return total >= getJuicePrice();
	}

	public Output purchace() {
		return new Output(1, 0);
	}
}

Juice.java


public class Juice {
	private String name;
	private int price;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
}

500円を投入して購入する。

VendingMachineTest.java

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

1000円を投入して購入する。

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 total = vm.getTotal();
		assertThat(total, is(0));
	}

在庫切れを実装する。
500円を投入して購入する。

VendingMachineTest.java

	@Test
	public void testPurchace500() {
		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);
			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));
	}

売上高を確認できるようにする。

VendingMachineTest.java

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

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

5月18日

前回来てなかった人

テキストの以下のサンプルを入力して実行してみよう!

  • WriterSample2
  • ReaderSample
  • FileCopy
  • CloseSample
  • DownloadSample
  • FileSample

Java7から自動でcloseしてくれる機能が追加された。
try-with-resources文を使うと自動的にcloseしてくれる。
自動でcloseしてくれるオブジェクトは、AutoClosableインタフェースを実装しているクラス。
以下のコードを入力して、CloseSampleと比較してみよう!

CloseSample2.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
 
public class CloseSample2 {
 
    public static void main(String[] args) throws IOException {
        String src = &quot;hoge.txt&quot;;
        String dest = &quot;hoge2.txt&quot;;
        copy(dest, src);
    }
 
    public static void copy(String dest, String src) throws IOException {
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(dest)) {
            int len;
            byte[] b = new byte[1024];
            while (true) {
                len = in.read(b);
                if (len &lt; 0) break;
                out.write(b, 0, len);
            }
        }
    }
}

前回来ていた人

Comparatorを実装して、ファイルを日付順とサイズ順でソートできるようにしてみよう!

Dir.java

import java.io.File;
import java.util.Arrays;

public class Dir {

	public static void main(String[] args) {
		String path;
		String option = null;
		if (args.length == 0) {
			path = &quot;.&quot;;
		} else {
			path = args[0];
			option = getOption(args);
		}
		File file = new File(path);
		dir(file, option);
	}

	private static String getOption(String[] args) {
		for (String s : args) {
			if (s.startsWith(&quot;/&quot;)) {
				return s;
			}
		}
		return null;
	}

	static SimpleDateFormat formatter = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);

	private static void dir(File parent, String option) {
		System.out.println(&quot;option=&quot; + option);
		File[] children = parent.listFiles();
		if (children != null) {
			if (option == null) {
				// ソートなし
			} else
			if (option.equals(&quot;/ON&quot;)) {
				// 名前順
				Arrays.sort(children, new FileNameComparator());
			} else
			if (option.equals(&quot;/OD&quot;)) {
				// 日付順
				Arrays.sort(children, new FileDateComparator());
			} else
			if (option.equals(&quot;/OS&quot;)) {
				// サイズ順
				Arrays.sort(children, new FileSizeComparator());
			}
			for (int i = 0; i &lt; children.length; i++) {
				File file = children[i];
				String d = formatter.format(file.lastModified());
				System.out.println(file.getName() + &quot;\t&quot; + file.length() + &quot;\t&quot; + d);
			}
		}
	}
}

FileNameComparator.java

import java.io.File;
import java.util.Comparator;

public class FileNameComparator implements Comparator&lt;File&gt; {
	@Override
	public int compare(File o1, File o2) {
		String n1 = o1.getName();
		String n2 = o2.getName();
		return -n1.compareTo(n2); // マイナスをつけて逆順にしている
	}
}

FileDateComparator.java

import java.io.File;
import java.util.Comparator;

public class FileDateComparator implements Comparator&lt;File&gt; {
	@Override
	public int compare(File o1, File o2) {
		long l1 = o1.lastModified();
		long l2 = o2.lastModified();
		if (l1 &lt; l2) return -1;
		if (l2 &lt; l1) return 1;
		return 0;
	}
}

コマンドプロンプトでの実行

作業ディレクトリに移動する。
現在は、c24 プロジェクトでプログラムを作成しているので、c24 のフォルダに移動する。

&gt; cd \All-in-One-Eclipse4.4\workspace\c24

java コマンドを実行して Dir プログラムを起動する。

&gt; java -cp bin Dir src /OD

java : java コマンド
-cp : クラスパスを指定するオプション(クラスファイルがbinフォルダにあるため)
bin : クラスファイルがある bin フォルダを指定している
Dir : 起動するクラスファイルを指定
src : ファイルの内容を表示するフォルダを指定している
/OD : ソートのオプションを指定している

テスト駆動開発

FizzBuzz でテスト駆動開発の練習をする。

FizzBuzz.java


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

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); // say(1)を呼び出して結果を取得する
		assertThat(s, is("1")); // 結果が "1" かどうかを比較
	}
}

5月15日

24章 ストリームを使いこなす

Writerを使う

WriterSample.java

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class WriterSample {

	public static void main(String[] args) throws IOException {
		String path = &quot;hoge.txt&quot;;
		Writer out = new FileWriter(path);
		out.write(&quot;foo&quot;);
		out.write(&quot;bar&quot;);
		out.close();
		System.out.println(&quot;END&quot;);
	}

}

Writerのデコレータを使う

WriterSample2.java

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class WriterSample2 {

	public static void main(String[] args) throws IOException {
		String path = &quot;hoge.txt&quot;;
		PrintWriter out = new PrintWriter(
				new BufferedWriter(new FileWriter(path)));
		out.println(&quot;foo&quot;);
		out.println(&quot;baa&quot;);
		out.close();
		System.out.println(&quot;END&quot;);
	}
}

Readerを使う

ReaderSample.java

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class ReaderSample {

	public static void main(String[] args) throws IOException {
		String path = &quot;hoge.txt&quot;;
		BufferedReader in = new BufferedReader(new FileReader(path));
		String line;
		while ((line = in.readLine()) != null) {
			System.out.println(line);
		}
		in.close();
	}

}

バイナリデータの入出力

OutputStreamを使う

OutputSample.java

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;


public class OutputSample {

	public static void main(String[] args) throws IOException {
		String path = &quot;btest.bin&quot;;
		OutputStream out = new FileOutputStream(path);
		byte[] b = new byte[] {0x0a, 0x0b, 0x0c};
		out.write(b);
		out.close();
		System.out.println(&quot;END&quot;);
	}

}

InputStreamを使う

InputSample.java

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;


public class InputSample {

	public static void main(String[] args) throws IOException {
		String path = "btest.bin";
		InputStream in = new FileInputStream(path);
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int len;
		byte[] b = new byte[1024];
		while (true) {
			len = in.read(b);
			if (len < 0) break;
			out.write(b, 0, len);
		}
		in.close();
		byte[] result = out.toByteArray();
		for (int i = 0; i < result.length; i++) {
			System.out.print(Integer.toHexString(result[i]) + " ");
		}
	}

}

ファイルをコピーする

FileCopy.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileCopy {

	public static void main(String[] args) throws IOException {
		String src = "hoge.txt";
		String dest = "hoge2.txt";
		InputStream in = new FileInputStream(src);
		OutputStream out = new FileOutputStream(dest);
		int len;
		byte[] b = new byte[1024];
		while (true) {
			len = in.read(b);
			if (len < 0) break;
			out.write(b, 0, len);
		}
		in.close();
		out.close();
		System.out.println("END");
	}

}

必ずcloseする

CloseSample.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;


public class CloseSample {

	public static void main(String[] args) throws IOException {
		String src = "hoge.txt";
		String dest = "hoge2.txt";
		copy(dest, src);
	}

	public static void copy(String dest, String src) throws IOException {
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(src);
			out = new FileOutputStream(dest);
			int len;
			byte[] b = new byte[1024];
			while (true) {
				len = in.read(b);
				if (len < 0) break;
				out.write(b, 0, len);
			}
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {}
			}
			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {}
			}
		}
	}
}

Java7から自動でcloseしてくれる機能が追加された。
try-with-resources文を使うと自動的にcloseしてくれる。
自動でcloseしてくれるオブジェクトは、AutoClosableインタフェースを実装しているクラス。

CloseSample2.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class CloseSample2 {

	public static void main(String[] args) throws IOException {
		String src = "hoge.txt";
		String dest = "hoge2.txt";
		copy(dest, src);
	}

	public static void copy(String dest, String src) throws IOException {
		try (InputStream in = new FileInputStream(src);
			 OutputStream out = new FileOutputStream(dest)) {
			int len;
			byte[] b = new byte[1024];
			while (true) {
				len = in.read(b);
				if (len < 0) break;
				out.write(b, 0, len);
			}
		}
	}

}

Webページのコピー(ファイル以外の例)

DownloadSample.java を try-with-resources構文で書き換えてみる。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;

public class DownloadSample {

	public static void main(String[] args) throws IOException {
		String dest = "dest.html";
		URL url = new URL("http://www.yahoo.co.jp/");
		try (
				InputStream in = url.openStream();
				OutputStream out = new FileOutputStream(dest)
			) {
			int len;
			byte[] b = new byte[1024];
			while (true) {
				len = in.read(b);
				if (len < 0) break;
				out.write(b, 0, len);
			}
		}
	}
}

Fileクラスを使う

FileSample.java

import java.io.File;

public class FileSample {

	public static void main(String[] args) {
		File file = new File("C:/All-in-One-Eclipse4.4/workspace/c24/");
		list(file);
	}

	private static void list(File parent) {
		File[] children = parent.listFiles();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				File file = children[i];
				if (file.isDirectory()) {
					list(file);
				} else {
					System.out.println(file.getAbsolutePath());
				}
			}
		}
	}
}

Dirコマンドを作ってみよう

Dir.java

import java.io.File;

public class Dir {

	public static void main(String[] args) {
		File file = new File(args[0]);
		dir(file);
	}

	private static void dir(File parent) {
		File[] children = parent.listFiles();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				File file = children[i];
				System.out.println(file.getName());
			}
		}
	}

}
  1. 引数なしの場合、現在のディレクトリの内容を表示する
  2. /OS オプションが指定されたらサイズ順にソートする
  3. /OD オプションが指定されたら日付順にソートする

引数なしの場合、現在のディレクトリの内容を表示する

Dir.java

import java.io.File;

public class Dir {

	public static void main(String[] args) {
		String path;
		if (args.length == 0) {
			path = ".";
		} else {
			path = args[0];
		}
		File file = new File(path);
		dir(file);
	}

	private static void dir(File parent) {
		File[] children = parent.listFiles();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				File file = children[i];
				System.out.println(file.getName());
			}
		}
	}
}

オプションの取得と条件分岐

Dir.java

import java.io.File;

public class Dir {

	public static void main(String[] args) {
		String path;
		String option = null;
		if (args.length == 0) {
			path = ".";
		} else {
			path = args[0];
			option = getOption(args);
		}
		File file = new File(path);
		dir(file, option);
	}

	private static String getOption(String[] args) {
		for (String s : args) {
			if (s.startsWith("/")) {
				return s;
			}
		}
		return null;
	}

	private static void dir(File parent, String option) {
		File[] children = parent.listFiles();
		if (children != null) {
			if (option == null) {
				for (int i = 0; i < children.length; i++) {
					File file = children[i];
					System.out.println(file.getName());
				}
			}
			if (option.equals("/OD")) {
				// 日付順
			}
			if (option.equals("/OS")) {
				// サイズ順
			}
		}
	}

}