JUnit実践入門を読んで:コード補足その2

渡辺修司さん著の「JUnit実践入門」を読んで、自分なりに考えたテストコードを書きます。

第二段
第20章 テストダブルの活用 20.3 外部システムに依存するテスト

本では、mockitoを利用していましたが、私は、ExternalResourceを使って、getInputメソッドで、ファイルのInputStreamを返す方法をとりました。

テストコード

package ch20.ex03;

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

import java.io.InputStream;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;

public class NeworkResourcesTest {

	@Rule
	public NetworkLoaderResource resource = new NetworkLoaderResource();
	
	@Test
	public void NetworkResourcesのloadはHello_Worldを返す() throws Exception {
		NetworkResources sut = new NetworkResources();
		sut.loader = new NetworkLoader() {
			@Override
			public InputStream getInput() {
				return new NetworkLoaderResource().getInput();
			}
		};
		assertThat(sut.load(), is("Hello World"));
	}

	
	class NetworkLoaderResource extends ExternalResource {
		NetworkLoader loader;
		
		@Override
		protected void before() throws Throwable {
			loader = new NetworkLoader();
		}
		
		public InputStream getInput() {
			InputStream in = getClass().getResourceAsStream("input.txt");	
			return in;
		}
	}
}

]

ここで、input.txtファイルは、内容が

Hello World

で、Eclipseのプロジェクトでは src/test/resources/ch20/ex03に配置されます。

JUnit実践入門を読んで:コード補足その1

渡辺修司さん著の「JUnit実践入門」を読んで、写経して、コードが本に載っていなかったり、ダウンロードしたサンプルに入っていなかったりしたので、自分なりに考えて補足しました。

第一段
第20章 テストダブルの活用 20.1 システムに依存するテスト

リスト20.3に対するテストコードがないので、下記に記します。テスト対象コードのクラス名はMonthlyCalendar2とします。テストコードでは、MonthlyCalendar2を継承する無名クラスで、geCalendarメソッドをオーバーライドします。

package ch20.ex01;

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

import java.util.Calendar;

import org.junit.Test;


public class MonthlyCalendar2Test {

	@Test
	public void 現在時刻が20120131の場合getRemainingDaysは0を返す() throws Exception {
		MonthlyCalendar2 sut
		= new MonthlyCalendar2() {
			@Override
			Calendar getCalendar() {
				return newCalendar(2012, 1, 31);
			}
		};
		assertThat(sut.getRemainingDays(), is(0));
	}

	@Test
	public void 現在時刻が20120130の場合getRemainingDaysは1を返す() throws Exception {
		MonthlyCalendar2 sut
		= new MonthlyCalendar2() {
			@Override
			Calendar getCalendar() {
				return newCalendar(2012, 1, 30);
			}
		};
		assertThat(sut.getRemainingDays(), is(1));
	}

	@Test
	public void 現在時刻が20120201の場合getRemainingDaysは28を返す() throws Exception {
		MonthlyCalendar2 sut
		= new MonthlyCalendar2() {
			@Override
			Calendar getCalendar() {
				return newCalendar(2012, 2, 1);
			}
		};
		assertThat(sut.getRemainingDays(), is(28));
	}

	static Calendar newCalendar(int yyyy, int mm, int dd) {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, yyyy);
		cal.set(Calendar.MONTH, mm - 1);
		cal.set(Calendar.DATE, dd);
		cal.set(Calendar.HOUR, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		return cal;
	}

}

リスト20.4のテストコードを作って、テストをしようとしましたが、リスト20.4のコードだと、私はテストコードが書けませんでした。修正したテスト対象コードは次のようになりました。

テスト対象コード

package ch20.ex01;

import java.util.Calendar;

interface SystemCalendar {
	Calendar getInstance();
}

public class MonthlyCalendar3 implements SystemCalendar {
	@Override
	public Calendar getInstance() {
		return Calendar.getInstance();
	}
}

そして、テストコードは、下記のようになりました。テストで制御できない処理を別のクラスに移譲するのですが、その移譲クラスでgetRemainingDaysメソッドを実装しました。

package ch20.ex01;

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

import java.util.Calendar;

import org.junit.Test;

public class MonthlyCalendar3Test {

	@Test
	public void 現在時刻が20120131の場合getRemainingDaysは0を返す() throws Exception {
				
		DelegateObjectExample sut = new DelegateObjectExample();
		sut.setDate(2012, 1, 31);
		assertThat(sut.getRemainingDays(), is(0));
	}
	
	@Test
	public void 現在時刻が20120130の場合getRemainingDaysは1を返す() throws Exception {
		DelegateObjectExample sut = new DelegateObjectExample();
		sut.setDate(2012, 1, 30);
		assertThat(sut.getRemainingDays(), is(1));
	}
	
	@Test
	public void 現在時刻が20120201の場合getRemainingDaysは28を返す() throws Exception {
		DelegateObjectExample sut = new DelegateObjectExample();
		sut.setDate(2012, 2, 1);
		assertThat(sut.getRemainingDays(), is(28));
	}
}

class DelegateObjectExample {
	SystemCalendar sysCal = new MonthlyCalendar3();
	Calendar cal;
			
	public void setDate(int yyyy, int mm, int dd) {
		cal = sysCal.getInstance();
		cal = newCalendar(yyyy, mm, dd);
	}
	
	public int getRemainingDays() {
        return cal.getActualMaximum(Calendar.DATE) - cal.get(Calendar.DATE);
    }
	
	static Calendar newCalendar(int yyyy, int mm, int dd) {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, yyyy);
		cal.set(Calendar.MONTH, mm - 1);
		cal.set(Calendar.DATE, dd);
		cal.set(Calendar.HOUR, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		return cal;
	}
}

Androidラジオ語学講座ファイルダウンロード・再生アプリLangDroid公開終了のお知らせ

Mar 30, 2013、Androidラジオ語学講座ファイルダウンロード・再生アプリLangDroidの公開を終了しました。4月から、ストリーミングを公開しているサイトが、会員登録とストリーミング再生時ログインを要求するようになるので、本アプリの公開を終了しました。4月に入ったら、本アプリを改造して利用できるようになるか調査しますが、時間を要します。利用できない場合、公開停止は継続します。ご了承ください。

Androidラジオ語学講座ファイルダウンロード・再生アプリv1.1.41リリース

Androidラジオ語学講座ファイルダウンロード・再生アプリ v1.1.41 をリリースしました。2013年3/18にストリーミングを配信するサーバ側で仕様変更があったようで、アプリ側で、対応しました。今週公開分の語学ファイルをダウンロードできるようになりました。ただし、ストリーミングを公開しているサイトが、4月から会員登録をさせて、ストリーミング再生の時、ログインを要求するようになるようなので、本アプリは4月には使えなくなると思います。ご利用ありがとうございました。ダウンロードは、Google Playのラジオ語学講座ファイルダウンロード・再生アプリ(beta版)よりダウンロードできます。

3月いっぱいでLangDroidは公開、利用終了予定

某ラジオ放送局が、語学講座をストリーミングで公開していて、私は、それをAndroidアプリでダウンロードして、再生するアプリ LangDroid を公開していた。

公式なソースがない(追記、番組ストリーミングご利用方法の変更に関して)が、ストリーミングを公開しているサイトが、4月より、会員情報登録、PCでストリーミング再生の時、ログインを要求するようになるらしい。一応、4月になったら、本アプリで対応できるか調査したいと思うが、多分対応不可と思うので、3月いっぱいで、本アプリの公開、利用は終了になると思う。

私は、hikiで開発日記をつけているんだが、LangDroidについて最初に日記を書いたのは、2010年11月25日だった。このアプリのアイデアのヒントを得たのは、NHK語学講座ダウンロード @ ウィキ のページだ。このページによると、PCでソフトにより語学ファイルをダウンロードしていた。そのソフトではflvstreamerというコマンドラインソフトを使って、ストリーミングをファイルにダウンロードすることを行っていた。flvstreamerはGPLソフトで、それをAndroidアプリに組み込むと、Androidアプリのソースファイルを公開する義務が生じるし、コマンドラインツールをAndroidアプリに使うにはどうすればいいのかという課題があった。それらの課題には、2010年の夏から取り組んでいた、課題は何とかクリアできた。それについては、Androidデ部やABCのLTで発表し、資料は Androidデ部ミーティング発表資料ABC発表資料 ABC2011 Summer LT発表資料 – 「クローズドソースアプリでGPLソフトを使うには」にある。

本アプリは、語学ファイルを手動か予約自動でダウンロードすることができ、ダウンロードした語学ファイルをリスト表示することにより、聴きたいファイルを再生することができるし、また、外出時にダウンロード、再生ができるし、再生時に、休止や、シークバー操作で繰り返し再生ができ、ヒントを得たPCアプリに比べ格段に使い勝手がよかったと自負している。本アプリは、Google Playで平均評価4.6、レビュー数465、ダウンロード数 100,000~500,000を得て、私がはじめて本格的に開発して、公開したAndroidアプリとしては好評のできたった。Google PlayでのLangDroidの画面のキャプチャを記念にとった。

Google PlayでのLangDroidの画面

ただし、はじめて作ったためか、設計がよくなく、ソースコードも汚く、使い勝手が悪く、作り直しを検討していた。しかし、ストリーミングの内容の性質上、マネタイズに躊躇することがあり、作り直しのモチベーションがなかなか出てこず、今日に至ってしまった。有料アプリ、広告付き無料アプリ、アプリ内課金はダメだろうから、アプリ内課金をやって、任意で開発者に寄付してもらうのはどうかと思っていたが、4月に要ログイン仕様になるので、開発、公開、利用は終了せざるを得まい。結局、マネタイズは、アプリのメニューにサポートページをつけて、そこから私のサイトにアクセスしてもらい、サイトにはってある広告をクリックしてもらうという方法をとっていたが、開発コストに見合うお金が得られなかった。

LangDroidのご利用ありがとうございました。

リファクタリングRubyエディション 6.17のテストコード

今、リファクタリングRubyエディションを読んでいます。
テストコードが載っていないし、コードを補わないと動かなかったり、擬似コードで動かないコードが載っていたりして、ストレスマッハです。
6.17 動的メソッド定義(Dynamic Method Definition)のテストコードが書けたので、記事にします。

リファクタリング対象コードは、本では

	def failure
		self.state = :failure
	end

	def error
		self.state = :error		
	end

	def success
		self.state = :success
	end

となっていますが、stateはクラス変数で、アクセサを定義する必要があります。Ruby クラス変数のゲッタによると、クラス変数に対しては、attrなんとかでアクセサを定義することはできないそうなので、コードを書いて定義しました。リファクタリング対象コードは、次のようになります。

ファイル methodsample.rb

class MethodSample
	def state
		@@state
	end

	def state=(val)
		@@state = val
	end

	def failure
		self.state = :failure
	end

	def error
		self.state = :error		
	end

	def success
		self.state = :success
	end
end

これに対するテストコードは次のようになります。

ファイル test_methodsample.rb

require ‘test/unit’
require_relative ‘methodsample’

class TestMethodSample < Test::Unit::TestCase def setup @methodsample = MethodSample.new end def test_failure @methodsample.failure result = @methodsample.state expected_result = :failure assert_equal expected_result, result end def test_error @methodsample.error result = @methodsample.state expected_result = :error assert_equal expected_result, result end def test_success @methodsample.success result = @methodsample.state expected_result = :success assert_equal expected_result, result end end [/ruby] あと、この節のサンプル:def_eachを使って類似メソッドを定義するで、def_eachメソッドを定義するClassクラスは記述されているんですが、プロダクションコードでどう使うのか書かれていないので、補います。 ファイル methodsample.rb [ruby] require_relative 'class' class MethodSample def state @@state end def state=(val) @@state = val end def_each :failure, :error, :success do |method_name| self.state = method_name end end [/ruby] これは、Module#define_methodを抽象化する話を参考にさせていただきました。

Androidラジオ語学講座ファイルダウンロード・再生アプリv1.1.40リリース

PukiWikiの方でお知らせしましたが、Androidラジオ語学講座ファイルダウンロード・再生アプリ v1.1.40 をリリースしました。2013年3/11にストリーミングを配信するサーバ側で仕様変更があったようで、アプリ側で、対応しました。今週公開分の語学ファイルをダウンロードできるようになりました。しかし、期の変わり目に、サイトが仕様変更を行い、本アプリでファイルをダウンロードできなくなる恐れもあります。その場合は、ご了承ください。詳細は、LangDroid を参照ください。ダウンロードは、Google Playのラジオ語学講座ファイルダウンロード・再生アプリ(beta版)よりダウンロードできます。なお、以前からアナウンスしているのですが、このアプリは設計、作りが甘いため、作り直しを検討していますが、作り直しには着手していなく、機能は従来版と同じです。

アプリ開発用マシンをリプレースしました

今まで、Androidアプリは、4年前に発売されたCore 2 Quad q8400sのCPUとメモリ8GBを積んだマシンで、Virtual Box仮想マシンで開発していました。しかし、昨年、Ubuntu 12.04 amd64 desktopが出て、インストールしたら、動作が重くなり、Eclipseでの開発でイライラが増していました。DellのマシンにUbuntu Server 12.04を入れ、Jenkinsをインストールしてディスプレイなしで使っていたのですが、ディスプレイを接続しないと起動しない不具合が生じるようになりました。そこで、新しいマシンを新調して、Dellのマシンをお払い箱にして、Androidアプリ開発用に使っていたマシンをJenkins、Redmine、Gitサーバに転用することにしました。
新しいマシンは、いつものようにパーツを買ってきて組み立てました。CPUにはCore i7を買いたかったのですが、2、3万円して、高いので断念して、Core i5で妥協しました。Ubuntu Desktop amd64 12.04が動くようにしたかったので、ビックカメラで、パーツの仕様をメモって、スタバで情報収集をしたのですが、あまり有益な情報が得られなく、賭けに出ました。Ubuntuが動くかどうか懸念対象になるパーツは以下のものです。

マザーボード ASROCK H77 Pro4/MVP 7,980円
SATA 3TBハードディスク 3台 12,800円 x 3
ブルーレイディスクドライブバルク LG ‘HL-DT-ST’ ‘BD-RE BH14NS48 ‘ ‘1.00’ Removable CD-ROM 7,460円
ビデオカード 玄人志向 GF0GT630-E1GHD/D3 PCI-EX16 5,880円

マザーボードには、ビデオチップが内蔵しているのですが、Ubuntuではトラブルがあるような情報があるので、ビデオカードを追加買いしました。このビデオカードは、NVIDIAのドライバを使うと快適に使えるのですが、カーネルが更新されると、ビデオドライバをリビルドする必要がありますが、このビデオカードを使っているせいなのか、仮想ターミナルが開けずに、リモートで入って、lightdmをstopして、ビデオドライバをリビルドする必要がありました。イーサネットアダプタ、オーディオチップの認識は問題ないでした。ハードディスクは3TBを3台使いRAID5を組みました。ブルーレイドライブの認識も大丈夫そうです。
いつも、PCパーツを買うときは、Ubuntuが動くか、ハラハラだったのですが、今回は幸運に恵まれました。

リファクタリングRubyエディション6.5のテストコードを書いてみた

今、リファクタリングRubyエディションを読んでいます。6.5 一時変数からチェインへで、Selectクラスは、@optionsという配列にadd_optionを呼ぶたびに西暦を加えていきます。このテストコードを考えたのですが、結構面倒だったので、記事を書きます。

はじめ、テストコードでは配列をアサートすればいいかなと考えたのですが、そうではなかったです。配列とSelectの要素を比較するため、Selectクラスに要素のサイズを得るメソッドsizeと要素を取り出すメソッドelemを追加しました。

Selectクラスに追加したコード

def size
	@options.size
end

def elem(i)
	@options[i]
end

また、Selectの要素と配列の要素を比較するため、Test::Unitのassert_blockを使いました。テストコードは次のようになります。

require ‘test/unit’
require_relative ‘select’

class TestSelect < Test::Unit::TestCase def test_add_options result = Select.add_options expected_result = [1999, 2000, 2001, 2002] assert_block("array error") { boolean = true if (expected_result.size != result.size) boolean = false else for i in 0...expected_result.size do if (expected_result[i] != result.elem(i)) boolean = false end end boolean end } end end [/ruby] テスト対象コードをいじるのはあまり好ましくないだろうけど、ちょっと他の方法は思いつきませんでした。また、asssert_blockを使うときには、どの要素の値がどうで、期待値がどうとか表示できるようにした方がいいのでしょうが、目的がリファクタリングのためのテストコード作成だったので、そこまではやりませんでした。なお、assert_block内で、真偽値をreturnで返すようなことをやると、うまくテストコードが動きませんでした。

リファクタリングRubyエディション6.1に対するテストコード

今、リファクタリングRubyエディションを読んでいます。
6章に入ったら、いきなり標準出力するコードのリファクタリングが出てきます。JUnit実践入門を以前読んだとき、標準出力するコードのテストをやるには、スパイを利用して難しかった記憶があります。Rubyのテストでも、そうなのかなと、ググったのですが、Arieteという便利なgemがあることがわかりました。Rubyの標準出力をキャプチャしてユニットテストで使いやすくするモジュール Ariete
gemは次のようにインストールします。

$ gem install ariete

リファクタリング前のテスト対象コード owing.rb は

class Owing
	def initialize(name)
		@name = name
	end

	def print_owing(amount)
		print_banner
		puts "name: #{@name}"
		puts "amount: #{amount}"
	end

	def print_banner
		puts "banner"
	end

end

です。これに対するテストコード test_owing.rb は

require ‘test/unit’
require ‘ariete’
require_relative ‘owing’

class TestOwing < Test::Unit::TestCase include Ariete def setup @owing = Owing.new('foo') end def test_print_owing result = capture_stdout {@owing.print_owing(10)} expected_result = "banner\n" + "name: foo\n" + "amount: 10\n" assert_equal(expected_result, result) end end[/ruby] になります。 これで、標準出力しているコードでも、リファクタリングできるようになります。