5月17日

Webアプリケーション開発の基本

EclipseでWebアプリケーションを開発するには、そのためのプロジェクトを作成する。
基本的なWebアプリケーションの場合、「動的Webプロジェクト」を作成する。

Webアプリケーションを作成する場合、Eclipseのパースペクティブを「JavaEE」に切り替えると便利。
メニューから[ウィンドウ]-[パースペクティブ]-[パースペクティブを開く]-[その他]を選択し、ダイアログで「JavaEE」を選択する。

動的Webプロジェクト作成後、「新規サーブレット」を作成する。
名前:HelloServlet
スタブは doGet() のみを選択する。

生成された HelloServlet のコードに、テキストの「リスト16-7」のコードを記述する。

画面下側のフレーム内にある「サーバー」タブを選択する。

Webアプリケーションを動作させるためのサーバーを作成する。
「使用可能なサーバーがありません。・・・」をクリックする。

「Tomcat8サーバー」を選択して「次へ」をクリック。
「すべて追加」または「web」プロジェクトを選択して「追加」をクリックし、「web」を右側に移動する。
「完了」をクリック。

サーバータブにTomcat8サーバーが現れるので、右クリックして「開始」をクリックする。

Eclipseのツールバーの「Webブラウザーを開く」(地球のアイコン)をクリックしてWebブラウザを開く。
アドレスバーに「http://localhost:8080/web/HelloServlet」を入力してエンターすると、現在時刻が表示される。

JSP

JSPのサンプルを作成する。
プロジェクト名を右クリックし[新規]-[JSPファイル]を選択する。
ファイル名を「index.jsp」とする。
「次へ」をクリックするとテンプレートが表示されるので、そのまま「完了」をクリックし、JSPファイルを生成する。
WebContentフォルダの下に「index.jsp」が作られている。

JSPの基本

このあたりを参考にする。
サーブレット(Servlet) / JSP入門

JSPには以下の要素がある。
・スクリプトレット
・式
・宣言
・コメント

簡単なJSPの例

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSPサンプル</title>
</head>
<body>

<h1>JSPサンプル</h1>

<h2>スクリプトレット</h2>
<%
	out.println(new java.util.Date());
	for (int i = 0; i < 5; i++) {
		out.println("<div>i=" + i + "</div>");
	}
%>

<h2>式</h2>
<%= 5*5 %>

<h2>宣言</h2>
<%!
	int count = 0;
%>
<%=count %>

<h2>コメント</h2>
<!-- HTMLコメント -->
<%-- JSPコメント --%>

</body>
</html>

JSTL

JSTLは、JSP Standard Tag Libraryのことで、よく使われているタグライブラリ。

\\KGAKUSEI1\share\澤田\jstl\lib にある、以下の2ファイルをコピーする。
・jstl.jar
・standard.jar

プロジェクト内の WebContent/WEB-INF/lib に貼り付ける。

JSTLを使ったサンプルコードを index.jsp に追加する。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSPサンプル</title>
</head>
<body>

<h1>JSPサンプル</h1>

<h2>スクリプトレット</h2>
<%
	out.println(new java.util.Date());
	for (int i = 0; i < 5; i++) {
		out.println("<div>i=" + i + "</div>");
	}
%>

<h2>式</h2>
<%= 5*5 %>

<h2>宣言</h2>
<%!
	int count = 0;
%>
<%=count %>

<h2>コメント</h2>
<!-- HTMLコメント -->
<%-- JSPコメント --%>

<h2>JSTL</h2>
<c:set var="data" value="てすと!!" />
<c:out value="${data}" />


</body>
</html>

文字化けしている人は、以下の設定を確認する。
[ウィンドウ]-[設定]
左側のツリーで[Web]-[JSPファイル]を選択する。
エンコードを「UTF-8」に設定して「OK」する。

5月13日

レッドブルを購入するテストがうまくいかなかったので、購入できるかどうかを調べてみるために、isPurchasable()を追加してみる。

	@Test
	public void testレッドブルを購入する() {
		VendingMachine vm = new VendingMachine();
		RedBull r = (RedBull) vm.purchase("レッドブル");
		assertNull(r);
		vm.insert(200);
		boolean b = vm.isPurchasable("レッドブル");
		assertEquals(true, b);
		r = (RedBull) vm.purchase("レッドブル");
		assertNotNull(r);
	}

購入できないといわれるので、原因がどこかを考える。

200円を投入しているけど、200円玉などない!ので、お金が入ってない!

	@Test
	public void testレッドブルを購入する() {
		VendingMachine vm = new VendingMachine();
		RedBull r = (RedBull) vm.purchase("レッドブル");
		assertNull(r);
		vm.insert(100);
		vm.insert(100);
		boolean b = vm.isPurchasable("レッドブル");
		assertEquals(true, b);
		r = (RedBull) vm.purchase("レッドブル");
		assertNotNull(r);
	}

100円を2回投入すれば買えるようになった。

最後に、3種類の飲み物を購入してみる。
売上高とおつりを確認して終わりとする。

	@Test
	public void test複数の飲み物を購入する() {
		VendingMachine vm = new VendingMachine();
		vm.insert(1000);
		Coke c = (Coke) vm.purchase("コーラ");
		assertNotNull(c);
		Water w = (Water) vm.purchase("水");
		assertNotNull(w);
		RedBull r = (RedBull) vm.purchase("レッドブル");
		assertNotNull(r);
		int sa = vm.getSaleAmount();
		assertEquals(420, sa);
		int amount = vm.getAmount();
		assertEquals(580, amount);
		int change = vm.refund();
		assertEquals(580, change);
	}

テキスト16章

ストリームは、Javaがデータの入出力を行うための概念。

まずはファイルから文字列を読み書きするサンプルを作成してみる。

Filer.java

package tdd;

public class Filer {
	public long read(String name) {
		return 0;
	}
}

ファイルを読み込んでバイト数を返すメソッドread()を作ってみる。

テストを書く。

package tdd;

import static org.junit.Assert.*;

import java.io.IOException;

import org.junit.Test;

public class FilerTest {

	@Test
	public void testRead() {
		Filer f = new Filer();
		long l;
		l = f.read("Coke.java");
		assertEquals(56, l);
	}
}

テストにパスするようにコードを修正する。

package tdd;

public class Filer {
	public long read(String name) {
		return 56;
	}
}

テストを追加する。

package tdd;

import static org.junit.Assert.*;

import java.io.IOException;

import org.junit.Test;

public class FilerTest {

	@Test
	public void testRead() {
		Filer f = new Filer();
		long l;
		l = f.read("Coke.java");
		assertEquals(56, l);
		l = f.read("Drink.java");
		assertEquals(52, l);
	}
}

実際のファイルにアクセスするようコードを修正。

package tdd;

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

public class Filer {
	public long read(String name) throws IOException {
		long l = 0;
		FileReader r = new FileReader(name);
		return 56;
	}
}

例外をスローするので、テスト側も修正。
FileNotFoundExceptionを無視しないようにするため、RuntimeExceptionを作り直してスローして、テストケースが例外を認識できるようにする。

package tdd;

import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;

import org.junit.Test;

public class FilerTest {

	@Test
	public void testRead() {
		Filer f = new Filer();
		long l;
		try {
			l = f.read("Coke.java");
			assertEquals(56, l);
			l = f.read("Drink.java");
			assertEquals(52, l);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

}

作業ディレクトリを調べるためのコードを追加。

package tdd;

import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;

import org.junit.Test;

public class FilerTest {

	@Test
	public void testRead() {
		File cd = new File(".");
		System.out.println(cd.getAbsolutePath());
		Filer f = new Filer();
		long l;
		try {
			l = f.read("Coke.java");
			assertEquals(56, l);
			l = f.read("Drink.java");
			assertEquals(52, l);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

}

作業ディレクトリからの相対パスでファイルを指定するようにテストコードを修正。

package tdd;

import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;

import org.junit.Test;

public class FilerTest {

	@Test
	public void testRead() {
		File cd = new File(".");
		System.out.println(cd.getAbsolutePath());
		Filer f = new Filer();
		long l;
		try {
			l = f.read("src/tdd/Coke.java");
			assertEquals(56, l);
			l = f.read("src/tdd/Drink.java");
			assertEquals(52, l);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

}

FileNotFoundExceptionが出なくなった。

実際にバイト数を数えるようにFilerを修正する。

Java7から使えるようになった、try-with-resources構文を使用してコードを記述する。

package tdd;

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

public class Filer {
	public long read(String name) throws IOException {
		long l = 0;
		try (FileReader r = new FileReader(name);) {
			while (true) {
				int c = r.read();
				if (c < 0) break;
				l++;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return l;
	}
}

テストにパスしたので、バイト数を正しく数えているらしい。

ファイルをコピーする機能を追加してみる。
空のメソッドを作成する。

	public long copy(String src, String dst) {
		return 0;
	}

テストを作成。コピーしたバイト数を返すことにする。

	@Test
	public void testCopy() {
		Filer f = new Filer();
		long l;
		l = f.copy("src/tdd/Coke.java", "Coke.java");
		assertEquals(56, l);
	}

copy() メソッドを実装する。

	public long copy(String src, String dst) {
		long l = 0;
		try (FileReader r = new FileReader(src);
			 FileWriter w = new FileWriter(dst);) {
			while (true) {
				int c = r.read();
				if (c < 0) break;
				l++;
				w.write(c);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return l;
	}

テストを実行すると、テストにパスする。
パッケージエクスプローラでプロジェクト名を右クリックし、「リフレッシュ」を選択すると、Coke.javaがtddプロジェクトの直下に現れる。

指定したURLのコンテンツをダウンロードするdownload() メソッドの追加。

	public void download(String url, String dst) {
		return;
	}

ダウンロードのテストを作成する。
Yahooのトップページをダウンロードしたらファイルのサイズが1000バイト以上あるだろう?

	@Test
	public void testDownload() {
		Filer f = new Filer();
		long l;
		f.download("http://www.yahoo.co.jp", "yahoo.html");
		File html = new File("yahoo.html");
		assertTrue(html.length() > 1000);
	}

download()メソッドを実装する。
URLクラスのopenStream()メソッドを呼ぶことで、コンテンツにアクセスする入力ストリームを取得できる。
入力ストリームから読み込んだ内容をファイルに出力すればよい。

	public void download(String url, String dst) throws IOException {
		long l = 0;
		URL u = new URL(url);
		try (InputStream is = u.openStream();
			 FileOutputStream os = new FileOutputStream(dst);) {
			while (true) {
				int c = is.read();
				if (c < 0) break;
				l++;
				os.write(c);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return;
	}

パッケージエクスプローラでプロジェクト名を右クリックし、「リフレッシュ」を選択すると、yahoo.htmlがtddプロジェクトの直下に現れる。

ファイルを開くと、HTMLがダウンロードできていることがわかる。

5月10日

ジュースを3種類管理できるようにする。
在庫にレッドブル(値段:200円、名前”レッドブル”)5本を追加する。
在庫に水(値段:100円、名前”水”)5本を追加する。
投入金額、在庫の点で購入可能なドリンクのリストを取得できる。

3種類のドリンクに対応するために、Stockを3個の要素を持つ配列にしてみる。
コンパイルエラーが発生するので、stock変数にアクセスしている部分をすべてstock[0]に変更する。
そうすると、コンパイルエラーは消せる。

package tdd;

public class VendingMachine {
	private int amount = 0;
	private int saleAmount = 0;
	private Stock[] stock;

	public VendingMachine() {
		stock = new Stock[3];
		stock[0] = new Stock();
		stock[0].setCount(5);
		stock[0].setName("コーラ");
		stock[0].setPrice(120);
	}

	public int insert(int money) {
		if (!isAcceptable(money)) return money;
		amount += money;
		return 0;
	}

	private boolean isAcceptable(int money) {
		if (money == 10) return true;
		if (money == 50) return true;
		if (money == 100) return true;
		if (money == 500) return true;
		if (money == 1000) return true;
		return false;
	}

	public int getAmount() {
		return amount;
	}

	public int refund() {
		int a = amount;
		amount = 0;
		return a;
	}

	public Stock getStock() {
		return stock[0];
	}

	public boolean isPurchasable() {
		if (amount < 120) return false;
		if (stock[0].getCount() == 0) return false;
		return true;
	}

	public Coke purchase() {
		if (!isPurchasable()) return null;
		amount -= 120;
		saleAmount += 120;
		int c = stock[0].getCount();
		stock[0].setCount(c - 1);
		return new Coke();
	}

	public int getSaleAmount() {
		return saleAmount;
	}

	public String[] getPurchasableNames() {
		return new String[1];
	}
}

追加したテストがパスするように実装する。

	public String[] getPurchasableNames() {
		if (amount >= 100) {
			String[] s = new String[1];
			s[0] = "水";
			return s;
		}
		return new String[0];
	}

水を購入するテストを追加する。

	@Test
	public void test水を購入する() {
		VendingMachine vm = new VendingMachine();
		Water w = (Water) vm.purchase("水");
		assertNull(w);
		vm.insert(100);
		w = (Water) vm.purchase("水");
		assertNotNull(w);
	}

現状ではpurchase()メソッド内で引数をチェックしてないので、コーラの販売しかできない。
水を購入できるように、実装しなおす必要がある。

抽象クラス Drink を追加して、CokeとWaterがDrinkを継承する。

package tdd;

public abstract class Drink {

}
package tdd;

public class Water extends Drink {

}
package tdd;

public class Coke extends Drink {

}

飲み物を購入するメソッド purchase()に引数を追加して種類を指定できるようにする。
戻り値の型を Drink に変更して、CokeとWaterの両方を購入できるようにする。
水を購入できるかどうかを調べるisPurchasable()メソッドにも引数を追加して、種類を指定できるようにする。

	public boolean isPurchasable(String name) {
		if (name.equals("コーラ")) {
			if (amount < 120) return false;
			if (stock[0].getCount() == 0) return false;
			return true;
		}
		if (name.equals("水")) {
			if (amount < 100) return false;
			if (stock[2].getCount() == 0) return false;
			return true;
		}
		return false;
	}

	public Drink purchase(String name) {
		if (!isPurchasable(name)) return null;
		amount -= 120;
		saleAmount += 120;
		int c = stock[0].getCount();
		stock[0].setCount(c - 1);
		return new Coke();
	}

purchase()メソッドを修正して、テストにパスするように実装する。

	public Drink purchase(String name) {
		if (!isPurchasable(name)) return null;
		if (name.equals("コーラ")) {
			amount -= 120;
			saleAmount += 120;
			int c = stock[0].getCount();
			stock[0].setCount(c - 1);
			return new Coke();
		}
		if (name.equals("水")) {
			amount -= 100;
			saleAmount += 100;
			int c = stock[2].getCount();
			stock[2].setCount(c - 1);
			return new Water();
		}
		return null;
	}

getStock()メソッドも現状はコーラしか参照していないので、複数の飲み物に対応できるように修正する。
まずはテストを変更する。
getStock()メソッドに引数を追加して飲み物の種類を指定できるようにする。

	@Test
	public void testコーラの在庫() {
		VendingMachine vm = new VendingMachine();
		Stock s = vm.getStock("コーラ");
		assertEquals(5, s.getCount());
		assertEquals("コーラ", s.getName());
		assertEquals(120, s.getPrice());
	}

	@Test
	public void test水の在庫() {
		VendingMachine vm = new VendingMachine();
		Stock s = vm.getStock("水");
		assertEquals(5, s.getCount());
		assertEquals("水", s.getName());
		assertEquals(100, s.getPrice());
	}

テストにパスするように実装しなおす。

	public Stock getStock(String name) {
		for (Stock s : stock) {
			if (s.getName().equals(name)) return s;
		}
		return null;
	}

getStock()を実装しなおしたことにより、isPurchasable()とpurchase()の実装を簡素化できるので、両方のメソッドをリファクタリングする。

リファクタリング (refactoring) とは、コンピュータプログラミングにおいて、プログラムの外部から見た動作を変えずにソースコードの内部構造を整理することである。また、いくつかのリファクタリング手法の総称としても使われる。
リファクタリング (プログラミング) – Wikipedia

isPurchasable()メソッドをリファクタリングする。

	public boolean isPurchasable(String name) {
		Stock s = getStock(name);
		if (s == null) return false;
		if (amount < s.getPrice()) return false;
		if (s.getCount() == 0) return false;
		return true;
	}

同様に、purchase()メソッドもリファクタリングできる。
ただし、飲み物の種類によって戻り値として返すDrinkのインスタンスが異なるため、その違いをStockクラスで吸収する。

	public Drink getDrink() {
		if (name.equals("コーラ")) return new Coke();
		if (name.equals("水")) return new Water();
		return null;
	}

StockにgetDrink()を追加したことで、purchase()メソッドをリファクタリングして簡略化できる。

	public Drink purchase(String name) {
		if (!isPurchasable(name)) return null;
		Stock s = getStock(name);
		amount -= s.getPrice();
		saleAmount += s.getPrice();
		int c = s.getCount();
		s.setCount(c - 1);
		return s.getDrink();
	}

うまく動いていない部分があるので、さらにテストを追加する。

	@Test
	public void test購入可能なドリンクのリストを取得() {
		VendingMachine vm = new VendingMachine();
		String[] names = vm.getPurchasableNames();
		assertEquals(0, names.length);
		vm.insert(100);
		names = vm.getPurchasableNames();
		assertEquals(1, names.length);
		assertEquals("水", names[0]);

		vm.insert(50);
		names = vm.getPurchasableNames();
		assertEquals(2, names.length);
	}

getPurchasableNames()を変更してテストにパスさせる。
コレクションフレームワークを使用して実装。

	public String[] getPurchasableNames() {
		ArrayList<String> list = new ArrayList<String>();
		for (Stock s : stock) {
			if (amount >= s.getPrice()) {
				list.add(s.getName());
			}
		}
		return list.toArray(new String[0]);
	}

レッドブルを購入するテストを追加する。

	@Test
	public void testレッドブルを購入する() {
		VendingMachine vm = new VendingMachine();
		RedBull r = (RedBull) vm.purchase("レッドブル");
		assertNull(r);
		vm.insert(200);
		r = (RedBull) vm.purchase("レッドブル");
		assertNotNull(r);
	}

5月6日

自動販売機の在庫を取得するメソッドを追加する。

	@Test
	public void testStock() {
		VendingMachine vm = new VendingMachine();
		Stock s = vm.getStock();
	}

在庫を表現するクラス Stock を作成する。
まずは、名前・価格・在庫数を表すフィールドだけを用意する。

package tdd;

public class Stock {
	private String name;
	private int price;
	private int count;
}

Eclipseではgetter/setterを自動生成できる。
メニューから[ソース]-[getterおよびsetterの生成]を選択する。
ダイアログで「すべて選択」をクリックして全部のフィールドにチェックを入れる。
挿入ポイントは「最後のメンバー」を選択する。
「完了」すると、getter/setterが生成される。

package tdd;

public class Stock {
	private String name;
	private int price;
	private int count;
	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;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
}

自動販売機は初期状態で120円のコーラを5本保持しているので、それを確認するテストコードを作成する。

	@Test
	public void testStock() {
		VendingMachine vm = new VendingMachine();
		Stock s = vm.getStock();
		assertEquals(5, s.getCount());
		assertEquals("コーラ", s.getName());
		assertEquals(120, s.getPrice());
	}

そのテストをパスするようにVendingMachineを実装する。

	public Stock getStock() {
		Stock s = new Stock();
		s.setName("コーラ");
		s.setPrice(120);
		s.setCount(5);
		return s;
	}

テストにパスしたら、次のテストを実装する。
次は、購入可能かどうかを投入金額と在庫を元に取得できるようにする。
購入可能かどうかを調べるメソッドを isPurchasable() としてbooleanで結果を返すようにする。
自動販売機では、購入できることを示すランプに対応する。

	@Test
	public void testPurchasable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchasable();
		assertEquals(false, b);
	}

VendingMachineにメソッドを追加する。
コンパイルエラーになっているうえにマウスカーソルを持っていくとメソッドを生成できる。

	public boolean isPurchasable() {
		return false;
	}

メソッドを生成するだけでテストにパスする。
500円を投入したら購入可能になるはずなので、それをテストに追加する。

	@Test
	public void testPurchasable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchasable();
		assertEquals(false, b);
		vm.insert(500);
		b = vm.isPurchasable();
		assertEquals(true, b);
	}

テストがエラーになるので、テストにパスするように実装する。
投入済みの金額が120円以上ならtrueを返すようにする。

	public boolean isPurchasable() {
		if (amount >= 120) return true;
		return false;
	}

テストにパスするので、実際に購入する部分のテストを追加する。
purchase()メソッドで購入の操作を表現する。
購入するとコーラ(Coke)が出てくるはずなので、それをテストで表現する。

	@Test
	public void testPurchasable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchasable();
		assertEquals(false, b);
		vm.insert(500);
		b = vm.isPurchasable();
		assertEquals(true, b);
		Coke c = vm.purchase();
		assertNotNull(c);
	}

Cokeクラスを作成し、purchase()メソッドでCokeのインスタンスを生成して返すようにする。

	public Coke purchase() {
		return new Coke();
	}

テストにパスするので、投入済みの金額が減ったことを確認するテストを追加する。

	@Test
	public void testPurchasable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchasable();
		assertEquals(false, b);
		vm.insert(500);
		b = vm.isPurchasable();
		assertEquals(true, b);
		Coke c = vm.purchase();
		assertNotNull(c);
		int amount = vm.getAmount();
		assertEquals(500 - 120, amount);
	}

テストにパスするようにコードを実装する。

	public Coke purchase() {
		amount -= 120;
		return new Coke();
	}

次は、在庫が減ったことを確認するテストコードを追加する。

	@Test
	public void testPurchasable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchasable();
		assertEquals(false, b);
		vm.insert(500);
		b = vm.isPurchasable();
		assertEquals(true, b);
		Coke c = vm.purchase();
		assertNotNull(c);
		int amount = vm.getAmount();
		assertEquals(500 - 120, amount);
		Stock s = vm.getStock();
		assertEquals(4, s.getCount());
	}

在庫を表す変数は、getStock()メソッドのローカル変数で宣言しているが、これをインスタンス変数に変更する。
コンストラクタ内で初期化する。

package tdd;

public class VendingMachine {
	private int amount = 0;
	private Stock stock;

	public VendingMachine() {
		stock = new Stock();
		stock.setCount(5);
		stock.setName("コーラ");
		stock.setPrice(120);
	}

getStock()メソッドは、インスタンス変数を返すように修正する。

	public Stock getStock() {
		return stock;
	}

購入操作で在庫を減らすコードを追加する。

	public Coke purchase() {
		amount -= 120;
		int c = stock.getCount();
		stock.setCount(c - 1);
		return new Coke();
	}

これでテストにパスする。
次は、売り上げ金額を確認するテストを追加する。
getSaleAmount()メソッドで売り上げ金額を取得できるようにする。

	@Test
	public void testPurchasable() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.isPurchasable();
		assertEquals(false, b);
		vm.insert(500);
		b = vm.isPurchasable();
		assertEquals(true, b);
		Coke c = vm.purchase();
		assertNotNull(c);
		int amount = vm.getAmount();
		assertEquals(500 - 120, amount);
		Stock s = vm.getStock();
		assertEquals(4, s.getCount());
		int sa = vm.getSaleAmount();
		assertEquals(120, sa);
	}

テストにパスするように実装する。

	public int getSaleAmount() {
		return 120;
	}

自動販売機のインスタンスを生成した直後は売り上げが0のはずなので、テストを追加する。

	@Test
	public void testPurchasable() {
		VendingMachine vm = new VendingMachine();
		int sa = vm.getSaleAmount();
		assertEquals(0, sa);
		boolean b = vm.isPurchasable();
		assertEquals(false, b);
		vm.insert(500);
		b = vm.isPurchasable();
		assertEquals(true, b);
		Coke c = vm.purchase();
		assertNotNull(c);
		int amount = vm.getAmount();
		assertEquals(500 - 120, amount);
		Stock s = vm.getStock();
		assertEquals(4, s.getCount());
		sa = vm.getSaleAmount();
		assertEquals(120, sa);
	}

テストにパスするように実装を修正する。
getSaleAmount()メソッドでは、インスタンス変数 saleAmount の値を返すようにする。
購入の操作の中で、投入金額を減らすと同時に売り上げ金額をプラスする。

package tdd;

public class VendingMachine {
	private int amount = 0;
	private int saleAmount = 0;
	private Stock stock;

	(略)

	public Coke purchase() {
		amount -= 120;
		saleAmount += 120;
		int c = stock.getCount();
		stock.setCount(c - 1);
		return new Coke();
	}

	public int getSaleAmount() {
		return saleAmount;
	}
}

「投入金額が足りない場合もしくは在庫がない場合、購入操作を行っても何もしない。」を実装するために、そのテストを追加する。
お金を入れていない状態では、purchase()メソッドを呼び出してもnullを返して、コーラが出てこないようにする。

	@Test
	public void testPurchase() {
		VendingMachine vm = new VendingMachine();
		Coke c = vm.purchase();
		assertNull(c);
	}

投入金額が120円未満の場合は購入できずにnullを返すようにする。

テストにパスしたら、在庫がない場合も購入できないようにするためのテストを追加する。

	@Test
	public void testPurchase() {
		VendingMachine vm = new VendingMachine();
		Coke c = vm.purchase();
		assertNull(c);
		vm.insert(1000);
		for (int i = 0; i < 5; i++) {
			c = vm.purchase();
			assertNotNull(c);
		}
		c = vm.purchase();
		assertNull(c);
	}
&#91;/java&#93;


VendingMachineに購入可能かどうかを調べるisPurchasable()メソッドがあるので、purchase()メソッド内でもそれを使うようにする。

&#91;java&#93;
	public Coke purchase() {
		if (!isPurchasable()) return null;
		amount -= 120;
		saleAmount += 120;
		int c = stock.getCount();
		stock.setCount(c - 1);
		return new Coke();
	}
&#91;/java&#93;

isPurchasable()メソッドで在庫を確認する。

&#91;java&#93;
	public boolean isPurchasable() {
		if (amount < 120) return false;
		if (stock.getCount() == 0) return false;
		return true;
	}
&#91;/java&#93;

これでテストにパスする。

次の機能拡張に挑戦する!



<blockquote>
ジュースを3種類管理できるようにする。
在庫にレッドブル(値段:200円、名前”レッドブル”)5本を追加する。
在庫に水(値段:100円、名前”水”)5本を追加する。
投入金額、在庫の点で購入可能なドリンクのリストを取得できる。
</blockquote>




	@Test
	public void test購入可能なドリンクのリストを取得() {
		VendingMachine vm = new VendingMachine();
		String[] names = vm.getPurchasableNames();
		assertEquals(0, names.length);
	}

4月14日

テストファーストの練習をもう一度

TDDブートキャンプ大阪の課題にチャレンジしてみよう!

TDDBC大阪

package tdd;

import static org.junit.Assert.*;

import org.junit.Test;

public class VendingMachineTest {

	@Test
	public void testInsert() {
		VendingMachine vm = new VendingMachine();
		int change = vm.insert(10);
		assertEquals(0, change);
		int amount = vm.getAmount();
		assertEquals(10, amount);
		vm.insert(10);
		amount = vm.getAmount();
		assertEquals(20, amount);
		change = vm.refund();
		assertEquals(20, change);
		amount = vm.getAmount();
		assertEquals(0, amount);
	}

	@Test
	public void testRefund() {
		VendingMachine vm = new VendingMachine();
		vm.insert(100);
		int amount = vm.getAmount();
		assertEquals(100, amount);
		int change = vm.refund();
		assertEquals(100, change);
	}

	@Test
	public void testInsertX() {
		VendingMachine vm = new VendingMachine();
		int change = vm.insert(1);
		assertEquals(1, change);
		change = vm.insert(5);
		assertEquals(5, change);
	}
}
package tdd;

public class VendingMachine {
	private int amount = 0;

	public int insert(int money) {
		if (!isAcceptable(money)) return money;
		amount += money;
		return 0;
	}

	private boolean isAcceptable(int money) {
		if (money == 10) return true;
		if (money == 50) return true;
		if (money == 100) return true;
		if (money == 500) return true;
		if (money == 1000) return true;
		return false;
	}

	public int getAmount() {
		return amount;
	}

	public int refund() {
		int a = amount;
		amount = 0;
		return a;
	}
}

3月31日

前回の復習と続き

前回は以下のテストを作成したところまで。

次のテストを追加する。

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

テストを実行するとエラーになることを確認する。

テストにパスするように say() メソッドを実装する。

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

テストを実行してパスすることを確認する。

次のテストを追加する。

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

テストを実行するとエラーになることを確認する。

テストにパスするように say() メソッドを実装する。

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

テストを実行してパスすることを確認する。

次のテストを追加する。
7、8は数字を返し、9は Fizz を返すので、次は 10 の場合のテストを作る。

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

テストを実行するとエラーになることを確認する。

テストにパスするように say() メソッドを実装する。

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

テストを実行してパスすることを確認する。

次のテストを追加する。
11、13,14は数字を返し、12は Fizz を返すので、次は 15 の場合のテストを作る。

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

テストを実行するとエラーになることを確認する。

テストにパスするように say() メソッドを実装する。

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

テストを実行すると、この実装ではエラーになる!
エラー情報を見ると、「FizzBuzz」ではなく「Fizz」が返されていることがわかる。
15の倍数は3でも割り切れるので、「Fizz」を返してしまう。

引数の値を評価するif文の順番を変更する。

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

テストを実行してパスすることを確認する。

3月24日

自己紹介

「さわださとし」で検索してください。

Webサイト

WordPressで作成しています。
授業内容を、随時記録していきます。

FizzBuzz

テスト駆動開発の練習としてFizzBuzzを作る。

新規Javaプロジェクト「tdd」を作成する。
新規Javaクラス「FizzBuzz」を作成する。
メソッド say() を追加する。

package tdd;

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

FizzBuzzクラスを右クリックし「新規」-「JUnitテストケース」を選択する。
「次へ」をクリックし、say()メソッドにチェックを入れて「完了」をクリックすると、FizzBuzzTestクラスが生成される。

テストの作成

testSay()メソッドの実装を変更する。

package tdd;

import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {

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

このテストにパスするように、FizzBuzzクラスのsay()メソッドを実装する。

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

テストにパスしたら、次のテストを追加する。

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

テストを実行するとエラーになることを確認する。

テストにパスするように say() メソッドを実装する。

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

テストを実行してパスすることを確認する。

次のテストを追加する。

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

テストを実行するとエラーになることを確認する。

テストにパスするように say() メソッドを実装する。

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

テストを実行してパスすることを確認する。

次のテストを追加する。

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

テストを実行するとエラーになることを確認する。

テストにパスするように say() メソッドを実装する。
if 文ばかり並ぶのはきれいじゃないので、コードを簡略化する。

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

テストを実行してパスすることを確認する。

次のテストを追加する。

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

テストを実行するとエラーになることを確認する。

テスト駆動開発では、エラーの状態で終わらせておく。
次に作業開始するときは、最初にテストを実行する。
エラーになるので、どこから作業開始すればいいのかがすぐにわかる。