9章
テストを書く目的=コストの削減
- バグを見つける
- 仕様書となる
- 動くものだからこそ、最新であることを保証する
- 設計の決定を遅らせる
- 暫定コードの動作を保証するので、現時点では超具体的過ぎるコードでも、今後リファクタリングできる
- 抽象を支える
- 設計の欠陥を明らかにする
テストは最小限にする。重複すると直すのが二度手間になるから。
適切に設計されていれば、オブジェクトは「ほかのオブジェクトから受け取ったメッセージ」と「ほかのオブジェクトに送られたメッセージ」によってのみ外部とつながる。
(内部では、内部同士「自身に送られたメッセージ」とつながっている / これは外部からは見れない)
なので、その外部とつながっているメッセージ(感覚的にインターフェースと言い換えてよさげ)のみテストする。
|
|
Foo.new.foo
のイメージ
受信メッセージがパブリックインターフェース
↑これに対してはテストする責任がある。メッセージの戻り値を表明(アサーション)する。
Foo
はBar#bar
についてはテストしないべき。
送信メッセージの種類
クエリ(質問)
副作用のない送信メッセージ。
戻り値のみが重要で、メッセージを送ったか送っていないかはどうでもいい。
→受け手オブジェクトの受信メッセージとしてテストすれば十分。
コマンド(命令)
副作用(ファイルの書き込み・データベースへの記録の保存・アクションが起こされるなど)のある送信メッセージ。端的に言うと「なにかが起こる」。
ちゃんとそのメッセージが送られたことは送り手オブジェクトの責任。
振る舞いのテストとして、メッセージが送られた回数と使われた引数をテストする(何が起こるかはテストしない / 受信側で)。
送られることに対してのみのテストなので、結合が緩くなる。
テストをいつ書くか
今でしょ!!
最初にテストを書くと、利用する側でコードを書くので再利用性を持たせることができる(not 適切な設計)。t_wadaさんがよく言ってること。
テストの種類
フレームワーク
- MiniTest
- RSpec
テスティング
- テスト駆動開発 - TDD(Test Driven Development)
- より内から外へ
- ドメインオブジェクトのテストから始まる。そこから隣接したオブジェクトのテストを書いていって広げる。
- だからDDDの文脈でTDDが出てくるのか(感想)
- 振る舞い駆動開発 - BDD(Behaver Driven Development)
- より外から内へ
- まだ書かれていないオブジェクトを用意するためにモックが必要。
- 外からこんなメソッドで呼ぶよ、どうなってる?どうなってる?と掘っていく(自分なりのイメージ)
雑に言うとxUnit系はTDDの思想、xSpec系はBDDの思想に基づいてます
by アジャイルおじさん
受信メッセージのテスト
受信メッセージには必ず依存するものがある(絶対にどこかから呼ばれている)。
呼ばれていないなら、そのメソッドは不要なメソッドなので削除する。
テストする項目
基本:考えられうる全ての状況において正しい値を返すこと。
|
|
このメソッドは別のオブジェクトが絡まっているが、外部からはWheel
を使っていることはわからない。テストからももちろんわからない。
→リスクになりうる(Gearが正しくてもWheelが壊れていたらダメ)。
依存を隔離する
|
|
このwheel
オブジェクトはDiameterizable
ロール(#diameter
に応える)を担う。ちなみに今のところはロールはWheelだけだし、コード上でWheel
とDiameterizable
の違いについてはわからない。
まあとりあえず、Wheel.new(rim, tire)
を注入する設計にしたことで複雑さが解消された。
メソッド名を変更したら……??
Wheel#diameter
をWheel#width
にしたら、Gear#gear_inches
のwheel.diameter
がエラーになる(当然)。
とはいえ、Wheelを用意するコストが高いとか、いろいろ具体が増えてきたならDiameterizable
を用意しよう。
テストダブルを作る
テストダブルはスタブとモックの両方を指す。場合によって、スタブにもなるしモックにもなるという認識。
|
|
スタブは「受信メッセージのテスト」のために使うオブジェクト。
「update
メソッド持ってますかー?呼んでいいですかー?」
スタブ「ええで」
|
|
モックは「送信メッセージのテスト」のために使うオブジェクト。
「name
はなーにー?」
モック「ほれ、名前
や。名前
しか返さんで」
|
|
使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita
とはいえ、ダブルを使っていると、実際の実装と変わってくるよね……。
プライベートメソッドのテスト
プライベートメソッドのテストはしない!!
- パブリックメソッドでそのロジックは使われているはずだから(そして、プライベートが壊れたらパブリックでも影響が出るはずだから)。
- プライベートメソッドは変わりやすいから。
- プライベートメソッドは内部のためのものなのに、外部が使いかねないから(?よくわからんかった。意味はわかるけど、privateは呼び出せなくね?Ruby以外の話?)
解決策
- プライベートメソッドを作らない
- プライベートメソッドになった時点で別オブジェクトに切り出す。
- まあゆーて中身変わっていないから、不安定なことは変わらない。
- パブリックメソッドの動作で担保する
(呟き)結局、パブリックメソッドに対して「〇〇のときは〜、〇〇のときは〜」ってやるのしんどいよなあ。
送信メッセージのテスト
|
|
-
クエリメッセージ(
wheel.diameter
)を無視する。
実行したとて、決まった返り値が返ってくるだけなので(そこはwheel
の方でテストする)。特にほかのオブジェクトにも依存していないので。 -
selfに送られたメッセージ(
ratio
)も無視する。
外に出ていくクエリメッセージも無視する。
コマンドメッセージのテスト
|
|
cog
やchainring
が変わったときにはobserver
にchange
を送るので、それをテストする必要がある。
changed
の戻り値については、observerの責任なのでGearがテストする必要はない。ただ、observerにchangedを送ったことだけテストすればいい。
モック
モックは振る舞いのテスト(←→状態のテスト)。
メッセージが送られればオッケー。
|
|
RSpecだとこんな感じになる。
|
|
使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」 - Qiita
ここで大事なのは、モックはあくまでも送ったことのみテストしていること。実際の中身はobserverの方でテストしてください案件。
必要なら返り値も設定できるが、それはただのおまけで中身はないようなもの。
依存オブジェクトを外部から注入していれば、テスト時にモックに置き換えるだけなので簡単。
RSpecだとinstance_double
を使えばいい。
ダックタイプをテストする
インターフェイスに対するメソッドをモジュールで作る。
それをコピペ的な感じでincludeする。
……受信のテスト
|
|
これをそれぞれのクラスのテストでincludeする。
あとは、prepare_tripを送信していることをテストすればいいので、モックを使う。
|
|
(省略)
RSpec における double / spy / instance_double / class_double のそれぞれの違いについて - Qiita