Contenu connexe Plus de Takaya Kotohata (6) Tdd2. 自己紹介
• twitter @tii_kot
• blog http://tkot.hatenablog.com/
• github https://github.com/mhersmh
• Rails歴1.5年
• 最近はNode.jsとかErlangに注目してます
12年12月25日火曜日
3. テストは書かなきゃいけないの?
• 書きましょう
• 書くことで得られる知識は意外と広いので純粋に勉強になる
• 慣れればブラウザをリロードが馬鹿らしくなる
• デプロイ後のアプリケーションの可用性を高める
• 継続的デリバリー
12年12月25日火曜日
4. 逆に書かなくてもよいケースとは?
• 金銭や人命にかかわらないアプリであること
• Railsの基本的機能しか使っていないこと
• とりあえず動いてます的なプロトタイプで十分なこと
• 保守・運用の予定がないこと
確かに上記をすべて満たせば書く必要はないか
もしれない。
12年12月25日火曜日
5. Railsでよく使われるテストフレームワーク
• Test::Unit Ruby標準ライブラリ。
• shoulda Test::UnitをRSpec風に拡張してくれる。
• RSpec 一番人気。
• Cucumber 自然言語でシナリオを書いて、対応するユーザやアプリの動きを
Rubyのコードで書く受け入れテスト
DHHはTest::Unit派。それぞれ得意不得意があるのでプロ
ジェクトごとにベストなものを選べばよいが、迷ったら
RSpecを使えば間違いない
12年12月25日火曜日
6. RSpecとCucumberの組み合わせ
• かなりメジャーな組み合わせ
• Cucumberは自然言語で書かれたユーザストーリーをfeatureとして定義、開発
者以外も読むことができるのが利点とされる
• とはいえ本当に開発者以外が読むの?
• RSpecとCucumberで異なるDSLを学習するコストがかかるので、RSpec一本
でもいいような気がする。
• だけど今日はCucumberも勉強します笑
12年12月25日火曜日
7. 基本的なTDDの流れ
①要件定義・設計
②受け入れテスト作成
③設計・ユニットテスト ④コーディング
⑤リファクタリング
12年12月25日火曜日
8. 各フローのアクション
• 要件定義・設計・・・アプリケーションにどのような機能が必要か、どのよ
うに画面は遷移するかなどを洗い出す。各リソースにどのような仕事分担をさ
せるかなども考えるとよい
• 受け入れテスト・・・これにパスしたら要件が満たされているというテス
ト。ユーザーストーリーに対応し、ユーザの挙動をシミュレートするテストに
なる。CucumberあるいはRequest Specに対応する。
• 設計・ユニットテスト・・・最小単位で戻り値のオブジェクト型や必要なイ
ンターフェースを設計・定義する
• コーディング・・・ユニットテストをパスする最低限のコードをひとまず書
き上げる
12年12月25日火曜日
9. よくある誤解
• アジャイル開発は設計を厳格に定義せずにとりあえずコードを書いてみる手
法だなんていう風に思ってるかもしれませんがそれは全く誤解です
• Railsは最初のリソース設計でコケると先ほどの開発フローチャートをすべて
やり直すことになる! 一つのイテレーションにおける要件定義と設計、画面
遷移のイメージ確認などは十分時間をかけるべき
• ドキュメントを書かないなんていうのも全く誤り。ドキュメント主体になら
ないというだけで、設計について話しあったり将来的に追加されそうな機能
は積極的にメモを残すべきだし、なるべくドキュメントも同一のバージョン
管理下に置いたほうがいい。
12年12月25日火曜日
10. テストは完璧とは限らない
• テストフレームワークに慣れてないうちはむしろテストの書き間違いのほうが
圧倒的に多い
• 受け入れテストの変更はイコール仕様変更なので問題ないが、ユニットテス
トの変更は設計の変更レベルであり得て、しかもリファクタリング中に起こ
りうる
• そのため、「③で理想的設計を定義し、④から⑤に移行して終了」という流
れがベストでも、「⑤から③に立ち戻る」というケースも多い。現実的に
③④⑤はグルグル回るが、やはり最終的にユニットテストをコードが通過して
いればよい。
12年12月25日火曜日
11. 受け入れテスト・ユニットテストとは何か
• 受け入れ(acceptance)テスト=このテストを通過すればストーリーを満たして
いると考えて良いテストのこと。「ユーザーがXXにアクセスして、idに⃝⃝、
passwordに⃝⃝を入力してregisterボタンを押すと∼∼が行われる」のように
実際の操作に近いテスト。
• ユニット(単体)テスト=オブジェクトの振る舞いが期待されたもの通りかを検
証する。たとえばMyObj#to_sメソッドはStringのインスタンスを返すこと
を、いくつかのMyObjインスタンスに対してチェックする、など。
12年12月25日火曜日
12. よくある誤解2
• ×ユニットテストはModelのテストのこと・・・Controllerでレスポンスが
xx_pathにリダイレクトされる、などもユニットテストといえる。
• ×ユニットテストはホワイトボックステストである・・・ホワイト~, ブラック
~は次元の異なる概念。プログラム構造に対して網羅的となるテストがホワイ
トボックステストで、メソッド内の実装に踏み込まないテストがブラック
ボックステスト。
12年12月25日火曜日
13. ユニットテスト(続き)
• プログラムを利用する場合は、各オブジェクトにメッセージを送ってやり取
りをするルーティンのほうに関心があるから、オブジェクトがすべき仕事を
記述するユニットテストがTDD初心者には分かりにくい。
具体例 UsersController#createをルーティン
class UsersController < ApplicationController で考えると、@user.saveでDBに保
def create 存されるかどうかに関心を持ってし
@user = User.new(params[:user])
まうが、DBに保存されるかどうか
if @user.save
redirect_to user_path(@user) はModelの仕事であるから、
else Controllerは単に@user.saveがtrueを
render :new
返したらuser_pathにリダイレクト
end
end し、そうでないならnewをrenderす
end ることに注力すればよい
12年12月25日火曜日
14. テストファーストの最大の利点
• テストを全く書かない場合、人間の思考はオブジェクトやメソッドがある実
行コンテキストでどのような仕事をするかに集中しがち。
• ユニットテストから書くことで各オブジェクトの仕事や領域、インターフェー
スが明確となって疎結合なコードを書くことができる
• 受け入れテストから書くことで実装の可否の基準が明確になる=仕様書を書
くコストを別の資産に転換できる
12年12月25日火曜日
15. Cucumber(1)
• Railsほど厳格な規約はないものの、例えばUserというリソースに対するテス
トならfeatures/users/registration.featureのようにまとめると便利
• 自然言語で書かれたfeatureと、その内容をコードの動作に解釈する
step_definitionsが存在
• features/step_definitions/user_steps.rbにUserのstepをまとめると良い
• step_definitions内で使われるfill_inやclick_buttonなどのメソッドはwebratが提
供するもの。
12年12月25日火曜日
16. cucumber(2)
⃝features/users/registration.feature (フィーチャー定義)
もし メールアドレスはuser@example.com,パスワードはtesttest,ユーザ名はuser1で
ユーザ登録をする
⃝features/step_definitions/user_steps.rb(ステップ定義)
When /^メールアドレスは(.*),パスワードは(.*),ユーザ名は(.*)でユーザ登録をする$/ do |
email, password, name|
visit new_user_registration_path
fill_in(“user_email”, with:email)
fill_in(“user_password”, with: password)
fill_in(“user_password_confirmation”, with: password)
fill_in(“user_display_name”, with:name)
click_button(“Sign up”)
end
12年12月25日火曜日
17. cucumber(3)
• Cucumberのstep_definitionsで書かれるコードはDBにレコードを投入するも
のと、ブラウザの動きをシミュレートものの2種類が大半
• 前者はもちろんActiveRecordなどのDSLが使用可能だが、後者はwebratある
いはcapybaraが提供するメソッドを覚える必要がある(10~20種類くらい)
• 現在は廃止されたが少し前までwebrat_ja_steps.rbなどの汎用的ステップ定義
が提供されていた
• 廃止されたのはフィーチャー定義が冗長になる原因だったから。
12年12月25日火曜日
18. webrat_ja_steps.rb
#coding: utf-8
When /^”(.*)”ボタンをクリックする$/ do |button|
click_button(button)
end
When /^”(.*)”リンクをクリックする$/ do |link|
click_link(link)
end
When /再読み込みする/ do
visit request.request_uri
end
When /^”(.*)”に”(.*)”と入力する$/ do |field, value|
fill_in(field, with: value)
end
などなど...
12年12月25日火曜日
19. Cucumber(4)
シナリオ: 登録されていないmailとパスワードで新規登録
もし “ユーザ登録ページ”へアクセス
かつ “user_email”に”user1@example.com”と入力する
かつ “user_password”に”testtest”と入力する
かつ “user_display_name”に”user1”と入力する
かつ “user_password_confirmation”に”testtest”と入力する
かつ “Sign up”ボタンをクリックする
ならば “You have signed up successfully.”と表示されていること
冗長でわかりづらいし、再利用性に乏しい
(3つ前のスライドと比べてみよう)
12年12月25日火曜日
20. Cucumber(5)
• featureに対して正規表現でマッチしたstepが呼び出される=2つ以上にマッチ
してしまうとエラーになるので注意。曖昧なstepは書かない。
• 引用符付きのstepとそうでないものがあるが、基本的には単なる文字列の一
部なので好みの問題。囲ったほうが変数がわかりやすいかも。
• stepの中で別のstepを再利用したり、tableでレコードを一度に登録するなど
ができます。覚えてください。
12年12月25日火曜日
21. RSpec (1)
• Cucumberがアプリケーションの仕様であるのに対して、RSpecはプログラム
の仕様と考えるとわかりやすい
• モジュールやクラスがどのようなメソッドを持って、そのメソッドはある引数
に対してどのような結果を返すのか、などのプログラムレベルでのテスト
• 最小レベルのテストで、対象の振る舞いだけに集中することが重要。例えば
あるクラスをテストするときに他のクラスに処理を委譲する箇所があったと
して、そちらの実装を気にせずにテストするためにオブジェクトやメソッド
を偽物(Fake)化する、など
12年12月25日火曜日
22. RSpec(2)
• RSpecはRailsに限定されたものではない。Railsで使う場合以下のようなテス
トディレクトリが存在。(1)spec/controllers (2)spec/models (3)spec/helpers
(4)spec/views (5)spec/libs (6)spec/routing (7)spec/requests (8)spec/support
• requestはwebratなどを使用した受け入れテスト。Cucumberを使う場合はか
ぶるので要らないと思う。
• routingはconfig/routes.rbとイメージしているURLが合致しているかをテスト
するだけなのでカバレッジを気にしないなら不要かも。
• viewも頻繁に書き換わるところなので、書く必要はないかもしれない。
12年12月25日火曜日
23. RSpec(3)
• 逆に書くべきなのはspec/models, spec/controllers, spec/libs, spec/helpers。
• Controllerのspecでは、あるルーティングにHTTPリクエストを飛ばした時に
想定しているオブジェクトに想定しているメッセージを送っているかを検証
=モデルの適切なメソッドを(適切な引数で)呼び出しているか、responseに
flashは含まれるか、想定しているURLにリダイレクトしているか、などをテス
トする
• Modelのspecでは、定義したメソッドが正しい挙動をしているか、実際に
メッセージを送って戻り値を検証するのがメイン。例外の発生やファイルが
作成されるかなども検証できる。
12年12月25日火曜日
24. RSpec(4)
describe UsersController do
※stub(:save).and_return(false) describe “POST create” do
実際にsaveされるかはここでテスト describe “with invalid params” do
しない。偽のメソッドによってfalse it “re-render the ‘new’ template” do
を返させる。 User.any_instance.stub(:save).and_return(false)
post :create, {user: {name: “test”}}
response.should render_template(“new”)
※should テストの核の部分。これを end
エクスペクテーションとかアサー end
end
ションと言う。
end
12年12月25日火曜日
25. RSpec(5)
• 各種のマッチャを覚える必要がある。
• 基本的にはitブロックの中に1つのエクスペクテーションを書く。
• エクスペクテーションごとに共通化できる処理はメソッドにして呼び出す
か、beforeにまとめるなどが可能
• describe(context)は各テストをグルーピングできる。グルーピングによってテ
ストケースがわかりやすくなるほか、before/afterなどのフックをグループご
とに定義可能になってうれしい。
12年12月25日火曜日
26. RSpec(6)
• スタブとモックは本物の代用となるコード。
• スタブはインターフェースを提供するが、モックはエクスペクテーションも提
供する。
• つまり以下のコードで下は@user.nameが呼ばれないとテストが通らない。
@user.stub(:name).and_return(“hogehoge”) スタブ
@user.should_receive(:name).and_return(“hogehoge”) モック
12年12月25日火曜日
27. RSpec(7)
• double,mock,stubの概念とRSpecにおける語用には隔たりがあるので注意。
RSpecが提供するmock(Obj)とstub(Obj)はdouble(Obj)のエイリアスである。
• rspec-railsが提供するmock_modelとstub_modelという拡張がある。例えば
form_forヘルパーに対してどのように応答するオブジェクトを渡すべきか知ら
ずに済ませたい=>mock_modelを使えばActiveRecordオブジェクトの基本的
な応答をスタブ化できる
• mock_modelとstub_modelの違い=>stub_modelは実際のActiveRecordオブ
ジェクトを作成する。ただしDBとのやり取りを行わない(saveや
update_attributesを呼び出すとエラーになる)
12年12月25日火曜日
28. TDD実践編 今回作るアプリケーション
• Twitterのようなアプリケーションを作りましょう
• 認証(devise)を含むテストも書いてみます
• デザインは作る気はないので、flash[:info]やi18nなどはかなり適当にやります
• https://github.com/t-kot/tdd_on_rails
12年12月25日火曜日