ユニットテストで人生に勝利する 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 後ろの人 まだ無理だね じゃあありがとうございました