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

良質なコードを高速に書くコツ
良質なコードを高速に書くコツ良質なコードを高速に書くコツ
良質なコードを高速に書くコツ
Shunji Konishi
 
エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織
Takafumi ONAKA
 
こわくない Git
こわくない Gitこわくない Git
こわくない Git
Kota Saito
 
ソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューMoriharu Ohzu
 
オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門
増田 亨
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
Takuto Wada
 
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
Takuto Wada
 
C#でわかる こわくないMonad
C#でわかる こわくないMonadC#でわかる こわくないMonad
C#でわかる こわくないMonad
Kouji Matsui
 
.NET Core 3.0時代のメモリ管理
.NET Core 3.0時代のメモリ管理.NET Core 3.0時代のメモリ管理
.NET Core 3.0時代のメモリ管理
KageShiron
 
クソコード動画「Managerクラス」解説
クソコード動画「Managerクラス」解説クソコード動画「Managerクラス」解説
クソコード動画「Managerクラス」解説
MinoDriven
 
やってはいけない空振りDelete
やってはいけない空振りDeleteやってはいけない空振りDelete
やってはいけない空振りDelete
Yu Yamada
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?
Yoshitaka Kawashima
 
PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門
泰 増田
 
ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説
増田 亨
 
C# 9.0 / .NET 5.0
C# 9.0 / .NET 5.0C# 9.0 / .NET 5.0
C# 9.0 / .NET 5.0
信之 岩永
 
オブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメオブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメYoji Kanno
 
ドメイン駆動設計の正しい歩き方
ドメイン駆動設計の正しい歩き方ドメイン駆動設計の正しい歩き方
ドメイン駆動設計の正しい歩き方
増田 亨
 
シリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのかシリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのか
Atsushi Nakada
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)
Yoshitaka Kawashima
 
やはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているやはりお前らのMVCは間違っている
やはりお前らのMVCは間違っている
Koichi Tanaka
 

What's hot (20)

良質なコードを高速に書くコツ
良質なコードを高速に書くコツ良質なコードを高速に書くコツ
良質なコードを高速に書くコツ
 
エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織エンジニアの個人ブランディングと技術組織
エンジニアの個人ブランディングと技術組織
 
こわくない Git
こわくない Gitこわくない Git
こわくない Git
 
ソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビューソースコードの品質向上のための効果的で効率的なコードレビュー
ソースコードの品質向上のための効果的で効率的なコードレビュー
 
オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門オブジェクト指向プログラミングのためのモデリング入門
オブジェクト指向プログラミングのためのモデリング入門
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
 
C#でわかる こわくないMonad
C#でわかる こわくないMonadC#でわかる こわくないMonad
C#でわかる こわくないMonad
 
.NET Core 3.0時代のメモリ管理
.NET Core 3.0時代のメモリ管理.NET Core 3.0時代のメモリ管理
.NET Core 3.0時代のメモリ管理
 
クソコード動画「Managerクラス」解説
クソコード動画「Managerクラス」解説クソコード動画「Managerクラス」解説
クソコード動画「Managerクラス」解説
 
やってはいけない空振りDelete
やってはいけない空振りDeleteやってはいけない空振りDelete
やってはいけない空振りDelete
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?
 
PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門
 
ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説ドメイン駆動設計サンプルコードの徹底解説
ドメイン駆動設計サンプルコードの徹底解説
 
C# 9.0 / .NET 5.0
C# 9.0 / .NET 5.0C# 9.0 / .NET 5.0
C# 9.0 / .NET 5.0
 
オブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメオブジェクト指向エクササイズのススメ
オブジェクト指向エクササイズのススメ
 
ドメイン駆動設計の正しい歩き方
ドメイン駆動設計の正しい歩き方ドメイン駆動設計の正しい歩き方
ドメイン駆動設計の正しい歩き方
 
シリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのかシリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのか
 
イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)イミュータブルデータモデル(入門編)
イミュータブルデータモデル(入門編)
 
やはりお前らのMVCは間違っている
やはりお前らのMVCは間違っているやはりお前らのMVCは間違っている
やはりお前らのMVCは間違っている
 

Similar to 最近の単体テスト

Start!! Ruby
Start!! RubyStart!! Ruby
Start!! Ruby
mitim
 
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.3
Masahiro Wakame
 
タダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnitタダで始めるテストファースト入門 ~ C# Express + NUnit
タダで始めるテストファースト入門 ~ C# Express + NUnit
Yasuhiko Yamamoto
 
第4回勉強会 単体テストのすすめ
第4回勉強会 単体テストのすすめ第4回勉強会 単体テストのすすめ
第4回勉強会 単体テストのすすめ
hakoika-itwg
 
はこだてIKA 第4回勉強会 単体テスト
はこだてIKA 第4回勉強会 単体テストはこだてIKA 第4回勉強会 単体テスト
はこだてIKA 第4回勉強会 単体テスト
Seiji KOMATSU
 
C言語講習会2
C言語講習会2C言語講習会2
C言語講習会2
odenhadengaku
 
第2回勉強会スライド
第2回勉強会スライド第2回勉強会スライド
第2回勉強会スライド
koturn 0;
 
Programming camp 2010 debug hacks
Programming camp 2010 debug hacksProgramming camp 2010 debug hacks
Programming camp 2010 debug hacks
Hiro Yoshioka
 
中3女子でもわかる constexpr
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexpr
Genya 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女子が狂える本当に気持ちのいい constexpr
Genya Murakami
 
Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3Lisp Tutorial for Pythonista : Day 3
Lisp Tutorial for Pythonista : Day 3
Ransui 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 Dialect
Ken Morishita
 
iOSやAndroidアプリ開発のGoodPractice
iOSやAndroidアプリ開発のGoodPracticeiOSやAndroidアプリ開発のGoodPractice
iOSやAndroidアプリ開発のGoodPractice
Ken 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

生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI
生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI
生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI
Osaka University
 
「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演
「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演
「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演
嶋 是一 (Yoshikazu SHIMA)
 
ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識
ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識
ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識
sugiuralab
 
Generating Automatic Feedback on UI Mockups with Large Language Models
Generating Automatic Feedback on UI Mockups with Large Language ModelsGenerating Automatic Feedback on UI Mockups with Large Language Models
Generating Automatic Feedback on UI Mockups with Large Language Models
harmonylab
 
Humanoid Virtual Athletics Challenge2024 技術講習会 スライド
Humanoid Virtual Athletics Challenge2024 技術講習会 スライドHumanoid Virtual Athletics Challenge2024 技術講習会 スライド
Humanoid Virtual Athletics Challenge2024 技術講習会 スライド
tazaki1
 
無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.
無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.
無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.
Yuki Miyazaki
 
論文紹介:Deep Learning-Based Human Pose Estimation: A Survey
論文紹介:Deep Learning-Based Human Pose Estimation: A Survey論文紹介:Deep Learning-Based Human Pose Estimation: A Survey
論文紹介:Deep Learning-Based Human Pose Estimation: A Survey
Toru Tamaki
 
ハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMM
ハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMMハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMM
ハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMM
osamut
 
ロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobody
ロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobodyロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobody
ロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobody
azuma satoshi
 

Recently uploaded (9)

生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI
生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI
生成AIがもたらすコンテンツ経済圏の新時代  The New Era of Content Economy Brought by Generative AI
 
「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演
「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演
「進化するアプリ イマ×ミライ ~生成AIアプリへ続く道と新時代のアプリとは~」Interop24Tokyo APPS JAPAN B1-01講演
 
ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識
ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識
ヒアラブルへの入力を想定したユーザ定義型ジェスチャ調査と IMUセンサによる耳タッチジェスチャの認識
 
Generating Automatic Feedback on UI Mockups with Large Language Models
Generating Automatic Feedback on UI Mockups with Large Language ModelsGenerating Automatic Feedback on UI Mockups with Large Language Models
Generating Automatic Feedback on UI Mockups with Large Language Models
 
Humanoid Virtual Athletics Challenge2024 技術講習会 スライド
Humanoid Virtual Athletics Challenge2024 技術講習会 スライドHumanoid Virtual Athletics Challenge2024 技術講習会 スライド
Humanoid Virtual Athletics Challenge2024 技術講習会 スライド
 
無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.
無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.
無形価値を守り育てる社会における「デー タ」の責務について - Atlas, Inc.
 
論文紹介:Deep Learning-Based Human Pose Estimation: A Survey
論文紹介:Deep Learning-Based Human Pose Estimation: A Survey論文紹介:Deep Learning-Based Human Pose Estimation: A Survey
論文紹介:Deep Learning-Based Human Pose Estimation: A Survey
 
ハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMM
ハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMMハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMM
ハイブリッドクラウド研究会_Hyper-VとSystem Center Virtual Machine Manager セッションMM
 
ロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobody
ロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobodyロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobody
ロジックから状態を分離する技術/設計ナイト2024 by わいとん @ytnobody
 

最近の単体テスト

  • 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