5月22日

ServletとJSP

WebアプリケーションとはどういうものなのかをPowerPointで説明。

Webアプリケーション

プロジェクトを作成する

Eclipseで[新規]-[プロジェクト]を選択
Webの下にある[動的Webプロジェクト]を選択
プロジェクト名: tomcat
ターゲットランタイム: Apache Tomcat v8.0
「次へ」→「次へ」→「完了」でプロジェクトを作成する

HelloWorldServlet

Eclipseで[新規]-[その他]-[サーブレット]を選択
パッケージ名は jp.abc とする
クラス名は HelloWorldServlet とする

Tomcatサーバーの準備

Eclipseで[Java EE]パースペクティブを選択すると、画面の下に[サーバー]タブが表示される。
新規サーバーを作成する。
Tomcat8サーバーを作成し、tomcat プロジェクトを追加してサーバーを開始する。
ツールバーの[Webブラウザーを開く]をクリックし、以下のURLを入力する。

http://localhost:8080/tomcat/HelloWorldServlet

日時表示

HelloWorldServletを変更してブラウザ上に日時を表示してみる。

package jp.abc;

import java.io.IOException;
import java.io.Writer;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class HelloWorldServlet
 */
@WebServlet("/HelloWorldServlet")
public class HelloWorldServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public HelloWorldServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Writer w = response.getWriter();
		w.append("<html>");
		w.append("<body>");
		w.append(new Date().toString());
		w.append("<p>");
		w.append("Served at: ").append(request.getContextPath());
		w.append("<br />");
		w.append("Hello World!");
		w.append("</p>");
		w.append("</body>");
		w.append("</html>");
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

JSPを作ってみる

WebContent フォルダを右クリックし[新規]-[JSPファイル]を選択する。
ファイル名を hello.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>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
<p>JSPはTomcatによって、自動的にServletに変換されます。</p>
</body>
</html>

以下のURLにアクセスするとJSPの内容が表示される。

http://localhost:8080/tomcat/hello.jsp

JSTL

JSTLはJavaServerPages Standard Tag Libraryの略で、よく使われる便利なタグが用意されている。

\\kgakusei1\share\澤田\jstl\lib から Eclipseの WebContent – WEB-INF – lib フォルダにコピーする。

JSPの先頭にJSTLを使うためのコードを追加する。

<%@ 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>Hello World</title>

スクリプトレットでデータを用意し、requestに設定して c:forEach ループで利用してみる。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="java.util.*" %>
<!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>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
<p>JSPはTomcatによって、自動的にServletに変換されます。</p>
<%="TEST!!" %><BR>
<%=(20+30)*10 %><BR>
<%=new java.util.Date() %><BR>
<%-- JSPコメント --%>
<!-- HTMLコメント -->

${request.method}
<%=request.getMethod() %>
<br />

<%
	List<String> list = new ArrayList<>();
	list.add("キリン");
	list.add("サッポロ");
	list.add("アサヒ");
	request.setAttribute("data", list);
%>

<c:forEach var="s" items="${data}" varStatus="status">
	${status.index} : ${s} <br />
</c:forEach>

</body>
</html>

ServletとJSPの連携

Servletで作成したデータをJSPで表示する。

DispatchServletを作成する。

package jp.abc;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class DispatchServlet
 */
@WebServlet("/DispatchServlet")
public class DispatchServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public DispatchServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		List<String> list = new ArrayList<>();
		list.add("ドラゴンクエスト");
		list.add("ゼルダの伝説");
		list.add("ファイナルファンタジー");
		request.setAttribute("game", list);
		RequestDispatcher rd = request.getRequestDispatcher("hello.jsp");
		rd.forward(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

hello.jsp 側で forEach を増やす。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="java.util.*" %>
<!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>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
<p>JSPはTomcatによって、自動的にServletに変換されます。</p>
<%="TEST!!" %><BR>
<%=(20+30)*10 %><BR>
<%=new java.util.Date() %><BR>
<%-- JSPコメント --%>
<!-- HTMLコメント -->

${request.method}
<%=request.getMethod() %>
<br />

<%
	List<String> list = new ArrayList<>();
	list.add("キリン");
	list.add("サッポロ");
	list.add("アサヒ");
	request.setAttribute("data", list);
%>

<c:forEach var="s" items="${data}" varStatus="status">
	${status.index} : ${s} <br />
</c:forEach>

<c:forEach var="s" items="${game}" varStatus="status">
	${status.index} : ${s} <br />
</c:forEach>
</body>
</html>

5月19日

TDDBC大阪の自動販売機の課題を復習する。

今回はペアを変更する。

自分のパソコンに課題を作成していない人が半数いるので、その人のパソコンで実施する。

できるだけ、TDDBC大阪の課題だけを見て、これまでに作成したプログラムは見ないようにして、新鮮な気持ちで作成すること。

2回目なのでスムーズに実装できたペアは、追加の課題をやってみましょう。

  1. ジュースの種類に「お茶(110円)」を追加する。
  2. ジュースの種類に「オレンジジュース(130円)」を追加する。

さらに新しい機能も追加してみましょう。

電子マネーで購入できるようにする
電子マネーで購入する場合の動作を考えてみる。

  1. 電子マネー支払いを指定する
  2. 購入するジュースを選択する
  3. 電子マネー残高を調べる
  4. 残高が足りていれば残高を減らす
  5. 指定されたジュースを出力する

5月15日

Eclipseを起動したらテストを実行する。

「水を買う」でエラーとなっていることがわかるので、そこから作業を再開する。

100円を入れても水を買えないのは、canBuy()がコーラに対応するコードしか書かれていないため、正しく判定できていないから。

まずは、コーラの場合だけをif文で分けてみる。

	public boolean canBuy(String name) {
		if (name.equals("コーラ")) {
			if (colas.size() == 0) return false;
			if (total < 120) return false;
		}
		return true;
	}

お金を入れなくてもレッドブルと水を買えるようになってしまうので、水を買うところにテストを追加して買えないようにする。

	@Test
	public void 水を買う() {
		VendingMachine vm = new VendingMachine();
		Juice j = vm.buy("水");
		assertNull(j);
		vm.putMoney(100);
		j = vm.buy("水");
		assertThat(j.getName(), is("水"));
	}

コーラと同様に、レッドブルと水についてもif文を追加する。

	public boolean canBuy(String name) {
		if (name.equals("コーラ")) {
			if (colas.size() == 0) return false;
			if (total < 120) return false;
		}
		if (name.equals("レッドブル")) {
			if (redbulls.size() == 0) return false;
			if (total < 200) return false;
		}
		if (name.equals("水")) {
			if (waters.size() == 0) return false;
			if (total < 100) return false;
		}
		return true;
	}

テストにパスするようになる。

投入金額と在庫数をもとに、購入可能なドリンクのリストを取得できるようにする。
新しく作成するメソッドの仕様を考える必要がある。
購入可能ということなので、既存のcanBuy()メソッドのオーバーロードにする。
戻り値はJuiceの配列で、引数なしとする。

	public Juice[] canBuy() {
		return null;
	}

テストを作成する。
初期状態ではお金を投入していないので、何も購入できない。

	@Test
	public void 購入可能なドリンクのリスト() {
		VendingMachine vm = new VendingMachine();
		Juice[] j = vm.canBuy();
		assertThat(j.length, is(0));
	}

テストにパスするように修正する。

	public Juice[] canBuy() {
		return new Juice[0];
	}

100円を入れると水を買えるようになる。

	@Test
	public void 購入可能なドリンクのリスト() {
		VendingMachine vm = new VendingMachine();
		Juice[] j = vm.canBuy();
		assertThat(j.length, is(0));
		vm.putMoney(100);
		j = vm.canBuy();
		assertThat(j.length, is(1));
		assertThat(j[0].getName(), is("水"));
	}

テストにパスするように修正する。

	public Juice[] canBuy() {
		if (total < 100) return new Juice[0];
		Juice j = new Juice();
		j.setName("水");
		Juice[] ja = new Juice[1];
		ja[0] = j;
		return ja;
	}

さらに50円を投入すると、水とコーラが買えるようになる。

	@Test
	public void 購入可能なドリンクのリスト() {
		VendingMachine vm = new VendingMachine();
		Juice[] j = vm.canBuy();
		assertThat(j.length, is(0));
		vm.putMoney(100);
		j = vm.canBuy();
		assertThat(j.length, is(1));
		assertThat(j[0].getName(), is("水"));
		vm.putMoney(50);
		j = vm.canBuy();
		assertThat(j.length, is(2));
	}

フェイクコードでは困難なので、投入金額をもとにJuice配列を生成する。

さらに売切れの場合も考慮したテストを作成する。

	@Test
	public void 購入可能なドリンクのリスト売切あり() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(1000);
		Juice[] ja = vm.canBuy();
		assertThat(ja.length, is(3));
		for (int i = 0; i < 5; i++) {
			Juice j = vm.buy("水");
		}
		ja = vm.canBuy();
		assertThat(ja.length, is(2));
	}

すでに在庫と投入金額をもとに買えるかどうかを調べられるcanBuy()があるので、それを利用する。

	public Juice[] canBuy() {
		Juice[] juices = getJuiceVariety();
		List<Juice> list = new ArrayList<>();
		for (Juice j : juices) {
			if (canBuy(j.getName())) {
				list.add(j);
			}
		}
		return list.toArray(new Juice[0]);
	}

リファクタリングの例

Juiceに引数付きコンストラクタを追加する。

package tdd;

public class Juice {
	private String name;
	private int price;

	public Juice() {}
	public Juice(String name, int price) {
		this.name = name;
		this.price = 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;
	}
}

VendingMachineのコンストラクタを短く書き換えられるようになる。

	public VendingMachine() {
		for (int i = 0; i < 5; i++) {
			colas.add(new Juice("コーラ", 120));
			redbulls.add(new Juice("レッドブル", 200));
			waters.add(new Juice("水", 100));
		}
	}

buy()メソッドのリファクタリング例。

	public Juice buy(String name) {
		if (!canBuy(name)) return null;
		List<Juice> list = null;
		if (name.equals("コーラ")) {
			list = colas;
		}
		if (name.equals("レッドブル")) {
			list = redbulls;
		}
		if (name.equals("水")) {
			list = waters;
		}
		if (list != null) {
			Juice j = list.remove(0);
			amountSold += j.getPrice();
			total -= j.getPrice();
			return j;
		}
		return null;
	}

getJuiceVariety()のリファクタリング例。

	public Juice[] getJuiceVariety() {
		Juice[] j = new Juice[3];
		j[0] = new Juice("コーラ", 120);
		j[1] = new Juice("レッドブル", 200);
		j[2] = new Juice("水", 100);
		return j;
	}

5月12日

前回の授業でTDDのテストがエラーになる状態で終了しているので、まずはテストを実行してみる。

コーラの在庫を調べるテスト testJuiceStock() でエラーになっている。
VendingMachineの実装を修正してテストにパスするようにする。
0を返すところを5を返すようにするだけ。

	public int getStock(String string) {
		return 5;
	}

次は、「ステップ3 購入」に進む。
「投入金額、在庫の点で、コーラが購入できるかどうかを取得できる。」

このテストを作成する。

	@Test
	public void testCanBuy() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.canBuy("コーラ");
		assertThat(b, is(false));
	}

クイックフィックスでcanBuy()メソッドを作成する。

	public boolean canBuy(String string) {
		return false;
	}

テストにパスするので、200円を入れたら買えるというテストを追加する。

	@Test
	public void testCanBuy() {
		VendingMachine vm = new VendingMachine();
		boolean b = vm.canBuy("コーラ");
		assertThat(b, is(false));
		vm.putMoney(100);
		vm.putMoney(100);
		b = vm.canBuy("コーラ");
		assertThat(b, is(true));
	}

テストにパスするよう修正する。

	public boolean canBuy(String string) {
		if (total >= 120) return true;
		return false;
	}

「ジュース値段以上の投入金額が投入されている条件下で購入操作を行うと、ジュースの在庫を減らし、売り上げ金額を増やす。」の部分をテストコードで表現する。
売上金額を増やすのはあとで追加する。

	@Test
	public void testBuy() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(500);
		Juice j = vm.buy("コーラ");
		assertThat(j.getName(), is("コーラ"));
		int v = vm.getStock("コーラ");
		assertThat(v, is(4));
	}

在庫を減らすところをきちんと実装するために、Javaのコレクションフレームワークを使用する。
ここでは、重複したオブジェクトを使用できるListを使用する。

コーラの在庫を保持するためのListを用意して、コンストラクタで5個のコーラを追加する。

public class VendingMachine {
	private int total;
	private List<Juice> colas = new ArrayList<>();

	public VendingMachine() {
		for (int i = 0; i < 5; i++) {
			Juice c = new Juice();
			c.setName("コーラ");
			c.setPrice(120);
			colas.add(c);
		}
	}

コーラの在庫をListで表現したので、getStock()メソッドの戻り値は、Listに格納された数を返すようにする。

	public int getStock(String string) {
		return colas.size();
	}

buy()メソッドでは、コーラの在庫からコーラを1本取得して返すようにする。

	public Juice buy(String string) {
		Juice cola = colas.remove(0);
		return cola;
	}

在庫を減らす実装ができたので、次は売上金額を増やすところを実装する。
まずは、1本のコーラを購入したら売上金額が120円となるテストを書く。

	@Test
	public void testBuy() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(500);
		assertThat(v, is(0));
		Juice j = vm.buy("コーラ");
		assertThat(j.getName(), is("コーラ"));
		int v = vm.getStock("コーラ");
		assertThat(v, is(4));
		v = vm.getAmountSold();
		assertThat(v, is(120));
	}

クイックフィックスでgetAmountSold()メソッドを追加する。

	public int getAmountSold() {
		return 0;
	}

戻り値を120にすればテストにパスする。

	public int getAmountSold() {
		return 120;
	}

購入前は売上高0円であることをテストに追加する。

	@Test
	public void 購入() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(500);
		int v = vm.getAmountSold();
		assertThat(v, is(0));
		Juice j = vm.buy("コーラ");
		assertThat(j.getName(), is("コーラ"));
		v = vm.getStock("コーラ");
		assertThat(v, is(4));
		v = vm.getAmountSold();
		assertThat(v, is(120));
	}

売上高をきちんと返せるようにVendingMachineクラスを修正する。
売上高は、インスタンス変数として保持するのが適当に思われるので、amountSoldメンバを追加する。
getAmountSold()では、その値を返すようにする。

public class VendingMachine {
	private int total;
	private int amountSold;
	private List<Juice> colas = new ArrayList<>();

	(略)

	public int getAmountSold() {
		return amountSold;
	}

amountSold の値は、購入操作を行ったところで更新する。

	public Juice buy(String string) {
		Juice cola = colas.remove(0);
		amountSold += cola.getPrice();
		return cola;
	}

これでテストにパスするようになる。

次は「投入金額が足りない場合もしくは在庫がない場合、購入操作を行っても何もしない。」のテストを追加する。

	@Test
	public void 買えない場合() {
		VendingMachine vm = new VendingMachine();
		Juice j = vm.buy("コーラ");
		assertNull(j);
	}

購入可能かどうかを調べているcanBuy()メソッドを使用する。

	public Juice buy(String string) {
		if (!canBuy(string)) return null;
		Juice cola = colas.remove(0);
		amountSold += cola.getPrice();
		return cola;
	}

お金を不足していると買えなくなるので、テストにパスする。
在庫切れのテストを追加する。

	@Test
	public void 買えない場合() {
		VendingMachine vm = new VendingMachine();
		Juice j = vm.buy("コーラ");
		assertNull(j);
		vm.putMoney(1000);
		for (int i = 0; i < 5; i++) {
			j = vm.buy("コーラ");
		}
		j = vm.buy("コーラ");
		assertNull(j);
	}

現状では在庫のチェックを行っていないので、テストがエラーになる。
canBuy()メソッドで在庫の有無を確認することで、テストにパスするようになる。
canBuy()メソッドのように、複数の条件をチェックする場合は、NG条件でfalseを返すコードを書いて、最後までNG条件にあたらかった場合にtrueを返すようにすると、コードが読みやすくなる。

	public boolean canBuy(String string) {
		if (colas.size() == 0) return false;
		if (total < 120) return false;
		return true;
	}

売上高を取得できるようにする。
テストを追加する。

	@Test
	public void 売上高と払い戻し() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(1000);
		Juice j = vm.buy("コーラ");
		j = vm.buy("コーラ");
		int v = vm.getAmountSold();
		assertThat(v, is(240));
	}

buy()メソッドで売上高を計上するようにする。

	public Juice buy(String string) {
		if (!canBuy(string)) return null;
		Juice cola = colas.remove(0);
		amountSold += cola.getPrice();
		return cola;
	}

お釣りの払い戻し金額を確認するテストを追加する。

	@Test
	public void 売上高と払い戻し() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(1000);
		Juice j = vm.buy("コーラ");
		j = vm.buy("コーラ");
		int v = vm.getAmountSold();
		assertThat(v, is(240));
		v = vm.refund();
		assertThat(v, is(760));
	}

ステップ4 機能拡張
ジュースを3種類管理できるようにする。
在庫にレッドブル(値段:200円、名前”レッドブル”)5本を追加する。
在庫に水(値段:100円、名前”水”)5本を追加する。

	@Test
	public void ジュースの種類を取得() {
		VendingMachine vm = new VendingMachine();
		Juice[] j = vm.getJuiceVariety();
		assertThat(j.length, is(3));
		assertThat(j[0].getName(), is("コーラ"));
		assertThat(j[0].getPrice(), is(120));
		assertThat(j[1].getName(), is("レッドブル"));
		assertThat(j[1].getPrice(), is(200));
		assertThat(j[2].getName(), is("水"));
		assertThat(j[2].getPrice(), is(100));
	}

テストにパスするように実装を修正する。
きれいなコードを書こうとせずに、とりあえずはベタでコードにパスできるようにする。
テストにパスしたら、コードを改善する。(リファクタリング)

	public Juice[] getJuiceVariety() {
		Juice[] j = new Juice[3];
		Juice cola = new Juice();
		cola.setName("コーラ");
		cola.setPrice(120);
		j[0] = cola;
		Juice r = new Juice();
		r.setName("レッドブル");
		r.setPrice(200);
		j[1] = r;
		Juice w = new Juice();
		w.setName("水");
		w.setPrice(100);
		j[2] = w;
		return j;
	}

レッドブルを買えるようにする。

	@Test
	public void レッドブルを買う() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(500);
		Juice j = vm.buy("レッドブル");
		assertThat(j.getName(), is("レッドブル"));
	}

コーラと同じように、在庫を管理するListを用意する。
コンストラクタ内で初期化する。

public class VendingMachine {
	private int total;
	private int amountSold;
	private List<Juice> colas = new ArrayList<>();
	private List<Juice> redbulls = new ArrayList<>();
	private List<Juice> waters = new ArrayList<>();

	public VendingMachine() {
		for (int i = 0; i < 5; i++) {
			Juice j = new Juice();
			j.setName("コーラ");
			j.setPrice(120);
			colas.add(j);
			j = new Juice();
			j.setName("レッドブル");
			j.setPrice(200);
			redbulls.add(j);
			j = new Juice();
			j.setName("水");
			j.setPrice(100);
			waters.add(j);
		}
	}

購入の動作は、引数で品名を渡されているので、品名をもとにListを区別する。

	public Juice buy(String name) {
		if (!canBuy(name)) return null;
		if (name.equals("コーラ")) {
			Juice cola = colas.remove(0);
			amountSold += cola.getPrice();
			total -= cola.getPrice();
			return cola;
		}
		if (name.equals("レッドブル")) {
			Juice r = redbulls.remove(0);
			amountSold += r.getPrice();
			total -= r.getPrice();
			return r;
		}
		if (name.equals("水")) {
			Juice w = waters.remove(0);
			amountSold += w.getPrice();
			total -= w.getPrice();
			return w;
		}
		return null;
	}

エラーになるテストを作る。
現状はコーラの値段で買えるかどうかを判断しているので、レッドブルと水では適切に判断できない。
それを確認するテストを作成する。

	@Test
	public void レッドブルを買う() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(100);
		vm.putMoney(10);
		vm.putMoney(10);
		Juice j = vm.buy("レッドブル");
		assertNull(j);
		vm.putMoney(100);
		j = vm.buy("レッドブル");
		assertThat(j.getName(), is("レッドブル"));
	}

	@Test
	public void 水を買う() {
		VendingMachine vm = new VendingMachine();
		vm.putMoney(100);
		Juice j = vm.buy("水");
		assertThat(j.getName(), is("水"));
	}

120円入れるとレッドブルを買えてしまう。
100円入れても水を買えない。

5月8日

TDDでFizzBuzzを実装する続き

前回作成したFizzBuzzクラスのコード

package tdd;

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

前回作成したテストコード

package tdd;

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

import org.junit.Test;

public class FizzBuzzTest {

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

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

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

}

テストを実行するとエラーになるので、テストにパスするようにFizzBuzzクラスを修正する。

package tdd;

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

テストにパスする。
4と5に対するテストコードを追加する。
通常は4の場合は数値を文字列化するだけなので、テストを書かないが、ここでは練習のためにテストを作成する。
5の場合はBuzzと返すので、テストにパスしなくなる。

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

FizzBuzzクラスを修正する。

package tdd;

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

テストにパスする。
次に6の場合のテストを追加する。

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

FizzBuzzクラスを修正する。

package tdd;

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

同時に5のif文も書き換えたいけど残しておく、先にテストを書く。
7~9はうまくいきそうなので、テストを書かないことにする。
10ではエラーとなるはずなので、テストを書く。

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

テストにパスするようにFizzBuzzクラスを修正する。

FizzBuzzクラスを修正する。

package tdd;

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

11~14はうまくいきそうなのでテストは書かないでおく。
15ではFizzBuzzと返されるというテストを追加する。

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

さらにTDDの練習問題を行う
TDDBC大阪の課題を使わせてもらう。
TDD Boot Camp 大阪 3.0/課題( #tddbc )

自動販売機クラス(VendingMachine)を作成する。
最初の課題は、お金を投入することなので、putMoney()メソッドを作成する。
投入した金額の総額を返すということなので、戻り値でその値を返すようにする。

package tdd;

public class VendingMachine {
	public int putMoney(int amount) {
		return 0;
	}
}

テストを作成する。
新規でJUnitテストケースを作成する。
テスト対象メソッドとしてputMoney()にチェックを入れておく。

package tdd;

import static org.junit.Assert.*;

import org.junit.Test;

public class VendingMachineTest {

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

10円を投入するテストを作成する。

	@Test
	public void testPutMoney() {
		VendingMachine vm = new VendingMachine();
		int v = vm.putMoney(10);
		assertThat(v, is(10));
	}

テストにパスするように修正する。

	public int putMoney(int amount) {
		return 10;
	}

さらに10円を投入するテストを追加する。

	@Test
	public void testPutMoney() {
		VendingMachine vm = new VendingMachine();
		int v = vm.putMoney(10);
		assertThat(v, is(10));
		v = vm.putMoney(10);
		assertThat(v, is(20));
	}
}

テストにパスするように修正する。

	public int putMoney(int amount) {
		total += amount;
		return total;
	}

払い戻し操作を追加する。
メソッド名をrefund()とする。
まずテストを追加する。

	@Test
	public void testPutMoney() {
		VendingMachine vm = new VendingMachine();
		int v = vm.putMoney(10);
		assertThat(v, is(10));
		v = vm.putMoney(10);
		assertThat(v, is(20));
		v = vm.refund();
		assertThat(v, is(20));
	}

}

テストにパスするように修正する。

package tdd;

public class VendingMachine {
	private int total;
	public int putMoney(int amount) {
		total += amount;
		return total;
	}
	public int refund() {
		return total;
	}
}

お金を再投入して払い戻すテストを追加してみる。

	@Test
	public void testPutMoney() {
		VendingMachine vm = new VendingMachine();
		int v = vm.putMoney(10);
		assertThat(v, is(10));
		v = vm.putMoney(10);
		assertThat(v, is(20));
		v = vm.refund();
		assertThat(v, is(20));
		vm.putMoney(100);
		v = vm.refund();
		assertThat(v, is(100));
	}

}

テストにパスするように修正する。

package tdd;

public class VendingMachine {
	private int total;
	public int putMoney(int amount) {
		total += amount;
		return total;
	}
	public int refund() {
		int r = total;
		total = 0;
		return r;
	}
}

1円や5円などが投入されたらそのまま釣銭として出力する。
どう表現するか?
putMoney()メソッドの戻り値で釣銭を出力するように仕様変更して、合計金額を取得するメソッドを追加することにする。

テストを修正する。

	@Test
	public void testPutMoney() {
		VendingMachine vm = new VendingMachine();
		int v = vm.putMoney(10);
		assertThat(v, is(0));
		v = vm.getTotal();
		assertThat(v, is(10));

		v = vm.putMoney(10);
		assertThat(v, is(0));
		v = vm.getTotal();
		assertThat(v, is(20));

		v = vm.putMoney(50);
		assertThat(v, is(0));
		v = vm.getTotal();
		assertThat(v, is(70));

		v = vm.refund();
		assertThat(v, is(70));

		vm.putMoney(100);
		v = vm.refund();
		assertThat(v, is(100));
	}

テストにパスするように修正する。

package tdd;

public class VendingMachine {
	private int total;
	public int putMoney(int amount) {
		total += amount;
		return 0;
	}
	public int refund() {
		int r = total;
		total = 0;
		return r;
	}
	public int getTotal() {
		return total;
	}
}

1円や5円など、受け付けない金額が投入された場合のテストを書く。

	@Test
	public void testPutMoneyNG() {
		VendingMachine vm = new VendingMachine();
		int v = vm.putMoney(1);
		assertThat(v, is(1));
		v = vm.getTotal();
		assertThat(v, is(0));
	}

テストにパスするように修正する。

	public int putMoney(int amount) {
		if (amount == 10 ||
			amount == 50 ||
			amount == 100 ||
			amount == 500 ||
			amount == 1000) {
			total += amount;
			return 0;
		}
		return amount;
	}

値段と名前の属性からなるジュースを1種類格納できるということなので、まずはJuiceクラスを作成する。

package tdd;

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

自動販売機に格納されているジュースの名前と金額を取得できるようにする。
実際の自動販売機でサンプルが並んでいる様子を表現しようとしている。
Juiceクラスを作成したので、Juiceクラスの配列を取得するという仕様にしてみる。

	@Test
	public void testJuiceVariety() {
		VendingMachine vm = new VendingMachine();
		Juice[] j = vm.getJuiceVariety();
		assertThat(j.length, is(1));
		assertThat(j[0].getName(), is("コーラ"));
		assertThat(j[0].getPrice(), is(120));
	}

テストにパスするように修正する。

	public Juice[] getJuiceVariety() {
		Juice[] j = new Juice[1];
		Juice cola = new Juice();
		cola.setName("コーラ");
		cola.setPrice(120);
		j[0] = cola;
		return j;
	}

在庫を取得できるようにするために、テストを追加する。

	@Test
	public void testJuiceStock() {
		VendingMachine vm = new VendingMachine();
		int v = vm.getStock("コーラ");
		assertThat(v, is(5));
	}

getStock()メソッドを生成してコンパイルエラーを解消する。
テストはパスしない状態で終了。
次回へ続く。

	public int getStock(String string) {
		return 0;
	}

5月1日

ファイルを読み書きする
Eclipseで開発する場合、プロジェクトの直下が作業フォルダになる。
テキストでは絶対パスを記述しているが、ここでは相対パスでファイルを指定する。

package c14;

import java.io.FileReader;

public class Main16_1 {

	public static void main(String[] args) throws Exception {
		String filename = "test.txt";
		FileReader fr = new FileReader(filename);
		char c1 = (char)fr.read();
		char c2 = (char)fr.read();
		fr.close();
		System.out.println("c1=" + c1 + ", c2=" + c2);
	}

}

例外の処理をきちんとコードとして記述すると以下のようになる。

package c16;

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

public class Main16_1 {

	public static void main(String[] args) {
		String filename = "src\\c16\\Account.java";
		FileReader fr = null;
		try {
			fr = new FileReader(filename);
			char c1 = (char)fr.read();
			char c2 = (char)fr.read();
			System.out.println("c1=" + c1 + ", c2=" + c2);
		} catch (Exception e) {
			e.printStackTrace();
			try {
				if (fr != null) fr.close();
			} catch (IOException e1) {}
		}
	}

}

BufferedReader を使って1行ずつ読み込み、コンソールに出力するように書き換える。

package c16;

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

public class Main16_1 {

	public static void main(String[] args) {
		String filename = "src\\c16\\Account.java";
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(filename));
			while (br.ready()) {
				String s = br.readLine();
				System.out.println(s);
			}
		} catch (Exception e) {
			e.printStackTrace();
			try {
				if (br != null) br.close();
			} catch (Exception e1) {}
		}
	}

}

ファイルに文字を書き込んでみる。
テキストp.604のリスト16-2を、try-with-resources文で書き換えたものが以下のコード。

package c16;

import java.io.FileWriter;

public class Main16_2 {

	public static void main(String[] args) {
		String filename = "test.txt";
		try (FileWriter fw = new FileWriter(filename)) {
			fw.write('そ');
			fw.write('れ');
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

実行すると、c16プロジェクトの直下に test.txt が作られる。
表示されていない場合は、c16プロジェクトを右クリックして、「リフレッシュ」を選択すると、パッケージエクスプローラの表示が更新される。

ファイルを読み込んで別のファイルにコピーするプログラムを作成する。

package c16;

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class Copy {

	public static void main(String[] args) {
		String src = "src\\c16\\Account.java";
		String dst = "out.txt";
		try (FileInputStream in = new FileInputStream(src);
			 FileOutputStream out = new FileOutputStream(dst)) {
			while (true) {
				int c = in.read();
				if (c < 0) break;
				out.write(c);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

プログラムを実行すると、c16プロジェクトの直下に out.txt ファイルが生成される。
ファイルの内容は Account.java がコピーされている。

Webページを取得する
テキストp.605のリスト16-3を作ってみる。
アクセス先のURLは自由に指定してよい。

package c16;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

public class Main16_3 {

	public static void main(String[] args) {
		try {
			URL u = new URL("http://www.yahoo.co.jp");
			try (InputStream is = u.openStream()) {
				while (true) {
					int i = is.read();
					if (i < 0) break;
					char c  = (char)i;
					System.out.print(c);
				}
				System.out.println();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

}

実行すると、URLで指定したWebサイトのページの内容がコンソールに出力される。

FizzBuzz問題
FizzBuzz問題をTDDで実装する。

まず最初にFizzBuzzクラスを作成する。
指定した数値に対して、答えを返す say() メソッドを定義する。
とりあえず、nullを返しておく。

package tdd;

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

[新規]-[その他]で「JUnitテスト・ケース」を選択して、テストクラスを生成する。

テスト対象メソッドは say() を選択して完了するとテストクラスが生成される。

package tdd;

import static org.junit.Assert.*;

import org.junit.Test;

public class FizzBuzzTest {

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

テストを実装する。
1のときは文字列”1″が返されることを確認するテスト。

package tdd;

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

import org.junit.Test;

public class FizzBuzzTest {

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

テストにパスするようにFizzBuzzクラスを修正する。

package tdd;

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

2のテストを追加する。

package tdd;

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

import org.junit.Test;

public class FizzBuzzTest {

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

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

テストにパスするようにFizzBuzzを修正する。

package tdd;

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

3のテストを追加する。

package tdd;

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

import org.junit.Test;

public class FizzBuzzTest {

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

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

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

4月28日

Eclipseを使ったJavaアプリケーションの開発に慣れよう!

Calendar クラスなどは、Ctrl+SPACEで補完する。
自動的にimport文まで生成してくれるので便利!

テキストp.537のリスト14-3に、SimpleDateFormatを追加した例。

package c14;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Main14_3 {

	public static void main(String[] args) {
		Date now = new Date();
		Calendar c = Calendar.getInstance();
		c.setTime(now);
		int y = c.get(Calendar.YEAR);
		System.out.println("今年は" + y + "年です");
		c.set(2010, 8, 22, 1, 23, 45);
		SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println(f.format(c.getTime()));
	}

}

テキストp.558の練習問題をやってみよう。

練習問題14-1

package c14;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class Main14_1 {

	public static void main(String[] args) {
		Date now = new Date();
		Calendar c = Calendar.getInstance();
		c.setTime(now);
		int day = c.get(Calendar.DAY_OF_MONTH);
		c.set(Calendar.DAY_OF_MONTH, day + 100);
		Date d = c.getTime();
		SimpleDateFormat f = new SimpleDateFormat("yyyy年MM月dd日");
		System.out.println(f.format(d));
	}

}

練習問題14-2用のテストプログラムを作成する。

package c14;

public class AccountTest {

	public static void main(String[] args) {
		Account a = new Account();
		a.setAccountNumber("4649");
		a.setBalance(1592);
		System.out.println(a);

		Account a1 = new Account();
		a1.setAccountNumber("4649");
		Account a2 = new Account();
		a2.setAccountNumber(" 4649");
		System.out.println(a1.equals(a2));
	}

}