ユニットテストで人生に勝利する by Mark Story
ユニットテストは開発の中で大事なプロセスです
ユニットテストは手動でクリックするようなテストと違い
自動化されたテストです
今日は僕がどうしてユニットテストをしているか
また実際のテストの方法についてお話しします
テストしづらい部分をテストする方法や
テストを最新の状態に保つ Continuous Integration についてもお話します
大学を出てすぐにプログラミングを始めました
お金が必要だったので(笑)
CakePHPを使って2年半になります
DebugKit や ApiGenerator などのプラグインを作りました
普段は FreshBooks で働いています
オンライン上で請求書を発行するようなサービスで
そこではリードデベロッパをしています
ユニットテストは僕を素晴らしくしてくれるのでしょうか?
激しくその通りです
何故ユニットテストをするのでしょうか
余分な時間は掛かりますが、その他のコードが正しく動いているのを確認する為です
特に後になってから起きた問題を修正する時間を削減するのが主な理由ですね
何かを変更した後にそれまで動いていた部分が動いている事を確認する
何かを変更して動いていた物を壊してしまった事はある人はどれくらい居ますか?
(挙手多数)
テストを書けばそういう事は起きません
バグを見つけたらその部分のテストを書けば
同じバグが発生する事はありません
またユニットテストは複雑なコードの設計の助けにもなります
プログラムをシンプルな部品に分割する
例えば「CMSを作れ」という素晴らしいチケットがあった時
細かいテストにブレークダウンして
テストを一つづつパスさせれば、何かが動いた事になります
またテストは生きたドキュメントにもなります
内容を忘れてもテストを見ればわかります
また問題を早めに見つける事もできます
何か変なものをコードに入れてしまってもすぐにわかります
コードの確実性を高める事もできます
とにかく知っている範囲で動くようにテストを書きます
クリックしながらテストをすると3時間かけても 自分の所では動いたと思うとしか言えません
でも僕らには自動化された殺人兵器があります
自動化されたテストの利点はまさに自動化されている事です
CIもできますし、テストを素早く実行できます
変更をする度に5時間かけてクリックするのではなく
ボタンをクリックするだけで何か失敗していないかやきちんと動いているかが確認できますね
テストできるものは全てテストするべきです
夜中に問題を起こしそうなコード
問題が起きる恐怖を軽減する為にテストを書きましょう
また金銭に関わるようなコードにもテストを書けばユーザーやあなたが金銭を失わないようにできます
また以前にも問題が起きた壊れやすいコードにもテストを書きましょう
また手動でテストしにくいものにもテストを書きます
PDFの生成のように時間がかかる部分にテストを書いた事があります
またテストには異なる種類があります
ユニットテストと機能テスト、結合テストです
ユニットテストはアプリケーションの細かい部分のテスト
オブジェクトやメソッド、関数単位で正しい結果を返しているかなどをテストします
また多くの場合モックを使います これはテストしづらい部分をテストするのにとても便利です
テスト駆動開発をする際にもユニットテストは便利です
テスト駆動開発ではコードを書く前にテストを書きます
テストを書いて コードを書いて またテストを書いてコードを書く その繰り返しですね
テストが失敗するようなコードを本番環境向けに書かない
僕は普段、コードを書いてそれからテストを書きます
そしてテストが壊れないように余分なコードを消します
機能テストは部品が一緒にうまく動作するかをテストします
上位のオブジェクトから部品が正しく動いているかをテストする
例えばCakePHPもModelが変更されたら結合テストを行う
あなたが書いたコードだけなく データベースと通信する部分などが正しく動いている事をテストする
機能テストは比較的時間がかかります
多くのリソースやデータベースを使うからです
さらにはリモートのサービスを実行したりもします
ユニットテストは基本的に1つのクラスのテストです
細かい部品ごとに正しい動作をテストする
それをまとめてに行うのが結合テストです
テストをする時にはいくつかの課題があります
ちょっと水を飲みます
話すのが速すぎるかな? 大丈夫?
まずはテストを書くのに時間がかかる事
しかし同じバグを何度も修正するのはもっと時間がかかります
正しいテストを書けば同じバグはもう起こりません
テストは問題がある事しか証明しません
問題が無い事は証明しません
1つテストがあるのはバグが無いという意味ではなく、1つのバグがテストされたという事にしかなりません
またユニットテストはテストがある部分のエラーしか見つけられません
コンピューターはテストがある部分だけテストします
なのでありえるパラメータ範囲などもテストを書かないといけません
テストの利点はなんでしょうか
余分な手間が最初にかかりますが、問題を早く見つける事ができます
QAテスターや上司がクリックして「どうして壊れてるんだ」と聞かれる前に問題を見つけられます
またテストのプロセスを自動化する事が出来ます
自動化はすばらしいです フレームワークも色々な事を自動化してくれますが テストはもっと自動化してくれます
またユニットテストは結合テストに発展させる事もできます
セレニウムのスクリプトを書いてコンソールから
ブラウザでクリックするようなテストを実行できます
これはほんとに素晴らしいです
決められた仕様をテストにすれば
テストがパスすれば仕様を満たせている事が確認できます
以上でテストの素晴らしさの賞賛は終わりです
次にモックオブジェクトの話題
モックオブジェクトはテストしづらい部分をテストする際にとても便利です
個別のテストを書きづらい時にモックオブジェクトを使えば簡単になります
オブジェクト同士を呼び合わないようにテストさせる事ができます
オブジェクトがそれぞれ間違った方法で呼び出し合って
テストが失敗するのを防げます
またモックは実装前にオブジェクトの振る舞いを確認する助けにもなります
例えば何かのサービスにPOSTして結果を配列で返すようなクラスの場合
例えばそのサービスが友達のジョーが書くとしたそれがどうなるのか予測できません
必要な配列を返すモックがあればジョーの方で問題があっても関係ない
ジョーの部分が終ったらジョーがその部分に対してテストを書けばよい
そうすれば僕はジョーの所で何をやっているかは知らなくても大丈夫
モックをグローバル関数やグローバルなスコープで読み込まれるファイルに使えるでしょうか?
使えません
モックを使うにはクラスになっていて、処理がメソッドの中にある必要があります
モックはどういう時に使うべきでしょうか?
モックは外部と通信するようなコストが高かったり 危険の大きい時に使うのがベストです
テストを実行する度に顧客にEメールを送信なんて事ありえないよね(笑)
メール送信処理はサーバーとの通信などのコストも高い
そういった外部依存を少なくする
TwitterやPaypalなどと通信する部分にモックを使うのもパーフェクトな例
Twitterが落ちてしまうとコードが壊れていないのにテストが失敗してしまう
この部分にモックを使っておけばTwitterが落ちても開発を継続できる
また落ちた事が無いようなサービスにモックを使っておけば
そのサービスが落ちてしまった時に自分のコードに何が起きるのかを知る事ができる
例えばPaypalが落ちた時にコードが爆発してしまうのか そのまま動いてしまうかは知っておくべきだ
ユニットテストとモックを使えばこういった事態を想定できる
またユニットテスト中で不必要に大量にディスクに書き込みをしてしまう場合もモックにできる
データベースもご存知の通り遅いのでモックにできる
データベースに /dev/null でも使ってない限りはね (笑)
/dev/null はWEBスケーラブルだ (有名なジョーク)
データベースへの書き込みは遅くてコストかかるのでモックを使えばたくさんのテストを実行するのに便利だ
本当にデータベースに通信しなければいけない場合は別だけど 大抵の場合はそうじゃないね
クラスが2つある場合 それぞれが正しく呼び出し合っている事を確認することになる
その場合にスタブとしてモックを使う事ができる それぞれが期待する動作をモックに実装する
期待しないパラメータが呼び出されたらテストが失敗するようにしておく
そうやって問題の範囲を絞って
個別のコードが動くために必要な部分をスタブ化して扱いやすくする
そうすれば問題を分離できる
例えばこのメソッドがNullを返すとエラーが起きる時に
ファイルがおかしい時にNullになるなんて時にモックを使ってテストすれば問題を分離できる
どうやってモックを作るか
モックにするには依存関係を実行時に変更できる必要がある
僕が使っている3つの方法はコンストラクタ渡しとファクトリーメソッドとセッターメソッドだね
それぞれの例ではPHPUnitのモックを使っているけど質問があれば聞いてください
コンストラクタ渡しはJavaなんかで古くから使われている依存関係の処理で
依存するクラスのオブジェクトをコンストラクタで受け取るようにする方法だ
このCarクラスの場合はエンジンとドライバーのオブジェクトをコンストラクタで受け取っている
モックを使いたい時はエンジンやドライバーのモックオブジェクトを変わりに渡せばいい
次はファクトリーメソッド
ファクトリーメソッドはサブクラスでモックを返すようにオーバーライドすればいい
このRaceCarクラスはエンジンのオブジェクトをgetEngineメソッドから取得する
モックを使う場合はgetEngineメソッドを別のオブジェクトを返すようにオーバーライドすればいい
さらに別の方法がセッターメソッド
フレームワークやツールでよくあるようにsetやgetでオブジェクトを操作する方法だ
これなら簡単にオブジェクトを入れ替える事ができる
GiantEngineを入れ替えたければsetEngineを使える
モックはテストの目的によっては重要になる
モックやスタブで危険なオブジェクトやメソッドをテストから除く事ができる
モックを使ってオブジェクトが他のオブジェクトを正しく扱っているかを確かめられる
この例ではgetMockメソッドでモックを取得し
何かしら危険なtypeメソッドをスタブ化している
最初はjsonのパラメータが渡され 次の例ではxmlのパラメータが渡されている事をチェックしている
RequestHandlerオブジェクトが正しく動いていればテストはパスするはず
テストの中でオブジェクトを作る時にモックを作り 期待するパラメータごとの結果をスタブ化する
これはCake2の実際のテストの例だけど
危険な処理のスタブ化
ヘッダーの送信の処理は危険な処理だ
外部のサービスに通信する処理もそうだし
呼び出しの度に課金が発生するようなサービスをテストの度に呼び出すのはよくないやりかただ
そういった場合にスタブを使えばいい
さっきのやり方と同じでヘッダを送るメソッドをスタブ化している
statusCodeメソッドが呼び出された場合は301
headerが呼び出された場合はLocationとURLの2つのパラメーターが渡る事を期待している
どうしてスタブ化するかというと オブジェクトがheader関数を呼んでしまうからだ
スタブ化しないで実行してしまうとテストの度にcakephpのサイトを見る事になってしまう
モックを使ってテスト時に起こる良くない現象を解決している
コストのかかる処理のスタブ化
このCampainMontiorは遅いので隠してしまおう
幾つか連絡先の情報を作っておいてモックを作り
このメソッドが呼ばれた時はこのデータが返ってくるとみなす
DependencyInjectionでこのクラス全体を置き換える事もできるけど
モックを使って自動的にメソッドが呼ばれた時の戻り値を設定してる
わかるかな?
この場合だとonceなので2回呼び出された場合はテストが失敗する
厳密に数値ごとの結果を定義してテストを失敗させることもできる
このセクションのまとめ
モックはテストを素早く実行させたり、テストの事前事後の処理を少なくする事ができる
外部サービスを呼ぶような場合もスタブ化してしまえば
本当の意味でのユニットテストが実行できる
重要なところをテストして余分なものを分離する
以上でモックの話はおわり
もちろんSimpleTestにもモックオブジェクトはある
コアクラスのテストケースを見れば大量にモックを使っているのがわかると思う
自動殺人ロボット
人体模型でも出来る自動テストだ
実行されていないテストは消していい
こういう場合ビルドサーバーを建てよう
Hudsonはとても使いやすいJava製のビルドサーバーだ
たくさんのプラグインがあってメールを送ったり、Jabberにメッセージを送ったりできる
テストがコードをどれだけカバーしているかのカバレッジレポートを出すこともできる
テストを毎晩とか毎時とか毎日にスケジュールしたり、ポストコミットのフックを設定もできる
起動したHudsonはコードをチェックアウトして
さらにスクリプトを実行してテストを行う
SvnでもGitでも使える
MercurrealやBazarも大丈夫 使わない理由はないね
一般的なのはコミットやプッシュの度にテストを実行する
短い間隔でジョブを待機させてテストする
開発が進んでない場合も夜間にテストを実行する
サーバーが空いている時にカバレッジレポートを出力する
Hudsonのセットアップは今見せようと思ったけどネットワークがおかしいみたいで
夕べやったやつをお見せします
wgetでダウンロードしてjavaコマンドで実行する
ターミナルの大きさお変えて
Hudsonのディレクトリを開く
見えるかな?
Hudsonは他のJavaプログラムと違ってコンテナやTomcat無しで
5000の依存関係をコンパイルしたりしなくても
1ファイルをダウンロードして実行できる
インストールに何時間もかかってTomcatを入れてなんて
事も前にあったけど
これでローカルでHudsonが動いている
localhostの8080にアクセスして
おっと
(.comを指摘されて)みなさんのほうが賢いみたいだ
これが僕のマシンの上のHudson
この画面でHudsonの管理ができる
ビルドの履歴をプロジェクトリストから見たり
最後にテストが失敗したか成功したかが見れる
これはプロジェクトの例
テストのプロジェクトを作ってみよう
フリースタイルプロジェクトを選んで
gitリポジトリを使う
このブランチを使って
手動でビルドしたり定期的にビルドしたり SCMから呼ばれたりもできる
実行するシェルの設定
cakeのコンソールを使ってテストを実行
コアのヘルパーのテストを実行するようにする
メールの送信を設定
誰かメールを受け取りたいかい?
誰もいない? じゃあグラハムだ
ビルドが失敗する度に君にメールを送る
ビルドを壊した人にメールを送るなんてのもあるね
これでプロジェクトは設定できた
これでビルドが失敗するとすぐに「マークがビルドを失敗させた」と周知する事ができる
これで壊した人が責任をもって直すようになるね
実にすばらしい
ビルド中だね
♪
Hudsonには複数のプロジェクトを作成できる
Hudsonはリポジトリからローカルにコードを持ってきて
指定されたコマンドを実行する
cakeコマンドとか必要なら他のコマンドも先に実行できる
そして最後のコマンドのexitステータスが失敗ならビルドは失敗した事になる
exitステータスが成功ならビルドも成功
コンソールのアウトプットを見てみよう
githubからクローン中だね
時間がかかりそうだ
(ネットワークが不調)失敗しそうだ
ビルド失敗だね
1分前に失敗 グラハムにはメールが届くはずだ
設定を変更
(ローカルのリポジトリに変更)
これで動くかな
ディレクトリ名で動くらしい グラハムによると
実行中だ
ネット接続が面白い事になってるね
失敗だ
ネットワーク接続があればなー
遅いな
やっぱり繋がってないな
本来ならここにビルド成功を示す青いラインがでる
ちゃんと動いていればね
コンソールにテストの内容と結果が表示される
グラハム メールは来た?
これで発表は終わりです
質問があればどうぞ 時間は大丈夫?
HudsonでSereniumは使えるか? 使えるよ
PHPUnitからSereniumを実行しているかSereniumRCを使ってブラウザを動かしていれば
ちょっと設定は面倒だけど
PHPUnitからSereniumを実行して結果をチェックする
HudsonがPHPUnitを実行してPHPUnitがSereniumを実行して Sereniumがブラウザを実行し
PHPUnitがテストが失敗したかどうかを伝える
これで回答になったかな? OK
何が動かないか? 設定が面倒だけど
依存関係も多くないし
Hudsonはユニットテストフレームワークには関与しない
単にコンソールを見るのでSimpleTestでも問題ない
Hudsonのコンソールを見ればわかるけど
何が実行されてどうして失敗したのか
OK
後ろの人
まだ無理だね
じゃあありがとうございました