最近の単体テスト

7,838 views
7,436 views

Published on

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

Published in: Technology
0 Comments
37 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,838
On SlideShare
0
From Embeds
0
Number of Embeds
58
Actions
Shares
0
Downloads
41
Comments
0
Likes
37
Embeds 0
No embeds

No notes for slide

最近の単体テスト

  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  

×