SlideShare a Scribd company logo
最近の単体テスト	
2014年2月	
  
株式会社ゆめみ	
  	
  
森下	
  
@mokemokechicken	

1	
  
はじめに	
•  社内勉強会の資料です	
  
•  初心者〜中級者くらい向けです	
  
•  私なぞが単体テストについて書いていいのか
という気もしますが、、もう書いてしまいました	

2	
  
単体テストの基本	

3	
  
単体(Unit)テスト(Test)	
•  自動で何度も実行・検証できる	
  
•  モジュール・クラス・メソッド単位でテストする	
  
•  実装コードとテストコードが対応している	
def add(a, b) !
return a + b !
end !
	
def test_add !
... 	
end !

⇐	
  「実装コード」に対応した	
  

⇐	
  「テストコード」が(複数)ある	

4	
  
「単体テスト」じゃないテスト?	
•  Integra:onテスト	
  
–  統合テストとか結合テストとも言う	
  
–  複数のモジュールやシステムをまたがるテスト	

実際は、「単体テスト」とちょっぴり「Integra:onテスト」を含むテストコードをよく書きます	

5	
  
単体テストの大事な精神	
•  どんな環境でも軽快に確実に実行できること	
•  「ないよりはマシ」くらいの気持ちで書く	
•  完璧を求めない、ポイントを抑える	

6	
  
大事な原則	
•  テスト対象の実装コードの全てをカバーする	
  
–  メソッド・モジュールの数だけテストを書く	
  

•  テストコードのテストは書かない!	
  
•  変更の掟	
  
–  仕様を変更するなら先にテストを変更する	
  
–  リファクタリングするならテストは変更しない	
7	
  
全てにテストを書く?	
•  かかない部分があっても良い	
  
–  例えばView周りとかは、かなり工夫しないと意味
があるテストを記述できない	
  

•  なるべく書ける範囲を増やす気持ちが大事	
  

8	
  
TDDという概念	
•  テスト駆動開発(Test-­‐Driven	
  Development)	
  
•  テストを最初に書いてPassさせるという考え	
  
–  →	
  テスト実行	
  →	
  テスト失敗	
  
–  →	
  実装修正	
  	
  	
  	
  
–  →	
  テスト実行	
  →	
  テスト成功	
  

•  テストを最初に書く: 「テストファースト」と呼ぶ	
  
•  なかなかストイックな開発手法である	
9	
  
TDD、だがしかし	
•  ぶっちゃけ、いつもいつもTDDだと疲れる	
  
•  もちろんTDDが綺麗にハマる場合もある	
  
•  もしTDDで慣れている人は継続した方が良い	
  
•  私は「テストセカンド」くらいで良いと思う	
  
–  実装書いたらテスト書く	
  
–  だって、「無いよりマシ」なんだから	
  
10	
  
最大公約数を求める関数	

TDDの例	

11	
  
最大公約数を求める関数	
•  仕様	
  
–  名前は gcm(a,	
  b)	
  	
  
–  変な値が来たら例外を投げる	
  
–  それ以外は答えを返す	

12	
  
まずテストを書く	
基本的なケース1: 「12」と「28」なら「4」である	
  

describe 'gcm' do !
it 'should return 4 that gcm(12, 28)' do !
expect(gcm(12, 28)).to eq 4 !
end !
end	

※意味の通る英文になるのが好ましい(要英文センス)(私には無い)	
  

13	
  
テストを実行する	

テストは失敗する。	
  
	
  
だって、gcmという関数がないのだから。	

14	
  
そして実装を書く	
def gcm(a, b) !
4!
end !

おい! ちゃんと計算しろ!	
  

でもテストは通るからOK	
  

describe 'gcm' do !
it 'should return 4 that gcm(12, 28)' do !
expect(gcm(12, 28)).to eq 4 !
end !
end	
15	
  
さらにテストを書く	
describe 'gcm' do !
it 'should return 4 that gcm(12, 28)' do !
expect(gcm(12, 28)).to eq 4 !
end !
	
it 'should return 7 that gcm(21, 28)' do !
expect(gcm(21, 28)).to eq 7 !
end !
end!
基本的なケース2: 21と28なら7	
  

16	
  
実装を修正する	
def gcm(a, b) !
a == 12 ? 4 : 7 	
end !

おい! ちゃんと計算しろって!!!	
  

でもテストは通るからOK	
  

describe 'gcm' do !
it 'should return 4 that gcm(12, 28)' do !
expect(gcm(12, 28)).to eq 4 !
end !
	
it 'should return 7 that gcm(21, 28)' do !
expect(gcm(21, 28)).to eq 7 !
end !
end!

17	
  
よろしい、ならば戦争だ	
describe 'gcm' do !
it 'should return 4 that gcm(12, 28)' do !
expect(gcm(12, 28)).to eq 4 !
end !
	
it 'should return 7 that gcm(21, 28)' do !
expect(gcm(21, 28)).to eq 7 !
end !
	
it 'should return 7 that gcm(28, 21)' do !
大小入れ替え	
  
expect(gcm(28, 21)).to eq 7 !
end !
	
it 'should return 28 that gcm(28, 28)' do ! 同じ数	
  
expect(gcm(28, 28)).to eq 28 !
end !
	
it 'should raise error that gcm(-1, 6)' do !
変な値→例外来い	
  
expect{gcm(-1, 6)}.to raise_error !
end !
end!

18	
  
そろそろ真面目に実装する	
def gcm(a, b) !
raise ArgumentError.new if a < 1 || b < 1 !
if a < b !
gcm(b, a) 	
else !
if a % b == 0 !
b 	
else !
gcm(b, a % b) 	
end !
end !
end !

※テストに通りました	
  

19	
  
考察	
•  批判	
  
–  別にまだふざけた実装も可能じゃないか?	
  
–  文字列とか小数のケースをテストしてないよ	
  

•  でも、それは気にしなくても良い	
  
–  テストは関数の完全性を担保するわけではない	
  
–  「ポイントを抑える」「無いよりはマシ」である	
  
–  必要だと思えば追加しておきましょう	
  
•  小数の場合くらいはあるべきかな・・	
20	
  
テストのメリット	
•  目標ができる	
  
–  テストをパスすると何か達成した感じがある	
  

•  安心できる	
  
–  関数gcm()が正しいか自分なりに自信が持てる	
  
–  もっと安心したいから、調子に乗って実際もっとテスト
ケースを追加することも多い	
  →	
  品質も上がる	
  

•  仕様がはっきりする	
  
–  テストコードを見て、実装コードの挙動がわかる	
21	
  
単体テストの歴史?	

22	
  
元祖は JUnit	
  (だと思う)	
•  JUnit	
  (Java用のUnitTestライブラリ)	
  

–  最初に発表されたのが1997年頃らしい	
  
–  ちなみにJava1.0が1995年	
  

•  その後流行した(=良いやり方だった)	
  
–  RUnit	
  (ruby),	
  PHPUnit,	
  PyUnit(Python)	
  など続く	
  
–  xUnitが無い言語は少ないと思う	
  

•  これらをまとめて 「xUnit」 系のテストフレーム
ワークと呼ぶ	
23	
  
xUnit	
  の共通点・1	
# test_gcm.rb !

①	
  テストを書くファイル名:	
  	
  test_<元ファイル>	
  とする	
  

②	
  メソッド名: test_<対象メソッド>***	
  	
  とする	
  
def test_gcm_1 !
assert_equals(4, gcm(12, 28)) !

end !
③	
  assert*****	
  で値のcheckを行う	
  
④	
  assert*****	
  では(期待される値、実際の値)の順に書く	
  
⑤	
  assertで検証するのは、1テストメソッドに付き1つにするのがお作法	
  

※命名規則は、言語によってCamel	
  Caseになります	

24	
  
xUnit	
  の共通点・2	
def setup !

①	
  setup	
  に各テスト実行前の共通処理を書ける	
  

File.write('/tmp/hogehoge', "aaan" * 29) !
end !
	
def test_parse_file !
assert_equals(30, count_line('/tmp/hogehoge')) !
end !
	
def teardown !

②	
  teardownに各テスト実行後の共通処理を書ける	
  

File.delete('/tmp/hogehoge') !
end !
25	
  
xUnitのメリット	
•  ルールが簡単で覚えやすい	
  
•  意外とこれくらいできれば十分である	

26	
  
RSpec革命	
•  RSpec	
  (Rubyのテストフレームワーク)	
  
•  より自然に仕様や振る舞いを記述	
  
•  ぶっちゃけ、xUnit	
  とできることは同じ	
  
•  ただ、その華麗さにより xUnit	
  を過去のもの
にし始めている	
27	
  
RSpecの雰囲気	
describe Array, "when empty" do !

何についてテストするのかを書ける	
  

before do !
@empty_array = [] !
end !

	
  before	
  に各テスト実行前の共通処理	
  

	
it "should be empty" do !
@empty_array.should be_empty !

テスト内容をより明確に書ける	
  

end !
	

テスト条件の記述が読み易い	
  
it "should size 0" do !
@empty_array.size.should == 0 !
end !

	
after do !
@empty_array = nil !

	
  aberに各テスト実行後の共通処理	
  

end !
end!

28	
  
RSpecは内容を仕様書的に出力できる	
describe Array, "when empty" do !
before do !
@empty_array = [] !
end !
	
it "should be empty" do !
@empty_array.should be_empty !
end !
	
it "should size 0" do !
@empty_array.size.should == 0 !

こんな感じ	

%	
  spec	
  -­‐fs	
  array_spec.rb	
  

	
  
Array	
  when	
  empty	
  
-­‐	
  should	
  be	
  empty	
  
-­‐	
  should	
  size	
  0	

end !
	
after do !
@empty_array = nil !
end !
end!

29	
  
specブーム	
•  RSpecの派生が流行している	
  
–  PHPSpec,	
  Kiwi(Objec:ve-­‐C),	
  specs2(Scala)	
  
–  Jasmine(JavaScript),	
  Mspec?(C#)	
  

•  ただ、文法的な制約が言語によってあるので
RSpecほど美しいのは少ない	
  
–  Specs2	
  と Jasmine+CoffeeScript	
  くらいか?	
  

30	
  
xUnit	
  と	
  spec	
  どっちが良いの?	
•  ぱっとみ綺麗に書ける方を選ぶと良い	
  
•  でも、Ruby,	
  JS,	
  Scala	
  なら	
  spec系一択かな	
  
•  ゆめみでは	
  PHPは PHPSpec	
  を標準にしようかと
思っています	
  
–  PHPには色々spec系があるのですが、	
  
PHP的に奇天烈じゃないのでPHPSpecが良いのかな	
31	
  
単体テストは	
  
何をテストして	
  
何をテストしないのか?	

32	
  
単体テストは何をテストするのか	
•  ブラックボックステスト か ホワイトボックステストかで
異なる	
  
	
  
ちなみに	
  
•  ブラックボックステストの意味	
  
–  中の実装を見ないでテストする	
  
–  先ほどの最大公約数のテストはこちら	
  

•  ホワイトボックステストの意味	
  	
  

–  中の実装を見ても良いからテストする	
33	
  
ブラックボックステスト	
•  何をテストしているか	
  
–  実装コードの「仕様」をテストしている	
  

•  正常系・異常系・境界系などの入力値で正し
い振る舞いをするか検証する	
  
•  これは理想的なテスト	
34	
  
ホワイトボックステスト	
•  何をテストしているか	
  
–  実装コードの「ロジック」をテストしている	
  
–  実装が想定通り「外部への入出力」をするかをテストしている	
  

•  全ての分岐を一度は通るテストを書く	
  
–  コードカバレッジ(テストが通過した実装コード率)を高めるのが
一つの指標	
  
–  全ての「分岐の組み合わせ」はテストしなくても良い	
  
–  ただ、分岐が間違っていれば検知すること	
  

•  色々仮定を置いた上で「入出力」をテストする	
  
–  MockやStub(後述)	
  
35	
  
どっちが良いの?	
•  ブラックボックステストが書けるのが望ましい	
  
–  これが書ければリファクタリングが容易	
  

•  ブラックボックステストが書けないケースがあ
る(後述)	
  
–  ホワイトボックステストで頑張る	
  
•  無いよりマシ	
  

–  なるべくブラックボックステストになるよう設計する	
  
36	
  
単体テストは何をテストしないのか	
•  「他の関数・API・外部機能・フレームワーク等」	
  
–  の、呼び出し方が正しいか	
  
–  が、想定通り動くか	
  
などはテストしない。あくまで「単体」がメイン。	
  
	
  
目の前の「実装コード」がとにかく重要である	
  
	
  

•  テストしても良いけど、	
  
「どんな環境でも軽快に確実に実行できること」	
  
の方が重要なことは忘れずに	
  
	
  

37	
  
「テストコード」自体の品質	
•  どの程度間違いを検出できるか	
  
•  実装コードを誤修正して検出するかでわかる	
  
–  適当にコメントアウトしてみる	
  
–  異常な定数に変更してみる	
  
–  if	
  の 条件に not	
  を入れてみる	
  
–  and	
  を or	
  にしてみる	
  
–  Etc..	
38	
  
最近の単体テスト	

39	
  
もう一度、テストファースト	
•  テストファーストの元々の意味	
  
–  「最初にテストコードを書く」	
  

•  最近思うテストファースト	
  
–  「テストを行うことを最優先に考える」	
  

•  そして	
  
–  テストを支援するフレームワークを使う!	
  
–  必要なテストを支援しないフレームワークはダメです	
  
•  可能な限り使わない	
  
40	
  
最近の風潮	
•  ホワイトボックステストで構わないからどんど
ん書こう!	
  
•  MockとStubを活用して、今まで書きにくかった
テストもどんどん書こう!	
  

41	
  
MockとStub	
•  テストダブル(Test	
  Double)という概念がある	
  
–  hmp://ja.wikipedia.org/wiki/
%E3%83%86%E3%82%B9%E3%83%88%E3%83%8
0%E3%83%96%E3%83%AB	
  

•  「ダブル」は代役・影武者を意味する	
  
•  Mock、Stubはその中で定義される用語	
  
42	
  
MockとStubの違い	
•  この会では違いは重要ではないが一応	
  
•  Stub	
  
–  あるObjectのように振る舞うDouble	
  

•  Mock	
  
–  Stubに「その結果の検証」機能が付いたもの	
  

•  他にもSpyとかFakeとかいう用語もある	
43	
  
MockやStubが何故重要か	
•  生成するのが大変なObjectの代わりができる	
  
–  Frameworkが作成するようなObject	
  
–  ごにょごにょ深淵から湧いてくるObject	
  

•  外部APIなどにアクセスした「つもり」にできる	
  
–  アクセスした「つもり」で「結果」を与えられる	
  
•  どこでも実行できるテストになる	
  
•  いつでも同じ結果になるテストになる	
  

•  テスト可能コードが格段に簡単に増やせる	
44	
  
例えば	
「日曜日?」という関数が、こんな実装だったとする	
  

def is_sunday? !
Time.now.wday == 0 !
end !
一見こいつは手に負えない。	
  
何故なら Time.now	
  を直接呼んでいるので、	
  
テストを実行したときの日時に左右されてしまうからである。	
  
	
  
まあ、この実装は直せるが、とりあえずこれを例に考える。	
  

45	
  
考え方	
•  テストしたいのはこのロジックの部分だ	
  
•  Time.now	
  の結果によって、ちゃんと変化するかをテ
ストすれば良いのだ	
  
•  私はTime.now	
  を内部で使っていることを知っている	
  
•  Time.now	
  の結果を操れればテストできる!	
46	
  
Stubで振る舞いを変える	
Time	
  に	
  now	
  が来たら特定の値を返すように仕込む	
  
describe 'is_sunday?' do !
it 'should be true if today is Sunday' do !
# 2014/2/2 は 日曜日 	
Time.stub(:now).and_return(Time.new(2014, 2, 2)) !
expect(is_sunday?).to eq true !
end !
	
it 'should be false if today is Saturday' do !
# 2014/2/1 は 土曜日 	
Time.stub(:now).and_return(Time.new(2014, 2, 1)) !
expect(is_sunday?).to eq false !
end !
end!
47	
  
考察	
•  こんな風に動的に既存のObjectの振る舞いを変えら
れるかどうかは言語によって違う	
  
–  言語によってStubのHackがどこまでできるかは調べよう	
  

•  実装コードを修正しないとテストできない場合もある	
  
–  こういうのも言語によって色々テクニックがある	
  

•  テストのため実装を修正するのか!?	
  
–  答えはYES! YESだよ!!	
  
–  テストファーストだよ!	
48	
  
テストしにくそうな機能について	

もう少し具体例	

49	
  
郵便番号を元に都道府県をprintする機能	
•  仕様	
  
–  入力: 郵便番号(文字列)	
  
–  出力:	
  
•  都道府県文字列 →	
  標準出力 	
  
•  ZIPCODEが実在しない場合は何も出力しない	
  
こんな感じ	
class SomeClass!
def print_prefecture(zipcode) !
end !
end!
50	
  
郵便番号を元に都道府県をprintする機能	
•  都道府県を調べる方法	
  
–  hmp://zip.cgis.biz/	
  	
  の	
  APIを使う	
  
–  例:	
  hmp://zip.cgis.biz/xml/zip.php?zn=1030000	

51	
  
APIの戻り値: 実在する場合	
<ZIP_result> !
<result name="ZipSearchXML"/> !
<result version="1.01"/> !
<result request_url="http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn%3D1030000"/> !
<result request_zip_num="1030000"/> !
<result request_zip_version="none"/> !
<result result_code="1"/> !
<result result_zip_num="1030000"/> !
<result result_zip_version="0"/> !
<result result_values_count="1"/> !
<ADDRESS_value> !
<value state_kana="トウキョウト"/> !
<value city_kana="チュウオウク"/> !
<value address_kana="イカニケイサイガナイバアイ"/> !
<value company_kana="none"/> !

sample1,	
  とします	

<value state="東京都"/> !
<value city="中央区"/> !
<value address="none"/> !
<value company="none"/> !
</ADDRESS_value> !
</ZIP_result>!
52	
  
APIの戻り値: 実在しない場合	
<ZIP_result> !
<result name="ZipSearchXML"/> !
<result version="1.01"/> !
<result request_url="http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn
%3D1030900"/> !
<result request_zip_num="1030900"/> !
<result request_zip_version="none"/> !
<result result_code="1"/> !
<result result_zip_num="1030900"/> !
<result result_zip_version="0"/> !
<result result_values_count="0"/> !
</ZIP_result>!

sample2,	
  とします	

53	
  
この機能は少しテストしにくい	
•  外部APIアクセスがある	
  
•  出力を標準出力に行うことになっている	
  

まあしかしとりあえずやってみましょう	

54	
  
とりあえずテスト書いて実行	
describe SomeClass do !
※中身がない	
describe 'print_prefecture' do !
it 'should print 東京都 when zipcode=1030000' !
it 'should not print anything when zipcode=1030900' !
end !
end !

SomeClass	
  
	
  	
  print_prefecture	
  
	
  	
  	
  	
  should	
  print	
  東京都 when	
  zipcode=1030000	
  (PENDING:	
  Not	
  yet	
  implemented)	
  
	
  	
  	
  	
  should	
  not	
  print	
  anything	
  when	
  zipcode=1030900	
  (PENDING:	
  Not	
  yet	
  implemented)	
  
	
  
Finished	
  in	
  0.00034	
  seconds	
  

2	
  examples,	
  0	
  failures,	
  2	
  pending	
「Pending」になる。これもSpec系の特徴。	
  
「失敗」とは少し違う。「まだ書いてないよ」という状態。	

55	
  
よし、じゃあテストの中身を書こう	
  
・・・いや待てよ。。。	
•  テストが書けない	
  
–  返り値をテストするわけではないし	
  
–  APIアクセスあるからネットワーク切れてたらテストできな
いし	
  

•  この機能全体をブラックボックステストできない	
  
ホワイトボックステストしかない	

•  先に実装を進めよう	
  
–  テストしやすくなるように気をつけながら	
  
56	
  
とりあえずこんな感じで	
class SomeClass !
def print_prefecture(zipcode) !
prefecture = get_prefecture(zipcode) !

ここで都道府県が戻る想定	
  

!
if prefecture !
puts prefecture !
end !
end !
end !

Nil	
  じゃなければ出力	
  

57	
  
Stubでホワイトボックステスト	
describe SomeClass do !
describe 'print_prefecture' do !
before do !
@obj = SomeClass.new !
@obj	
  に毎回Objectを入れる共通処理	
  
end !
	
it 'should print 東京都 when zipcode=1030000' do !
@obj.stub(:get_prefecture => '東京都') 	
 get_prefecture	
  が 東京都
	
end !
end !
end!

を返すならば	
  

あれ?	
  Puts	
  したかってどうテストする?	
  

出力したか、という判定をするのが難しい。	
  
→	
  少し工夫が必要	
  
※Kernel.puts	
  を調べれば良いんですけどね。今回はこういう想定で。	
  

58	
  
実装コードをテストしやすく修正	
class SomeClass !
def print_prefecture(zipcode) !
prefecture = get_prefecture(zipcode) !
if prefecture !

output(prefecture)
end !
end !

!

出力する、というのを呼び出すようにする	
  

	

def output(str) !
puts str !
end !
end !

59	
  
Output()をCallすればOKと考える	

it 'should print 東京都 when zipcode=1030000' do !

get_prefecture	
  が 東京都 を返すなら	
  
@obj.stub(:get_prefecture => '東京都') 	

@objは	
  output(‘東京都’)	
  が呼ばれることが期待される	
  
expect(@obj).to receive(:output).with('東京都') !

じゃあ、実行しましょう	
  
@obj.print_prefecture('1030000') !

end !

となる	
  
60	
  
Outputされないケースも追加	
describe SomeClass do !
describe 'print_prefecture' do !
before do !
@obj = SomeClass.new !
end !
	
it 'should print 東京都 when zipcode is in 東京都 area' do !
@obj.stub(:get_prefecture => '東京都') 	
expect(@obj).to receive(:output).with('東京都') !
@obj.print_prefecture('1030000') !
end !
	
it 'should not print anything when zipcode doesnot exist' do !

Exampleの文言も修正する	
  

nil) !
expect(@obj).not_to receive(:output)

@obj.stub(:get_prefecture =>

@obj.print_prefecture('1030900') !
end !
end !
end !

!

get_prefectureがnilの時も追加する	
  

仮に:get_prefecture	
  が	
  nil	
  
期待:	
  output()は呼ばれない	
  

61	
  
テスト実行!	
SomeClass	
  
	
  	
  print_prefecture	
  
	
  	
  	
  	
  should	
  print	
  東京都 when	
  zipcode	
  is	
  in	
  東京都 area	
  
	
  	
  	
  	
  should	
  not	
  print	
  anything	
  when	
  zipcode	
  doesnot	
  exist	
  
	
  
Finished	
  in	
  0.00124	
  seconds	
  

2	
  examples,	
  0	
  failures	

テストが通った	
  
62	
  
続けて細かい実装を先に書いてしまう	
勢いってものがあるじゃないですか	
  
require 'net/http' !
require 'rexml/document' !
	
class SomeClass !
def print_prefecture(zipcode) !
prefecture = get_prefecture(zipcode) !
if prefecture !
output(prefecture) !
end !
end !
	
def get_prefecture(zipcode) !
xml_data = Net::HTTP.get(URI.parse("http://zip.cgis.biz/xml/zip.php?zn=#{zipcode}")) !
scan_prefecture(xml_data) !
end !
	
def scan_prefecture(xml_data) !
attr_state = REXML::Document.new(xml_data).elements['//@state'] !
attr_state ? attr_state.value : nil !
end !
	
def output(str) !
puts str !
end !
end !
63	
  

get_prefecture()	
  	
  	
  
と	
  	
  
scan_prefecture()	
  	
  
に分けた	
  
テスト: get_prefecture()	
実装コード	
  
def get_prefecture(zipcode) !
xml_data = Net::HTTP.get(URI.parse("http://zip.cgis.biz/xml/zip.php?zn=#{zipcode}")) !
scan_prefecture(xml_data) !
end !

テストコード	
  
describe 'get_prefecture' do !
it 'should call API Request with zipcode' do !
expect(Net::HTTP).to receive(:get) !
expect(URI).to receive(:parse).with(/zn=1030000/) !
@obj.get_prefecture('1030000') !
end !
!
	
it 'should return scan_prefecture result' do !
Net::HTTP.stub(:get => 'SOME DATA') !

期待:HTTP.get	
  が呼ばれる	
  
期待:URI.parse	
  が呼ばれて、	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  引数は	
  /zn=103000/	
  の	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  正規表現とMatchするはず。	
  

expect(@obj).to receive(:scan_prefecture).with('SOME DATA').and_return('RESULT') !
expect(@obj.get_prefecture('1030000')).to eq('RESULT') !
end !
end !

仮に: HTTP.get	
  が	
  ‘SOME	
  DATA’を返すとして、	
  
期待:	
  @obj.scan_prefecture(‘SOME	
  DATA’)が呼ばれる	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  その結果が	
  ‘RESULT’	
  だったとして、	
  
期待:	
  @obj.get_prefecture()	
  の返り値は	
  ’RESULT’	
  になる。	
  

64	
  
テスト: scan_prefecture()	
実装コード	
  
def scan_prefecture(xml_data) !
attr_state = REXML::Document.new(xml_data).elements['//@state'] !
attr_state ? attr_state.value : nil !
end !

テストコード	
  
describe 'scan_prefecture' do !
it 'should return 東京都 if the xml data is so' do !
expect(@obj.scan_prefecture(D1)).to eq '東京都' 	
end !
!

期待:
scan_pefecture(sample1)な
ら戻り値は 東京都 である	
  

!

	
it 'should return nil if the xml data doesnot contain pref info' do !
expect(@obj.scan_prefecture(D2)).to eq nil !
end !
end !

期待:
scan_pefecture(sample2)な
ら戻り値は	
  nil	
  である	
  

65	
  
※ブラックボックスになっている	
  
describe SomeClass do !
before do !
@obj = SomeClass.new !
end !
	
describe 'print_prefecture' do !
it 'should print 東京都 when zipcode is in 東京都 area' do !
@obj.stub(:get_prefecture => '東京都') 	
expect(@obj).to receive(:output).with('東京都') !
@obj.print_prefecture('1030000') !
end !
	
it 'should not print anything when zipcode doesnot exist' do !
@obj.stub(:get_prefecture => nil) !
expect(@obj).not_to receive(:output) !
@obj.print_prefecture('1030900') !
end !
end !
	
describe 'get_prefecture' do !
it 'should call API Request with zipcode' do !
expect(Net::HTTP).to receive(:get) !
expect(URI).to receive(:parse).with(/zn=1030000/) !
@obj.get_prefecture('1030000') !
end !
	
it 'should return scan_prefecture result' do !
Net::HTTP.stub(:get => 'SOME DATA') !
expect(@obj).to receive(:scan_prefecture).with('SOME DATA').and_return('RESULT') !
expect(@obj.get_prefecture('1030000')).to eq('RESULT') !
end !
end !
	
describe 'scan_prefecture' do !
it 'should return 東京都 if the xml data is so' do !
expect(@obj.scan_prefecture(D1)).to eq '東京都' 	
end !
	
it 'should return nil if the xml data doesnot contain pref info' do !
expect(@obj.scan_prefecture(D2)).to eq nil !
end !
end !
end !

テスト全体	
  

66	
  
テスト実行	
SomeClass	
  
	
  	
  print_prefecture	
  
	
  	
  	
  	
  should	
  print	
  東京都 when	
  zipcode	
  is	
  in	
  東京都 area	
  
	
  	
  	
  	
  should	
  not	
  print	
  anything	
  when	
  zipcode	
  doesnot	
  exist	
  
	
  	
  get_prefecture	
  
	
  	
  	
  	
  should	
  call	
  API	
  Request	
  with	
  zipcode	
  
	
  	
  	
  	
  should	
  return	
  scan_prefecture	
  result	
  
	
  	
  scan_prefecture	
  
	
  	
  	
  	
  should	
  return	
  東京都 if	
  the	
  xml	
  data	
  is	
  so	
  
	
  	
  	
  	
  should	
  return	
  nil	
  if	
  the	
  xml	
  data	
  doesnot	
  contain	
  pref	
  info	
  
	
  
Finished	
  in	
  0.00804	
  seconds	
  

6	
  examples,	
  0	
  failures	
テストが通った	
  

67	
  
考察1	
•  結構Test	
  Doubleを多用するハメになった	
  
•  しかし、一番間違えやすそうな
scan_prefecture()	
  はブラックボックステストで
きている	
  
–  ここはもっとテストケースが追加されるべきだろう	
  

•  他の部分は、そんなに難しいことしてないし、
一応テストは付いている	
68	
  
考察2	
•  「外部入出力のOutput()	
  と	
  Net::HTTP.get」 だ
け	
  Test	
  Doubleにすれば、全体を通したテスト
も可能	
  
–  1つくらい書いておくと安心できる	
  

•  あまり手間をかけずに、効率良くテストできる	
  

69	
  
さいごに	

70	
  
さいごに	
•  最近の単体テストは割とお手軽になっている	
  
•  「無いよりはマシ」なので、どんなに時間がな
くても1つくらい書こうよ	
  
–  環境をSetupしておくのが大事	
  

•  難しいロジックを書く時だけ使ってもOK	
71	
  
単体テストの今後	
•  知らない・書けない、だと恥ずかしい	
  
–  面倒だから書かない、ならまだわかるが	
  

•  職業プログラマの基本スキル	
  
–  ゆめみでもそう位置づけます	
  

•  単なる慣れですので、早目に覚えましょう	
72	
  
参考図書	
•  体系的ソフトウェアテスト入門 	
  
–  hmp://www.amazon.co.jp/dp/4822282074	

73	
  

More Related Content

What's hot

脱RESTful API設計の提案
脱RESTful API設計の提案脱RESTful API設計の提案
脱RESTful API設計の提案樽八 仲川
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法についてYuji Otani
 
このPHP QAツールがすごい!2019
このPHP QAツールがすごい!2019 このPHP QAツールがすごい!2019
このPHP QAツールがすごい!2019 sasezaki
 
Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)
Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)
Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)Hiro H.
 
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニックinfinite_loop
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」Takuto Wada
 
DockerとPodmanの比較
DockerとPodmanの比較DockerとPodmanの比較
DockerとPodmanの比較Akihiro Suda
 
PHPからgoへの移行で分かったこと
PHPからgoへの移行で分かったことPHPからgoへの移行で分かったこと
PHPからgoへの移行で分かったことgree_tech
 
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)NTT DATA Technology & Innovation
 
実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjug実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjugYahoo!デベロッパーネットワーク
 
Linux女子部 systemd徹底入門
Linux女子部 systemd徹底入門Linux女子部 systemd徹底入門
Linux女子部 systemd徹底入門Etsuji Nakai
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計Yoshinori Matsunobu
 
ぱぱっと理解するSpring Cloudの基本
ぱぱっと理解するSpring Cloudの基本ぱぱっと理解するSpring Cloudの基本
ぱぱっと理解するSpring Cloudの基本kazuki kumagai
 
これからSpringを使う開発者が知っておくべきこと
これからSpringを使う開発者が知っておくべきことこれからSpringを使う開発者が知っておくべきこと
これからSpringを使う開発者が知っておくべきこと土岐 孝平
 
やはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているやはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているKoichi Tanaka
 
オープンソースのAPIゲートウェイ Kong ご紹介
オープンソースのAPIゲートウェイ Kong ご紹介 オープンソースのAPIゲートウェイ Kong ご紹介
オープンソースのAPIゲートウェイ Kong ご紹介 briscola-tokyo
 
ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版
ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版
ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版Masahiro Nagano
 
AWSのログ管理ベストプラクティス
AWSのログ管理ベストプラクティスAWSのログ管理ベストプラクティス
AWSのログ管理ベストプラクティスAkihiro Kuwano
 
WebブラウザでC#実行 WebAssemblyの技術
WebブラウザでC#実行 WebAssemblyの技術WebブラウザでC#実行 WebAssemblyの技術
WebブラウザでC#実行 WebAssemblyの技術Sho Okada
 

What's hot (20)

脱RESTful API設計の提案
脱RESTful API設計の提案脱RESTful API設計の提案
脱RESTful API設計の提案
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法について
 
このPHP QAツールがすごい!2019
このPHP QAツールがすごい!2019 このPHP QAツールがすごい!2019
このPHP QAツールがすごい!2019
 
Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)
Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)
Linuxにて複数のコマンドを並列実行(同時実行数の制限付き)
 
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
大規模ソーシャルゲーム開発から学んだPHP&MySQL実践テクニック
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
DockerとPodmanの比較
DockerとPodmanの比較DockerとPodmanの比較
DockerとPodmanの比較
 
PHPからgoへの移行で分かったこと
PHPからgoへの移行で分かったことPHPからgoへの移行で分かったこと
PHPからgoへの移行で分かったこと
 
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
 
実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjug実運用して分かったRabbit MQの良いところ・気をつけること #jjug
実運用して分かったRabbit MQの良いところ・気をつけること #jjug
 
Linux女子部 systemd徹底入門
Linux女子部 systemd徹底入門Linux女子部 systemd徹底入門
Linux女子部 systemd徹底入門
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
ぱぱっと理解するSpring Cloudの基本
ぱぱっと理解するSpring Cloudの基本ぱぱっと理解するSpring Cloudの基本
ぱぱっと理解するSpring Cloudの基本
 
これからSpringを使う開発者が知っておくべきこと
これからSpringを使う開発者が知っておくべきことこれからSpringを使う開発者が知っておくべきこと
これからSpringを使う開発者が知っておくべきこと
 
やはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているやはりお前らのMVCは間違っている
やはりお前らのMVCは間違っている
 
オープンソースのAPIゲートウェイ Kong ご紹介
オープンソースのAPIゲートウェイ Kong ご紹介 オープンソースのAPIゲートウェイ Kong ご紹介
オープンソースのAPIゲートウェイ Kong ご紹介
 
Jenkins 再入門
Jenkins 再入門Jenkins 再入門
Jenkins 再入門
 
ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版
ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版
ISUCONで学ぶ Webアプリケーションのパフォーマンス向上のコツ 実践編 完全版
 
AWSのログ管理ベストプラクティス
AWSのログ管理ベストプラクティスAWSのログ管理ベストプラクティス
AWSのログ管理ベストプラクティス
 
WebブラウザでC#実行 WebAssemblyの技術
WebブラウザでC#実行 WebAssemblyの技術WebブラウザでC#実行 WebAssemblyの技術
WebブラウザでC#実行 WebAssemblyの技術
 

Similar to 最近の単体テスト

Start!! Ruby
Start!! RubyStart!! Ruby
Start!! Rubymitim
 
Cobolでもやりたいテスト自動化
Cobolでもやりたいテスト自動化 Cobolでもやりたいテスト自動化
Cobolでもやりたいテスト自動化 daisukhayash
 
JavaScriptクイックスタート
JavaScriptクイックスタートJavaScriptクイックスタート
JavaScriptクイックスタートShumpei Shiraishi
 
ng-japan 2015 TypeScript+AngularJS 1.3
ng-japan 2015 TypeScript+AngularJS 1.3ng-japan 2015 TypeScript+AngularJS 1.3
ng-japan 2015 TypeScript+AngularJS 1.3Masahiro Wakame
 
タダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnitタダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnitYasuhiko Yamamoto
 
第4回勉強会 単体テストのすすめ
第4回勉強会 単体テストのすすめ第4回勉強会 単体テストのすすめ
第4回勉強会 単体テストのすすめhakoika-itwg
 
はこだてIKA 第4回勉強会 単体テスト
はこだてIKA 第4回勉強会 単体テストはこだてIKA 第4回勉強会 単体テスト
はこだてIKA 第4回勉強会 単体テストSeiji KOMATSU
 
第2回勉強会スライド
第2回勉強会スライド第2回勉強会スライド
第2回勉強会スライドkoturn 0;
 
Programming camp 2010 debug hacks
Programming camp 2010 debug hacksProgramming camp 2010 debug hacks
Programming camp 2010 debug hacksHiro Yoshioka
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexprGenya Murakami
 
リテラル文字列型までの道
リテラル文字列型までの道リテラル文字列型までの道
リテラル文字列型までの道Satoshi Sato
 
Constexpr 中3女子テクニック
Constexpr 中3女子テクニックConstexpr 中3女子テクニック
Constexpr 中3女子テクニックGenya Murakami
 
Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~CHY72
 
Debug Hacks at Security and Programming camp 2011
Debug Hacks at Security and Programming camp 2011 Debug Hacks at Security and Programming camp 2011
Debug Hacks at Security and Programming camp 2011 Hiro Yoshioka
 
Kink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based languageKink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based languageTaku Miyakawa
 
シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)icchy
 
中3女子が狂える本当に気持ちのいい constexpr
中3女子が狂える本当に気持ちのいい constexpr中3女子が狂える本当に気持ちのいい constexpr
中3女子が狂える本当に気持ちのいい constexprGenya Murakami
 
Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3Ransui Iso
 
Web本文抽出 using crf
Web本文抽出 using crfWeb本文抽出 using crf
Web本文抽出 using crfShuyo Nakatani
 

Similar to 最近の単体テスト (20)

Start!! Ruby
Start!! RubyStart!! Ruby
Start!! Ruby
 
Cobolでもやりたいテスト自動化
Cobolでもやりたいテスト自動化 Cobolでもやりたいテスト自動化
Cobolでもやりたいテスト自動化
 
JavaScriptクイックスタート
JavaScriptクイックスタートJavaScriptクイックスタート
JavaScriptクイックスタート
 
ng-japan 2015 TypeScript+AngularJS 1.3
ng-japan 2015 TypeScript+AngularJS 1.3ng-japan 2015 TypeScript+AngularJS 1.3
ng-japan 2015 TypeScript+AngularJS 1.3
 
タダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnitタダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnit
 
第4回勉強会 単体テストのすすめ
第4回勉強会 単体テストのすすめ第4回勉強会 単体テストのすすめ
第4回勉強会 単体テストのすすめ
 
はこだてIKA 第4回勉強会 単体テスト
はこだてIKA 第4回勉強会 単体テストはこだてIKA 第4回勉強会 単体テスト
はこだてIKA 第4回勉強会 単体テスト
 
C言語講習会2
C言語講習会2C言語講習会2
C言語講習会2
 
第2回勉強会スライド
第2回勉強会スライド第2回勉強会スライド
第2回勉強会スライド
 
Programming camp 2010 debug hacks
Programming camp 2010 debug hacksProgramming camp 2010 debug hacks
Programming camp 2010 debug hacks
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexpr
 
リテラル文字列型までの道
リテラル文字列型までの道リテラル文字列型までの道
リテラル文字列型までの道
 
Constexpr 中3女子テクニック
Constexpr 中3女子テクニックConstexpr 中3女子テクニック
Constexpr 中3女子テクニック
 
Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~Unity2015_No10_~UGUI&Audio~
Unity2015_No10_~UGUI&Audio~
 
Debug Hacks at Security and Programming camp 2011
Debug Hacks at Security and Programming camp 2011 Debug Hacks at Security and Programming camp 2011
Debug Hacks at Security and Programming camp 2011
 
Kink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based languageKink: invokedynamic on a prototype-based language
Kink: invokedynamic on a prototype-based language
 
シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)シェル芸初心者によるシェル芸入門 (修正版)
シェル芸初心者によるシェル芸入門 (修正版)
 
中3女子が狂える本当に気持ちのいい constexpr
中3女子が狂える本当に気持ちのいい constexpr中3女子が狂える本当に気持ちのいい constexpr
中3女子が狂える本当に気持ちのいい constexpr
 
Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3
 
Web本文抽出 using crf
Web本文抽出 using crfWeb本文抽出 using crf
Web本文抽出 using crf
 

More from Ken Morishita

BigQuery勉強会 Standard SQL Dialect
BigQuery勉強会 Standard SQL DialectBigQuery勉強会 Standard SQL Dialect
BigQuery勉強会 Standard SQL DialectKen Morishita
 
iOSやAndroidアプリ開発のGoodPractice
iOSやAndroidアプリ開発のGoodPracticeiOSやAndroidアプリ開発のGoodPractice
iOSやAndroidアプリ開発のGoodPracticeKen Morishita
 
知らないと損するアプリ開発におけるStateMachineの活用法(full版)
知らないと損するアプリ開発におけるStateMachineの活用法(full版)知らないと損するアプリ開発におけるStateMachineの活用法(full版)
知らないと損するアプリ開発におけるStateMachineの活用法(full版)Ken Morishita
 
知らないと損するアプリ開発におけるStateMachineの活用法(15分版)
知らないと損するアプリ開発におけるStateMachineの活用法(15分版)知らないと損するアプリ開発におけるStateMachineの活用法(15分版)
知らないと損するアプリ開発におけるStateMachineの活用法(15分版)Ken Morishita
 
SwiftでのiOSアプリ開発
SwiftでのiOSアプリ開発SwiftでのiOSアプリ開発
SwiftでのiOSアプリ開発Ken Morishita
 
iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞い
iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞いiOS/Androidアプリエンジニアが理解すべき「Model」の振る舞い
iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞いKen Morishita
 
IOS/Androidアプリの3つの大事な設計方針
IOS/Androidアプリの3つの大事な設計方針IOS/Androidアプリの3つの大事な設計方針
IOS/Androidアプリの3つの大事な設計方針Ken Morishita
 
Logをs3とredshiftに格納する仕組み
Logをs3とredshiftに格納する仕組みLogをs3とredshiftに格納する仕組み
Logをs3とredshiftに格納する仕組みKen Morishita
 
Pythonとdeep learningで手書き文字認識
Pythonとdeep learningで手書き文字認識Pythonとdeep learningで手書き文字認識
Pythonとdeep learningで手書き文字認識Ken Morishita
 

More from Ken Morishita (9)

BigQuery勉強会 Standard SQL Dialect
BigQuery勉強会 Standard SQL DialectBigQuery勉強会 Standard SQL Dialect
BigQuery勉強会 Standard SQL Dialect
 
iOSやAndroidアプリ開発のGoodPractice
iOSやAndroidアプリ開発のGoodPracticeiOSやAndroidアプリ開発のGoodPractice
iOSやAndroidアプリ開発のGoodPractice
 
知らないと損するアプリ開発におけるStateMachineの活用法(full版)
知らないと損するアプリ開発におけるStateMachineの活用法(full版)知らないと損するアプリ開発におけるStateMachineの活用法(full版)
知らないと損するアプリ開発におけるStateMachineの活用法(full版)
 
知らないと損するアプリ開発におけるStateMachineの活用法(15分版)
知らないと損するアプリ開発におけるStateMachineの活用法(15分版)知らないと損するアプリ開発におけるStateMachineの活用法(15分版)
知らないと損するアプリ開発におけるStateMachineの活用法(15分版)
 
SwiftでのiOSアプリ開発
SwiftでのiOSアプリ開発SwiftでのiOSアプリ開発
SwiftでのiOSアプリ開発
 
iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞い
iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞いiOS/Androidアプリエンジニアが理解すべき「Model」の振る舞い
iOS/Androidアプリエンジニアが理解すべき「Model」の振る舞い
 
IOS/Androidアプリの3つの大事な設計方針
IOS/Androidアプリの3つの大事な設計方針IOS/Androidアプリの3つの大事な設計方針
IOS/Androidアプリの3つの大事な設計方針
 
Logをs3とredshiftに格納する仕組み
Logをs3とredshiftに格納する仕組みLogをs3とredshiftに格納する仕組み
Logをs3とredshiftに格納する仕組み
 
Pythonとdeep learningで手書き文字認識
Pythonとdeep learningで手書き文字認識Pythonとdeep learningで手書き文字認識
Pythonとdeep learningで手書き文字認識
 

Recently uploaded

Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)
Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)
Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)keikoitakurag
 
論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayers
論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayers論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayers
論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayersToru Tamaki
 
2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptx
2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptx2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptx
2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptxssuserbefd24
 
Intranet Development v1.0 (TSG LIVE! 12 LT )
Intranet Development v1.0 (TSG LIVE! 12 LT )Intranet Development v1.0 (TSG LIVE! 12 LT )
Intranet Development v1.0 (TSG LIVE! 12 LT )iwashiira2ctf
 
20240523_IoTLT_vol111_kitazaki_v1___.pdf
20240523_IoTLT_vol111_kitazaki_v1___.pdf20240523_IoTLT_vol111_kitazaki_v1___.pdf
20240523_IoTLT_vol111_kitazaki_v1___.pdfAyachika Kitazaki
 
ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521
ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521
ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521Satoshi Makita
 
クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑
クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑
クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑Akihiro Kadohata
 
部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員
部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員
部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員Sadaomi Nishi
 
論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation
論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation
論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose EstimationToru Tamaki
 
5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一
5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一
5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一瑛一 西口
 
論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizes
論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizes論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizes
論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizesatsushi061452
 
論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...
論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...
論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...atsushi061452
 

Recently uploaded (12)

Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)
Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)
Amazon Cognitoで実装するパスキー (Security-JAWS【第33回】 勉強会)
 
論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayers
論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayers論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayers
論文紹介:Deep Occlusion-Aware Instance Segmentation With Overlapping BiLayers
 
2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptx
2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptx2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptx
2024年5月25日Serverless Meetup大阪 アプリケーションをどこで動かすべきなのか.pptx
 
Intranet Development v1.0 (TSG LIVE! 12 LT )
Intranet Development v1.0 (TSG LIVE! 12 LT )Intranet Development v1.0 (TSG LIVE! 12 LT )
Intranet Development v1.0 (TSG LIVE! 12 LT )
 
20240523_IoTLT_vol111_kitazaki_v1___.pdf
20240523_IoTLT_vol111_kitazaki_v1___.pdf20240523_IoTLT_vol111_kitazaki_v1___.pdf
20240523_IoTLT_vol111_kitazaki_v1___.pdf
 
ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521
ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521
ロボットマニピュレーションの作業・動作計画 / rosjp_planning_for_robotic_manipulation_20240521
 
クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑
クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑
クラウド時代におけるSREとUPWARDの取組ーUPWARD株式会社 CTO門畑
 
部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員
部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員
部内勉強会(IT用語ざっくり学習) 実施日:2024年5月17日(金) 対象者:営業部社員
 
論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation
論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation
論文紹介:ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation
 
5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一
5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一
5/22 第23回 Customer系エンジニア座談会のスライド 公開用 西口瑛一
 
論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizes
論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizes論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizes
論文紹介: Offline Q-Learning on diverse Multi-Task data both scales and generalizes
 
論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...
論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...
論文紹介: Exploiting semantic segmentation to boost reinforcement learning in vid...
 

最近の単体テスト

  • 2. はじめに •  社内勉強会の資料です   •  初心者〜中級者くらい向けです   •  私なぞが単体テストについて書いていいのか という気もしますが、、もう書いてしまいました 2  
  • 4. 単体(Unit)テスト(Test) •  自動で何度も実行・検証できる   •  モジュール・クラス・メソッド単位でテストする   •  実装コードとテストコードが対応している def add(a, b) ! return a + b ! end ! def test_add ! ... end ! ⇐  「実装コード」に対応した   ⇐  「テストコード」が(複数)ある 4  
  • 5. 「単体テスト」じゃないテスト? •  Integra:onテスト   –  統合テストとか結合テストとも言う   –  複数のモジュールやシステムをまたがるテスト 実際は、「単体テスト」とちょっぴり「Integra:onテスト」を含むテストコードをよく書きます 5  
  • 7. 大事な原則 •  テスト対象の実装コードの全てをカバーする   –  メソッド・モジュールの数だけテストを書く   •  テストコードのテストは書かない!   •  変更の掟   –  仕様を変更するなら先にテストを変更する   –  リファクタリングするならテストは変更しない 7  
  • 8. 全てにテストを書く? •  かかない部分があっても良い   –  例えばView周りとかは、かなり工夫しないと意味 があるテストを記述できない   •  なるべく書ける範囲を増やす気持ちが大事   8  
  • 9. TDDという概念 •  テスト駆動開発(Test-­‐Driven  Development)   •  テストを最初に書いてPassさせるという考え   –  →  テスト実行  →  テスト失敗   –  →  実装修正         –  →  テスト実行  →  テスト成功   •  テストを最初に書く: 「テストファースト」と呼ぶ   •  なかなかストイックな開発手法である 9  
  • 10. TDD、だがしかし •  ぶっちゃけ、いつもいつもTDDだと疲れる   •  もちろんTDDが綺麗にハマる場合もある   •  もしTDDで慣れている人は継続した方が良い   •  私は「テストセカンド」くらいで良いと思う   –  実装書いたらテスト書く   –  だって、「無いよりマシ」なんだから   10  
  • 12. 最大公約数を求める関数 •  仕様   –  名前は gcm(a,  b)     –  変な値が来たら例外を投げる   –  それ以外は答えを返す 12  
  • 13. まずテストを書く 基本的なケース1: 「12」と「28」なら「4」である   describe 'gcm' do ! it 'should return 4 that gcm(12, 28)' do ! expect(gcm(12, 28)).to eq 4 ! end ! end ※意味の通る英文になるのが好ましい(要英文センス)(私には無い)   13  
  • 15. そして実装を書く def gcm(a, b) ! 4! end ! おい! ちゃんと計算しろ!   でもテストは通るからOK   describe 'gcm' do ! it 'should return 4 that gcm(12, 28)' do ! expect(gcm(12, 28)).to eq 4 ! end ! end 15  
  • 16. さらにテストを書く describe 'gcm' do ! it 'should return 4 that gcm(12, 28)' do ! expect(gcm(12, 28)).to eq 4 ! end ! it 'should return 7 that gcm(21, 28)' do ! expect(gcm(21, 28)).to eq 7 ! end ! end! 基本的なケース2: 21と28なら7   16  
  • 17. 実装を修正する def gcm(a, b) ! a == 12 ? 4 : 7 end ! おい! ちゃんと計算しろって!!!   でもテストは通るからOK   describe 'gcm' do ! it 'should return 4 that gcm(12, 28)' do ! expect(gcm(12, 28)).to eq 4 ! end ! it 'should return 7 that gcm(21, 28)' do ! expect(gcm(21, 28)).to eq 7 ! end ! end! 17  
  • 18. よろしい、ならば戦争だ describe 'gcm' do ! it 'should return 4 that gcm(12, 28)' do ! expect(gcm(12, 28)).to eq 4 ! end ! it 'should return 7 that gcm(21, 28)' do ! expect(gcm(21, 28)).to eq 7 ! end ! it 'should return 7 that gcm(28, 21)' do ! 大小入れ替え   expect(gcm(28, 21)).to eq 7 ! end ! it 'should return 28 that gcm(28, 28)' do ! 同じ数   expect(gcm(28, 28)).to eq 28 ! end ! it 'should raise error that gcm(-1, 6)' do ! 変な値→例外来い   expect{gcm(-1, 6)}.to raise_error ! end ! end! 18  
  • 19. そろそろ真面目に実装する def gcm(a, b) ! raise ArgumentError.new if a < 1 || b < 1 ! if a < b ! gcm(b, a) else ! if a % b == 0 ! b else ! gcm(b, a % b) end ! end ! end ! ※テストに通りました   19  
  • 20. 考察 •  批判   –  別にまだふざけた実装も可能じゃないか?   –  文字列とか小数のケースをテストしてないよ   •  でも、それは気にしなくても良い   –  テストは関数の完全性を担保するわけではない   –  「ポイントを抑える」「無いよりはマシ」である   –  必要だと思えば追加しておきましょう   •  小数の場合くらいはあるべきかな・・ 20  
  • 21. テストのメリット •  目標ができる   –  テストをパスすると何か達成した感じがある   •  安心できる   –  関数gcm()が正しいか自分なりに自信が持てる   –  もっと安心したいから、調子に乗って実際もっとテスト ケースを追加することも多い  →  品質も上がる   •  仕様がはっきりする   –  テストコードを見て、実装コードの挙動がわかる 21  
  • 23. 元祖は JUnit  (だと思う) •  JUnit  (Java用のUnitTestライブラリ)   –  最初に発表されたのが1997年頃らしい   –  ちなみにJava1.0が1995年   •  その後流行した(=良いやり方だった)   –  RUnit  (ruby),  PHPUnit,  PyUnit(Python)  など続く   –  xUnitが無い言語は少ないと思う   •  これらをまとめて 「xUnit」 系のテストフレーム ワークと呼ぶ 23  
  • 24. xUnit  の共通点・1 # test_gcm.rb ! ①  テストを書くファイル名:    test_<元ファイル>  とする   ②  メソッド名: test_<対象メソッド>***    とする   def test_gcm_1 ! assert_equals(4, gcm(12, 28)) ! end ! ③  assert*****  で値のcheckを行う   ④  assert*****  では(期待される値、実際の値)の順に書く   ⑤  assertで検証するのは、1テストメソッドに付き1つにするのがお作法   ※命名規則は、言語によってCamel  Caseになります 24  
  • 25. xUnit  の共通点・2 def setup ! ①  setup  に各テスト実行前の共通処理を書ける   File.write('/tmp/hogehoge', "aaan" * 29) ! end ! def test_parse_file ! assert_equals(30, count_line('/tmp/hogehoge')) ! end ! def teardown ! ②  teardownに各テスト実行後の共通処理を書ける   File.delete('/tmp/hogehoge') ! end ! 25  
  • 26. xUnitのメリット •  ルールが簡単で覚えやすい   •  意外とこれくらいできれば十分である 26  
  • 27. RSpec革命 •  RSpec  (Rubyのテストフレームワーク)   •  より自然に仕様や振る舞いを記述   •  ぶっちゃけ、xUnit  とできることは同じ   •  ただ、その華麗さにより xUnit  を過去のもの にし始めている 27  
  • 28. RSpecの雰囲気 describe Array, "when empty" do ! 何についてテストするのかを書ける   before do ! @empty_array = [] ! end !  before  に各テスト実行前の共通処理   it "should be empty" do ! @empty_array.should be_empty ! テスト内容をより明確に書ける   end ! テスト条件の記述が読み易い   it "should size 0" do ! @empty_array.size.should == 0 ! end ! after do ! @empty_array = nil !  aberに各テスト実行後の共通処理   end ! end! 28  
  • 29. RSpecは内容を仕様書的に出力できる describe Array, "when empty" do ! before do ! @empty_array = [] ! end ! it "should be empty" do ! @empty_array.should be_empty ! end ! it "should size 0" do ! @empty_array.size.should == 0 ! こんな感じ %  spec  -­‐fs  array_spec.rb     Array  when  empty   -­‐  should  be  empty   -­‐  should  size  0 end ! after do ! @empty_array = nil ! end ! end! 29  
  • 30. specブーム •  RSpecの派生が流行している   –  PHPSpec,  Kiwi(Objec:ve-­‐C),  specs2(Scala)   –  Jasmine(JavaScript),  Mspec?(C#)   •  ただ、文法的な制約が言語によってあるので RSpecほど美しいのは少ない   –  Specs2  と Jasmine+CoffeeScript  くらいか?   30  
  • 31. xUnit  と  spec  どっちが良いの? •  ぱっとみ綺麗に書ける方を選ぶと良い   •  でも、Ruby,  JS,  Scala  なら  spec系一択かな   •  ゆめみでは  PHPは PHPSpec  を標準にしようかと 思っています   –  PHPには色々spec系があるのですが、   PHP的に奇天烈じゃないのでPHPSpecが良いのかな 31  
  • 33. 単体テストは何をテストするのか •  ブラックボックステスト か ホワイトボックステストかで 異なる     ちなみに   •  ブラックボックステストの意味   –  中の実装を見ないでテストする   –  先ほどの最大公約数のテストはこちら   •  ホワイトボックステストの意味     –  中の実装を見ても良いからテストする 33  
  • 34. ブラックボックステスト •  何をテストしているか   –  実装コードの「仕様」をテストしている   •  正常系・異常系・境界系などの入力値で正し い振る舞いをするか検証する   •  これは理想的なテスト 34  
  • 35. ホワイトボックステスト •  何をテストしているか   –  実装コードの「ロジック」をテストしている   –  実装が想定通り「外部への入出力」をするかをテストしている   •  全ての分岐を一度は通るテストを書く   –  コードカバレッジ(テストが通過した実装コード率)を高めるのが 一つの指標   –  全ての「分岐の組み合わせ」はテストしなくても良い   –  ただ、分岐が間違っていれば検知すること   •  色々仮定を置いた上で「入出力」をテストする   –  MockやStub(後述)   35  
  • 36. どっちが良いの? •  ブラックボックステストが書けるのが望ましい   –  これが書ければリファクタリングが容易   •  ブラックボックステストが書けないケースがあ る(後述)   –  ホワイトボックステストで頑張る   •  無いよりマシ   –  なるべくブラックボックステストになるよう設計する   36  
  • 37. 単体テストは何をテストしないのか •  「他の関数・API・外部機能・フレームワーク等」   –  の、呼び出し方が正しいか   –  が、想定通り動くか   などはテストしない。あくまで「単体」がメイン。     目の前の「実装コード」がとにかく重要である     •  テストしても良いけど、   「どんな環境でも軽快に確実に実行できること」   の方が重要なことは忘れずに     37  
  • 38. 「テストコード」自体の品質 •  どの程度間違いを検出できるか   •  実装コードを誤修正して検出するかでわかる   –  適当にコメントアウトしてみる   –  異常な定数に変更してみる   –  if  の 条件に not  を入れてみる   –  and  を or  にしてみる   –  Etc.. 38  
  • 40. もう一度、テストファースト •  テストファーストの元々の意味   –  「最初にテストコードを書く」   •  最近思うテストファースト   –  「テストを行うことを最優先に考える」   •  そして   –  テストを支援するフレームワークを使う!   –  必要なテストを支援しないフレームワークはダメです   •  可能な限り使わない   40  
  • 41. 最近の風潮 •  ホワイトボックステストで構わないからどんど ん書こう!   •  MockとStubを活用して、今まで書きにくかった テストもどんどん書こう!   41  
  • 42. MockとStub •  テストダブル(Test  Double)という概念がある   –  hmp://ja.wikipedia.org/wiki/ %E3%83%86%E3%82%B9%E3%83%88%E3%83%8 0%E3%83%96%E3%83%AB   •  「ダブル」は代役・影武者を意味する   •  Mock、Stubはその中で定義される用語   42  
  • 43. MockとStubの違い •  この会では違いは重要ではないが一応   •  Stub   –  あるObjectのように振る舞うDouble   •  Mock   –  Stubに「その結果の検証」機能が付いたもの   •  他にもSpyとかFakeとかいう用語もある 43  
  • 44. MockやStubが何故重要か •  生成するのが大変なObjectの代わりができる   –  Frameworkが作成するようなObject   –  ごにょごにょ深淵から湧いてくるObject   •  外部APIなどにアクセスした「つもり」にできる   –  アクセスした「つもり」で「結果」を与えられる   •  どこでも実行できるテストになる   •  いつでも同じ結果になるテストになる   •  テスト可能コードが格段に簡単に増やせる 44  
  • 45. 例えば 「日曜日?」という関数が、こんな実装だったとする   def is_sunday? ! Time.now.wday == 0 ! end ! 一見こいつは手に負えない。   何故なら Time.now  を直接呼んでいるので、   テストを実行したときの日時に左右されてしまうからである。     まあ、この実装は直せるが、とりあえずこれを例に考える。   45  
  • 46. 考え方 •  テストしたいのはこのロジックの部分だ   •  Time.now  の結果によって、ちゃんと変化するかをテ ストすれば良いのだ   •  私はTime.now  を内部で使っていることを知っている   •  Time.now  の結果を操れればテストできる! 46  
  • 47. Stubで振る舞いを変える Time  に  now  が来たら特定の値を返すように仕込む   describe 'is_sunday?' do ! it 'should be true if today is Sunday' do ! # 2014/2/2 は 日曜日 Time.stub(:now).and_return(Time.new(2014, 2, 2)) ! expect(is_sunday?).to eq true ! end ! it 'should be false if today is Saturday' do ! # 2014/2/1 は 土曜日 Time.stub(:now).and_return(Time.new(2014, 2, 1)) ! expect(is_sunday?).to eq false ! end ! end! 47  
  • 48. 考察 •  こんな風に動的に既存のObjectの振る舞いを変えら れるかどうかは言語によって違う   –  言語によってStubのHackがどこまでできるかは調べよう   •  実装コードを修正しないとテストできない場合もある   –  こういうのも言語によって色々テクニックがある   •  テストのため実装を修正するのか!?   –  答えはYES! YESだよ!!   –  テストファーストだよ! 48  
  • 50. 郵便番号を元に都道府県をprintする機能 •  仕様   –  入力: 郵便番号(文字列)   –  出力:   •  都道府県文字列 →  標準出力   •  ZIPCODEが実在しない場合は何も出力しない   こんな感じ class SomeClass! def print_prefecture(zipcode) ! end ! end! 50  
  • 51. 郵便番号を元に都道府県をprintする機能 •  都道府県を調べる方法   –  hmp://zip.cgis.biz/    の  APIを使う   –  例:  hmp://zip.cgis.biz/xml/zip.php?zn=1030000 51  
  • 52. APIの戻り値: 実在する場合 <ZIP_result> ! <result name="ZipSearchXML"/> ! <result version="1.01"/> ! <result request_url="http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn%3D1030000"/> ! <result request_zip_num="1030000"/> ! <result request_zip_version="none"/> ! <result result_code="1"/> ! <result result_zip_num="1030000"/> ! <result result_zip_version="0"/> ! <result result_values_count="1"/> ! <ADDRESS_value> ! <value state_kana="トウキョウト"/> ! <value city_kana="チュウオウク"/> ! <value address_kana="イカニケイサイガナイバアイ"/> ! <value company_kana="none"/> ! sample1,  とします <value state="東京都"/> ! <value city="中央区"/> ! <value address="none"/> ! <value company="none"/> ! </ADDRESS_value> ! </ZIP_result>! 52  
  • 53. APIの戻り値: 実在しない場合 <ZIP_result> ! <result name="ZipSearchXML"/> ! <result version="1.01"/> ! <result request_url="http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn %3D1030900"/> ! <result request_zip_num="1030900"/> ! <result request_zip_version="none"/> ! <result result_code="1"/> ! <result result_zip_num="1030900"/> ! <result result_zip_version="0"/> ! <result result_values_count="0"/> ! </ZIP_result>! sample2,  とします 53  
  • 54. この機能は少しテストしにくい •  外部APIアクセスがある   •  出力を標準出力に行うことになっている   まあしかしとりあえずやってみましょう 54  
  • 55. とりあえずテスト書いて実行 describe SomeClass do ! ※中身がない describe 'print_prefecture' do ! it 'should print 東京都 when zipcode=1030000' ! it 'should not print anything when zipcode=1030900' ! end ! end ! SomeClass      print_prefecture          should  print  東京都 when  zipcode=1030000  (PENDING:  Not  yet  implemented)          should  not  print  anything  when  zipcode=1030900  (PENDING:  Not  yet  implemented)     Finished  in  0.00034  seconds   2  examples,  0  failures,  2  pending 「Pending」になる。これもSpec系の特徴。   「失敗」とは少し違う。「まだ書いてないよ」という状態。 55  
  • 56. よし、じゃあテストの中身を書こう   ・・・いや待てよ。。。 •  テストが書けない   –  返り値をテストするわけではないし   –  APIアクセスあるからネットワーク切れてたらテストできな いし   •  この機能全体をブラックボックステストできない   ホワイトボックステストしかない •  先に実装を進めよう   –  テストしやすくなるように気をつけながら   56  
  • 57. とりあえずこんな感じで class SomeClass ! def print_prefecture(zipcode) ! prefecture = get_prefecture(zipcode) ! ここで都道府県が戻る想定   ! if prefecture ! puts prefecture ! end ! end ! end ! Nil  じゃなければ出力   57  
  • 58. Stubでホワイトボックステスト describe SomeClass do ! describe 'print_prefecture' do ! before do ! @obj = SomeClass.new ! @obj  に毎回Objectを入れる共通処理   end ! it 'should print 東京都 when zipcode=1030000' do ! @obj.stub(:get_prefecture => '東京都') get_prefecture  が 東京都 end ! end ! end! を返すならば   あれ?  Puts  したかってどうテストする?   出力したか、という判定をするのが難しい。   →  少し工夫が必要   ※Kernel.puts  を調べれば良いんですけどね。今回はこういう想定で。   58  
  • 59. 実装コードをテストしやすく修正 class SomeClass ! def print_prefecture(zipcode) ! prefecture = get_prefecture(zipcode) ! if prefecture ! output(prefecture) end ! end ! ! 出力する、というのを呼び出すようにする   def output(str) ! puts str ! end ! end ! 59  
  • 60. Output()をCallすればOKと考える it 'should print 東京都 when zipcode=1030000' do ! get_prefecture  が 東京都 を返すなら   @obj.stub(:get_prefecture => '東京都') @objは  output(‘東京都’)  が呼ばれることが期待される   expect(@obj).to receive(:output).with('東京都') ! じゃあ、実行しましょう   @obj.print_prefecture('1030000') ! end ! となる   60  
  • 61. Outputされないケースも追加 describe SomeClass do ! describe 'print_prefecture' do ! before do ! @obj = SomeClass.new ! end ! it 'should print 東京都 when zipcode is in 東京都 area' do ! @obj.stub(:get_prefecture => '東京都') expect(@obj).to receive(:output).with('東京都') ! @obj.print_prefecture('1030000') ! end ! it 'should not print anything when zipcode doesnot exist' do ! Exampleの文言も修正する   nil) ! expect(@obj).not_to receive(:output) @obj.stub(:get_prefecture => @obj.print_prefecture('1030900') ! end ! end ! end ! ! get_prefectureがnilの時も追加する   仮に:get_prefecture  が  nil   期待:  output()は呼ばれない   61  
  • 62. テスト実行! SomeClass      print_prefecture          should  print  東京都 when  zipcode  is  in  東京都 area          should  not  print  anything  when  zipcode  doesnot  exist     Finished  in  0.00124  seconds   2  examples,  0  failures テストが通った   62  
  • 63. 続けて細かい実装を先に書いてしまう 勢いってものがあるじゃないですか   require 'net/http' ! require 'rexml/document' ! class SomeClass ! def print_prefecture(zipcode) ! prefecture = get_prefecture(zipcode) ! if prefecture ! output(prefecture) ! end ! end ! def get_prefecture(zipcode) ! xml_data = Net::HTTP.get(URI.parse("http://zip.cgis.biz/xml/zip.php?zn=#{zipcode}")) ! scan_prefecture(xml_data) ! end ! def scan_prefecture(xml_data) ! attr_state = REXML::Document.new(xml_data).elements['//@state'] ! attr_state ? attr_state.value : nil ! end ! def output(str) ! puts str ! end ! end ! 63   get_prefecture()       と     scan_prefecture()     に分けた  
  • 64. テスト: get_prefecture() 実装コード   def get_prefecture(zipcode) ! xml_data = Net::HTTP.get(URI.parse("http://zip.cgis.biz/xml/zip.php?zn=#{zipcode}")) ! scan_prefecture(xml_data) ! end ! テストコード   describe 'get_prefecture' do ! it 'should call API Request with zipcode' do ! expect(Net::HTTP).to receive(:get) ! expect(URI).to receive(:parse).with(/zn=1030000/) ! @obj.get_prefecture('1030000') ! end ! ! it 'should return scan_prefecture result' do ! Net::HTTP.stub(:get => 'SOME DATA') ! 期待:HTTP.get  が呼ばれる   期待:URI.parse  が呼ばれて、                        引数は  /zn=103000/  の                        正規表現とMatchするはず。   expect(@obj).to receive(:scan_prefecture).with('SOME DATA').and_return('RESULT') ! expect(@obj.get_prefecture('1030000')).to eq('RESULT') ! end ! end ! 仮に: HTTP.get  が  ‘SOME  DATA’を返すとして、   期待:  @obj.scan_prefecture(‘SOME  DATA’)が呼ばれる                          その結果が  ‘RESULT’  だったとして、   期待:  @obj.get_prefecture()  の返り値は  ’RESULT’  になる。   64  
  • 65. テスト: scan_prefecture() 実装コード   def scan_prefecture(xml_data) ! attr_state = REXML::Document.new(xml_data).elements['//@state'] ! attr_state ? attr_state.value : nil ! end ! テストコード   describe 'scan_prefecture' do ! it 'should return 東京都 if the xml data is so' do ! expect(@obj.scan_prefecture(D1)).to eq '東京都' end ! ! 期待: scan_pefecture(sample1)な ら戻り値は 東京都 である   ! it 'should return nil if the xml data doesnot contain pref info' do ! expect(@obj.scan_prefecture(D2)).to eq nil ! end ! end ! 期待: scan_pefecture(sample2)な ら戻り値は  nil  である   65   ※ブラックボックスになっている  
  • 66. describe SomeClass do ! before do ! @obj = SomeClass.new ! end ! describe 'print_prefecture' do ! it 'should print 東京都 when zipcode is in 東京都 area' do ! @obj.stub(:get_prefecture => '東京都') expect(@obj).to receive(:output).with('東京都') ! @obj.print_prefecture('1030000') ! end ! it 'should not print anything when zipcode doesnot exist' do ! @obj.stub(:get_prefecture => nil) ! expect(@obj).not_to receive(:output) ! @obj.print_prefecture('1030900') ! end ! end ! describe 'get_prefecture' do ! it 'should call API Request with zipcode' do ! expect(Net::HTTP).to receive(:get) ! expect(URI).to receive(:parse).with(/zn=1030000/) ! @obj.get_prefecture('1030000') ! end ! it 'should return scan_prefecture result' do ! Net::HTTP.stub(:get => 'SOME DATA') ! expect(@obj).to receive(:scan_prefecture).with('SOME DATA').and_return('RESULT') ! expect(@obj.get_prefecture('1030000')).to eq('RESULT') ! end ! end ! describe 'scan_prefecture' do ! it 'should return 東京都 if the xml data is so' do ! expect(@obj.scan_prefecture(D1)).to eq '東京都' end ! it 'should return nil if the xml data doesnot contain pref info' do ! expect(@obj.scan_prefecture(D2)).to eq nil ! end ! end ! end ! テスト全体   66  
  • 67. テスト実行 SomeClass      print_prefecture          should  print  東京都 when  zipcode  is  in  東京都 area          should  not  print  anything  when  zipcode  doesnot  exist      get_prefecture          should  call  API  Request  with  zipcode          should  return  scan_prefecture  result      scan_prefecture          should  return  東京都 if  the  xml  data  is  so          should  return  nil  if  the  xml  data  doesnot  contain  pref  info     Finished  in  0.00804  seconds   6  examples,  0  failures テストが通った   67  
  • 68. 考察1 •  結構Test  Doubleを多用するハメになった   •  しかし、一番間違えやすそうな scan_prefecture()  はブラックボックステストで きている   –  ここはもっとテストケースが追加されるべきだろう   •  他の部分は、そんなに難しいことしてないし、 一応テストは付いている 68  
  • 69. 考察2 •  「外部入出力のOutput()  と  Net::HTTP.get」 だ け  Test  Doubleにすれば、全体を通したテスト も可能   –  1つくらい書いておくと安心できる   •  あまり手間をかけずに、効率良くテストできる   69  
  • 71. さいごに •  最近の単体テストは割とお手軽になっている   •  「無いよりはマシ」なので、どんなに時間がな くても1つくらい書こうよ   –  環境をSetupしておくのが大事   •  難しいロジックを書く時だけ使ってもOK 71  
  • 72. 単体テストの今後 •  知らない・書けない、だと恥ずかしい   –  面倒だから書かない、ならまだわかるが   •  職業プログラマの基本スキル   –  ゆめみでもそう位置づけます   •  単なる慣れですので、早目に覚えましょう 72  
  • 73. 参考図書 •  体系的ソフトウェアテスト入門   –  hmp://www.amazon.co.jp/dp/4822282074 73