Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
最近の単体テスト	
2014年2月	
  
株式会社ゆめみ	
  	
  
森下	
  
@mokemokechicken	

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

2	
  
単体テストの基本	

3	
  
単体(Unit)テスト(Test)	
•  自動で何度も実行・検証できる	
  
•  モジュール・クラス・メソッド単位でテストする	
  
•  実装コードとテストコードが対応している	
def add(a, b) !
return a + ...
「単体テスト」じゃないテスト?	
•  Integra:onテスト	
  
–  統合テストとか結合テストとも言う	
  
–  複数のモジュールやシステムをまたがるテスト	

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

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

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

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

8	
  
TDDという概念	
•  テスト駆動開発(Test-­‐Driven	
  Development)	
  
•  テストを最初に書いてPassさせるという考え	
  
–  →	
  テスト実行	
  →	
  テスト失敗	
  
–  →	...
TDD、だがしかし	
•  ぶっちゃけ、いつもいつもTDDだと疲れる	
  
•  もちろんTDDが綺麗にハマる場合もある	
  
•  もしTDDで慣れている人は継続した方が良い	
  
•  私は「テストセカンド」くらいで良いと思う	
  ...
最大公約数を求める関数	

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, 2...
テストを実行する	

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

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

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

でもテストは通るからOK	
  

describe 'gcm' do !
it 'should return 4 that gcm(...
さらにテストを書く	
describe 'gcm' do !
it 'should return 4 that gcm(12, 28)' do !
expect(gcm(12, 28)).to eq 4 !
end !
	
it 'should...
実装を修正する	
def gcm(a, b) !
a == 12 ? 4 : 7 	
end !

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

でもテストは通るからOK	
  

describe 'gcm' do !
it 'should ...
よろしい、ならば戦争だ	
describe 'gcm' do !
it 'should return 4 that gcm(12, 28)' do !
expect(gcm(12, 28)).to eq 4 !
end !
	
it 'shou...
そろそろ真面目に実装する	
def gcm(a, b) !
raise ArgumentError.new if a < 1 || b < 1 !
if a < b !
gcm(b, a) 	
else !
if a % b == 0 !
b ...
考察	
•  批判	
  
–  別にまだふざけた実装も可能じゃないか?	
  
–  文字列とか小数のケースをテストしてないよ	
  

•  でも、それは気にしなくても良い	
  
–  テストは関数の完全性を担保するわけではない	
  
...
テストのメリット	
•  目標ができる	
  
–  テストをパスすると何か達成した感じがある	
  

•  安心できる	
  
–  関数gcm()が正しいか自分なりに自信が持てる	
  
–  もっと安心したいから、調子に乗って実際もっと...
単体テストの歴史?	

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

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

•  その後流行し...
xUnit	
  の共通点・1	
# test_gcm.rb !

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

②	
  メソッド名: test_<対象メソッド>***	
  	
  とす...
xUnit	
  の共通点・2	
def setup !

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

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

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

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

before do !
@empty_array = [] !
end !

	
  before	
  に...
RSpecは内容を仕様書的に出力できる	
describe Array, "when empty" do !
before do !
@empty_array = [] !
end !
	
it "should be empty" do !
@...
specブーム	
•  RSpecの派生が流行している	
  
–  PHPSpec,	
  Kiwi(Objec:ve-­‐C),	
  specs2(Scala)	
  
–  Jasmine(JavaScript),	
  Mspec?(...
xUnit	
  と	
  spec	
  どっちが良いの?	
•  ぱっとみ綺麗に書ける方を選ぶと良い	
  
•  でも、Ruby,	
  JS,	
  Scala	
  なら	
  spec系一択かな	
  
•  ゆめみでは	
  PH...
単体テストは	
  
何をテストして	
  
何をテストしないのか?	

32	
  
単体テストは何をテストするのか	
•  ブラックボックステスト か ホワイトボックステストかで
異なる	
  
	
  
ちなみに	
  
•  ブラックボックステストの意味	
  
–  中の実装を見ないでテストする	
  
–  先ほどの最...
ブラックボックステスト	
•  何をテストしているか	
  
–  実装コードの「仕様」をテストしている	
  

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

•  全ての分岐を一度は通るテストを書く	
  
–...
どっちが良いの?	
•  ブラックボックステストが書けるのが望ましい	
  
–  これが書ければリファクタリングが容易	
  

•  ブラックボックステストが書けないケースがあ
る(後述)	
  
–  ホワイトボックステストで頑張る	
 ...
単体テストは何をテストしないのか	
•  「他の関数・API・外部機能・フレームワーク等」	
  
–  の、呼び出し方が正しいか	
  
–  が、想定通り動くか	
  
などはテストしない。あくまで「単体」がメイン。	
  
	
  
目の...
「テストコード」自体の品質	
•  どの程度間違いを検出できるか	
  
•  実装コードを誤修正して検出するかでわかる	
  
–  適当にコメントアウトしてみる	
  
–  異常な定数に変更してみる	
  
–  if	
  の 条件に ...
最近の単体テスト	

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

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

•  そして	
  
–  テ...
最近の風潮	
•  ホワイトボックステストで構わないからどんど
ん書こう!	
  
•  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%...
MockとStubの違い	
•  この会では違いは重要ではないが一応	
  
•  Stub	
  
–  あるObjectのように振る舞うDouble	
  

•  Mock	
  
–  Stubに「その結果の検証」機能が付いたもの	
 ...
MockやStubが何故重要か	
•  生成するのが大変なObjectの代わりができる	
  
–  Frameworkが作成するようなObject	
  
–  ごにょごにょ深淵から湧いてくるObject	
  

•  外部APIなどにアク...
例えば	
「日曜日?」という関数が、こんな実装だったとする	
  

def is_sunday? !
Time.now.wday == 0 !
end !
一見こいつは手に負えない。	
  
何故なら Time.now	
  を直接呼んでいる...
考え方	
•  テストしたいのはこのロジックの部分だ	
  
•  Time.now	
  の結果によって、ちゃんと変化するかをテ
ストすれば良いのだ	
  
•  私はTime.now	
  を内部で使っていることを知っている	
  
•  ...
Stubで振る舞いを変える	
Time	
  に	
  now	
  が来たら特定の値を返すように仕込む	
  
describe 'is_sunday?' do !
it 'should be true if today is Sunday'...
考察	
•  こんな風に動的に既存のObjectの振る舞いを変えら
れるかどうかは言語によって違う	
  
–  言語によってStubのHackがどこまでできるかは調べよう	
  

•  実装コードを修正しないとテストできない場合もある	
 ...
テストしにくそうな機能について	

もう少し具体例	

49	
  
郵便番号を元に都道府県をprintする機能	
•  仕様	
  
–  入力: 郵便番号(文字列)	
  
–  出力:	
  
•  都道府県文字列 →	
  標準出力 	
  
•  ZIPCODEが実在しない場合は何も出力しない	
  
...
郵便番号を元に都道府県をprintする機能	
•  都道府県を調べる方法	
  
–  hmp://zip.cgis.biz/	
  	
  の	
  APIを使う	
  
–  例:	
  hmp://zip.cgis.biz/xml/zip...
APIの戻り値: 実在する場合	
<ZIP_result> !
<result name="ZipSearchXML"/> !
<result version="1.01"/> !
<result request_url="http%3A%2F...
APIの戻り値: 実在しない場合	
<ZIP_result> !
<result name="ZipSearchXML"/> !
<result version="1.01"/> !
<result request_url="http%3A%2...
この機能は少しテストしにくい	
•  外部APIアクセスがある	
  
•  出力を標準出力に行うことになっている	
  

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

54	
  
とりあえずテスト書いて実行	
describe SomeClass do !
※中身がない	
describe 'print_prefecture' do !
it 'should print 東京都 when zipcode=1030000'...
よし、じゃあテストの中身を書こう	
  
・・・いや待てよ。。。	
•  テストが書けない	
  
–  返り値をテストするわけではないし	
  
–  APIアクセスあるからネットワーク切れてたらテストできな
いし	
  

•  この機能全...
とりあえずこんな感じで	
class SomeClass !
def print_prefecture(zipcode) !
prefecture = get_prefecture(zipcode) !

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

!...
Stubでホワイトボックステスト	
describe SomeClass do !
describe 'print_prefecture' do !
before do !
@obj = SomeClass.new !
@obj	
  に毎回O...
実装コードをテストしやすく修正	
class SomeClass !
def print_prefecture(zipcode) !
prefecture = get_prefecture(zipcode) !
if prefecture !
...
Output()をCallすればOKと考える	

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

get_prefecture	
  が 東京都 を返すなら	
  
@obj.stub(:get...
Outputされないケースも追加	
describe SomeClass do !
describe 'print_prefecture' do !
before do !
@obj = SomeClass.new !
end !
	
it '...
テスト実行!	
SomeClass	
  
	
  	
  print_prefecture	
  
	
  	
  	
  	
  should	
  print	
  東京都 when	
  zipcode	
  is	
  in	
  東...
続けて細かい実装を先に書いてしまう	
勢いってものがあるじゃないですか	
  
require 'net/http' !
require 'rexml/document' !
	
class SomeClass !
def print_pref...
テスト: get_prefecture()	
実装コード	
  
def get_prefecture(zipcode) !
xml_data = Net::HTTP.get(URI.parse("http://zip.cgis.biz/xml...
テスト: scan_prefecture()	
実装コード	
  
def scan_prefecture(xml_data) !
attr_state = REXML::Document.new(xml_data).elements['//@...
describe SomeClass do !
before do !
@obj = SomeClass.new !
end !
	
describe 'print_prefecture' do !
it 'should print 東京都 w...
テスト実行	
SomeClass	
  
	
  	
  print_prefecture	
  
	
  	
  	
  	
  should	
  print	
  東京都 when	
  zipcode	
  is	
  in	
  東京...
考察1	
•  結構Test	
  Doubleを多用するハメになった	
  
•  しかし、一番間違えやすそうな
scan_prefecture()	
  はブラックボックステストで
きている	
  
–  ここはもっとテストケースが追加され...
考察2	
•  「外部入出力のOutput()	
  と	
  Net::HTTP.get」 だ
け	
  Test	
  Doubleにすれば、全体を通したテスト
も可能	
  
–  1つくらい書いておくと安心できる	
  

•  あまり...
さいごに	

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

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

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

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

73	
  
Upcoming SlideShare
Loading in …5
×

最近の単体テスト

13,068 views

Published on

社内勉強会の資料です。
初心者〜中級者くらいを想定しています。

Published in: Technology
  • DOWNLOAD THIS BOOKS INTO AVAILABLE FORMAT (Unlimited) ......................................................................................................................... ......................................................................................................................... Download Full PDF EBOOK here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download Full EPUB Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... ACCESS WEBSITE for All Ebooks ......................................................................................................................... Download Full PDF EBOOK here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download EPUB Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download doc Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... ......................................................................................................................... ......................................................................................................................... .............. Browse by Genre Available eBooks ......................................................................................................................... Art, Biography, Business, Chick Lit, Children's, Christian, Classics, Comics, Contemporary, Cookbooks, Crime, Ebooks, Fantasy, Fiction, Graphic Novels, Historical Fiction, History, Horror, Humor And Comedy, Manga, Memoir, Music, Mystery, Non Fiction, Paranormal, Philosophy, Poetry, Psychology, Religion, Romance, Science, Science Fiction, Self Help, Suspense, Spirituality, Sports, Thriller, Travel, Young Adult,
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • DOWNLOAD THIS BOOKS INTO AVAILABLE FORMAT (Unlimited) ......................................................................................................................... ......................................................................................................................... Download Full PDF EBOOK here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download Full EPUB Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... ACCESS WEBSITE for All Ebooks ......................................................................................................................... Download Full PDF EBOOK here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download EPUB Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... Download doc Ebook here { https://tinyurl.com/y6a5rkg5 } ......................................................................................................................... ......................................................................................................................... ......................................................................................................................... .............. Browse by Genre Available eBooks ......................................................................................................................... Art, Biography, Business, Chick Lit, Children's, Christian, Classics, Comics, Contemporary, Cookbooks, Crime, Ebooks, Fantasy, Fiction, Graphic Novels, Historical Fiction, History, Horror, Humor And Comedy, Manga, Memoir, Music, Mystery, Non Fiction, Paranormal, Philosophy, Poetry, Psychology, Religion, Romance, Science, Science Fiction, Self Help, Suspense, Spirituality, Sports, Thriller, Travel, Young Adult,
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

最近の単体テスト

  1. 1. 最近の単体テスト 2014年2月   株式会社ゆめみ     森下   @mokemokechicken 1  
  2. 2. はじめに •  社内勉強会の資料です   •  初心者〜中級者くらい向けです   •  私なぞが単体テストについて書いていいのか という気もしますが、、もう書いてしまいました 2  
  3. 3. 単体テストの基本 3  
  4. 4. 単体(Unit)テスト(Test) •  自動で何度も実行・検証できる   •  モジュール・クラス・メソッド単位でテストする   •  実装コードとテストコードが対応している def add(a, b) ! return a + b ! end ! def test_add ! ... end ! ⇐  「実装コード」に対応した   ⇐  「テストコード」が(複数)ある 4  
  5. 5. 「単体テスト」じゃないテスト? •  Integra:onテスト   –  統合テストとか結合テストとも言う   –  複数のモジュールやシステムをまたがるテスト 実際は、「単体テスト」とちょっぴり「Integra:onテスト」を含むテストコードをよく書きます 5  
  6. 6. 単体テストの大事な精神 •  どんな環境でも軽快に確実に実行できること •  「ないよりはマシ」くらいの気持ちで書く •  完璧を求めない、ポイントを抑える 6  
  7. 7. 大事な原則 •  テスト対象の実装コードの全てをカバーする   –  メソッド・モジュールの数だけテストを書く   •  テストコードのテストは書かない!   •  変更の掟   –  仕様を変更するなら先にテストを変更する   –  リファクタリングするならテストは変更しない 7  
  8. 8. 全てにテストを書く? •  かかない部分があっても良い   –  例えばView周りとかは、かなり工夫しないと意味 があるテストを記述できない   •  なるべく書ける範囲を増やす気持ちが大事   8  
  9. 9. TDDという概念 •  テスト駆動開発(Test-­‐Driven  Development)   •  テストを最初に書いてPassさせるという考え   –  →  テスト実行  →  テスト失敗   –  →  実装修正         –  →  テスト実行  →  テスト成功   •  テストを最初に書く: 「テストファースト」と呼ぶ   •  なかなかストイックな開発手法である 9  
  10. 10. TDD、だがしかし •  ぶっちゃけ、いつもいつもTDDだと疲れる   •  もちろんTDDが綺麗にハマる場合もある   •  もしTDDで慣れている人は継続した方が良い   •  私は「テストセカンド」くらいで良いと思う   –  実装書いたらテスト書く   –  だって、「無いよりマシ」なんだから   10  
  11. 11. 最大公約数を求める関数 TDDの例 11  
  12. 12. 最大公約数を求める関数 •  仕様   –  名前は gcm(a,  b)     –  変な値が来たら例外を投げる   –  それ以外は答えを返す 12  
  13. 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  
  14. 14. テストを実行する テストは失敗する。     だって、gcmという関数がないのだから。 14  
  15. 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. 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. 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. 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. 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. 考察 •  批判   –  別にまだふざけた実装も可能じゃないか?   –  文字列とか小数のケースをテストしてないよ   •  でも、それは気にしなくても良い   –  テストは関数の完全性を担保するわけではない   –  「ポイントを抑える」「無いよりはマシ」である   –  必要だと思えば追加しておきましょう   •  小数の場合くらいはあるべきかな・・ 20  
  21. 21. テストのメリット •  目標ができる   –  テストをパスすると何か達成した感じがある   •  安心できる   –  関数gcm()が正しいか自分なりに自信が持てる   –  もっと安心したいから、調子に乗って実際もっとテスト ケースを追加することも多い  →  品質も上がる   •  仕様がはっきりする   –  テストコードを見て、実装コードの挙動がわかる 21  
  22. 22. 単体テストの歴史? 22  
  23. 23. 元祖は JUnit  (だと思う) •  JUnit  (Java用のUnitTestライブラリ)   –  最初に発表されたのが1997年頃らしい   –  ちなみにJava1.0が1995年   •  その後流行した(=良いやり方だった)   –  RUnit  (ruby),  PHPUnit,  PyUnit(Python)  など続く   –  xUnitが無い言語は少ないと思う   •  これらをまとめて 「xUnit」 系のテストフレーム ワークと呼ぶ 23  
  24. 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. 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. 26. xUnitのメリット •  ルールが簡単で覚えやすい   •  意外とこれくらいできれば十分である 26  
  27. 27. RSpec革命 •  RSpec  (Rubyのテストフレームワーク)   •  より自然に仕様や振る舞いを記述   •  ぶっちゃけ、xUnit  とできることは同じ   •  ただ、その華麗さにより xUnit  を過去のもの にし始めている 27  
  28. 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. 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. 30. specブーム •  RSpecの派生が流行している   –  PHPSpec,  Kiwi(Objec:ve-­‐C),  specs2(Scala)   –  Jasmine(JavaScript),  Mspec?(C#)   •  ただ、文法的な制約が言語によってあるので RSpecほど美しいのは少ない   –  Specs2  と Jasmine+CoffeeScript  くらいか?   30  
  31. 31. xUnit  と  spec  どっちが良いの? •  ぱっとみ綺麗に書ける方を選ぶと良い   •  でも、Ruby,  JS,  Scala  なら  spec系一択かな   •  ゆめみでは  PHPは PHPSpec  を標準にしようかと 思っています   –  PHPには色々spec系があるのですが、   PHP的に奇天烈じゃないのでPHPSpecが良いのかな 31  
  32. 32. 単体テストは   何をテストして   何をテストしないのか? 32  
  33. 33. 単体テストは何をテストするのか •  ブラックボックステスト か ホワイトボックステストかで 異なる     ちなみに   •  ブラックボックステストの意味   –  中の実装を見ないでテストする   –  先ほどの最大公約数のテストはこちら   •  ホワイトボックステストの意味     –  中の実装を見ても良いからテストする 33  
  34. 34. ブラックボックステスト •  何をテストしているか   –  実装コードの「仕様」をテストしている   •  正常系・異常系・境界系などの入力値で正し い振る舞いをするか検証する   •  これは理想的なテスト 34  
  35. 35. ホワイトボックステスト •  何をテストしているか   –  実装コードの「ロジック」をテストしている   –  実装が想定通り「外部への入出力」をするかをテストしている   •  全ての分岐を一度は通るテストを書く   –  コードカバレッジ(テストが通過した実装コード率)を高めるのが 一つの指標   –  全ての「分岐の組み合わせ」はテストしなくても良い   –  ただ、分岐が間違っていれば検知すること   •  色々仮定を置いた上で「入出力」をテストする   –  MockやStub(後述)   35  
  36. 36. どっちが良いの? •  ブラックボックステストが書けるのが望ましい   –  これが書ければリファクタリングが容易   •  ブラックボックステストが書けないケースがあ る(後述)   –  ホワイトボックステストで頑張る   •  無いよりマシ   –  なるべくブラックボックステストになるよう設計する   36  
  37. 37. 単体テストは何をテストしないのか •  「他の関数・API・外部機能・フレームワーク等」   –  の、呼び出し方が正しいか   –  が、想定通り動くか   などはテストしない。あくまで「単体」がメイン。     目の前の「実装コード」がとにかく重要である     •  テストしても良いけど、   「どんな環境でも軽快に確実に実行できること」   の方が重要なことは忘れずに     37  
  38. 38. 「テストコード」自体の品質 •  どの程度間違いを検出できるか   •  実装コードを誤修正して検出するかでわかる   –  適当にコメントアウトしてみる   –  異常な定数に変更してみる   –  if  の 条件に not  を入れてみる   –  and  を or  にしてみる   –  Etc.. 38  
  39. 39. 最近の単体テスト 39  
  40. 40. もう一度、テストファースト •  テストファーストの元々の意味   –  「最初にテストコードを書く」   •  最近思うテストファースト   –  「テストを行うことを最優先に考える」   •  そして   –  テストを支援するフレームワークを使う!   –  必要なテストを支援しないフレームワークはダメです   •  可能な限り使わない   40  
  41. 41. 最近の風潮 •  ホワイトボックステストで構わないからどんど ん書こう!   •  MockとStubを活用して、今まで書きにくかった テストもどんどん書こう!   41  
  42. 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. 43. MockとStubの違い •  この会では違いは重要ではないが一応   •  Stub   –  あるObjectのように振る舞うDouble   •  Mock   –  Stubに「その結果の検証」機能が付いたもの   •  他にもSpyとかFakeとかいう用語もある 43  
  44. 44. MockやStubが何故重要か •  生成するのが大変なObjectの代わりができる   –  Frameworkが作成するようなObject   –  ごにょごにょ深淵から湧いてくるObject   •  外部APIなどにアクセスした「つもり」にできる   –  アクセスした「つもり」で「結果」を与えられる   •  どこでも実行できるテストになる   •  いつでも同じ結果になるテストになる   •  テスト可能コードが格段に簡単に増やせる 44  
  45. 45. 例えば 「日曜日?」という関数が、こんな実装だったとする   def is_sunday? ! Time.now.wday == 0 ! end ! 一見こいつは手に負えない。   何故なら Time.now  を直接呼んでいるので、   テストを実行したときの日時に左右されてしまうからである。     まあ、この実装は直せるが、とりあえずこれを例に考える。   45  
  46. 46. 考え方 •  テストしたいのはこのロジックの部分だ   •  Time.now  の結果によって、ちゃんと変化するかをテ ストすれば良いのだ   •  私はTime.now  を内部で使っていることを知っている   •  Time.now  の結果を操れればテストできる! 46  
  47. 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. 48. 考察 •  こんな風に動的に既存のObjectの振る舞いを変えら れるかどうかは言語によって違う   –  言語によってStubのHackがどこまでできるかは調べよう   •  実装コードを修正しないとテストできない場合もある   –  こういうのも言語によって色々テクニックがある   •  テストのため実装を修正するのか!?   –  答えはYES! YESだよ!!   –  テストファーストだよ! 48  
  49. 49. テストしにくそうな機能について もう少し具体例 49  
  50. 50. 郵便番号を元に都道府県をprintする機能 •  仕様   –  入力: 郵便番号(文字列)   –  出力:   •  都道府県文字列 →  標準出力   •  ZIPCODEが実在しない場合は何も出力しない   こんな感じ class SomeClass! def print_prefecture(zipcode) ! end ! end! 50  
  51. 51. 郵便番号を元に都道府県をprintする機能 •  都道府県を調べる方法   –  hmp://zip.cgis.biz/    の  APIを使う   –  例:  hmp://zip.cgis.biz/xml/zip.php?zn=1030000 51  
  52. 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. 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. 54. この機能は少しテストしにくい •  外部APIアクセスがある   •  出力を標準出力に行うことになっている   まあしかしとりあえずやってみましょう 54  
  55. 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. 56. よし、じゃあテストの中身を書こう   ・・・いや待てよ。。。 •  テストが書けない   –  返り値をテストするわけではないし   –  APIアクセスあるからネットワーク切れてたらテストできな いし   •  この機能全体をブラックボックステストできない   ホワイトボックステストしかない •  先に実装を進めよう   –  テストしやすくなるように気をつけながら   56  
  57. 57. とりあえずこんな感じで class SomeClass ! def print_prefecture(zipcode) ! prefecture = get_prefecture(zipcode) ! ここで都道府県が戻る想定   ! if prefecture ! puts prefecture ! end ! end ! end ! Nil  じゃなければ出力   57  
  58. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 68. 考察1 •  結構Test  Doubleを多用するハメになった   •  しかし、一番間違えやすそうな scan_prefecture()  はブラックボックステストで きている   –  ここはもっとテストケースが追加されるべきだろう   •  他の部分は、そんなに難しいことしてないし、 一応テストは付いている 68  
  69. 69. 考察2 •  「外部入出力のOutput()  と  Net::HTTP.get」 だ け  Test  Doubleにすれば、全体を通したテスト も可能   –  1つくらい書いておくと安心できる   •  あまり手間をかけずに、効率良くテストできる   69  
  70. 70. さいごに 70  
  71. 71. さいごに •  最近の単体テストは割とお手軽になっている   •  「無いよりはマシ」なので、どんなに時間がな くても1つくらい書こうよ   –  環境をSetupしておくのが大事   •  難しいロジックを書く時だけ使ってもOK 71  
  72. 72. 単体テストの今後 •  知らない・書けない、だと恥ずかしい   –  面倒だから書かない、ならまだわかるが   •  職業プログラマの基本スキル   –  ゆめみでもそう位置づけます   •  単なる慣れですので、早目に覚えましょう 72  
  73. 73. 参考図書 •  体系的ソフトウェアテスト入門   –  hmp://www.amazon.co.jp/dp/4822282074 73  

×