オブジェクト指向設計実践ガイド 第9章前半 「費用対効果の高いテストを設計する」 


## はじめに
- この資料は社内勉強会ように用意したまとめスライドです
- [オブジェクト指向設計実践ガイド](https://www.amazon.co.jp/dp/B01L8SEVYI)第9章前半（9.1~9.3）の内容の要点をまとめています

---
## もくじ
- テストを書く目的
- 意図を持ったテスト
- 受信メッセージをテストする
- プライベートメソッドをテストする

---
## テストを書く目的
変更可能なコードを書くには三つのスキルが必要

1. オブジェクト指向設計の理解
2. コードのリファクタリングに長けている
3. 価値の高いテストを書く←今回の章のテーマ

- テストがあることでフィラクタリングを継続的に行える
    - 変更を行うコストを削減する
- 効果の高いテストとは？
    - コード全体の変更コストをあげない
    - 逆にテストのメンテナンスや記述コストが高いのであれば存在価値は低い


---
## 意図を持ったテスト1
テストを書くことにより得られる効果
→下逆にこのような効果のあるテストを書く

1. バグを初期段階で発見できる
    - バグを初期段階で発見することで、変更コストが引くたいタイミングでバグ修正ができる
2. 仕様書となる
    - テストはコードに対して常に真であり続けるドキュメントとなる
3. 設計の決定を遅らせる
    - インターフェースに依存するようにすることで、内部実装は後から自由に変更できる
4. 抽象を支える
    - 抽象度を高めてもテストがあることで動作の保証をすることができる
5. 設計の欠陥を明らかにする
    - 設計のまずさはテストの十分条件
    - 複雑なテストを求められるのであれば設計もまずい可能性がある

---
## 意図を持ったテスト2
何をテストするのか？

- テストはオブジェクトの受信と送信に対して検証する
    - 内部の実装に結合しない→結合するとリファクタリングのたびにテストの修正も必要になる
    - 受信: 自身のオブジェクトのメソッドが呼び出されること
    - 送信: 他のオブジェクトのメソッドを呼び出すこと
- 送信メッセージの種類
    - クエリ型: 副作用のないメソッド
    - コマンド型: 副作用をもつメソッド

---
## 意図を持ったテスト3
- 送信メッセージの種類ごとのテストの責務
    - クエリ型: 
        - 受信側のオブジェクトでテストをするのみでOK
    - コマンド型: 
        - 受信側のオブジェクトでテストをする
        - 送信元で送信したかのテストを行う（具体的な副作用のチェックは行わない）
- テストを用いた開発様式
    - 振る舞い駆動設計
        - アプリケーションの振る舞いをモックを用いて作成し、モックとして用意したものをオブジェクトとして実装、テストしていく
        - トップダウン
    - テスト駆動設計
        - 必要とるなるメインオブジェクトのテストをかき、それより上のレイヤーのテストと振る舞いを実装していく
        - ボトムアップ
- テストはテスト対象のオブジェクト以外のオブジェクトに対しては出来るだけ無知であるように実装する

---
## 受信メッセージをテストする1
- どこからも呼ばれていないメッセージは削除を行う
- 逆に呼び出されているときはテストの実装を必ず行う

---
## 受信メッセージをテストする2
- 受信メッセージはその実行によって戻される値や状態を表明することでテストされる

```rb
Rspec.describe Wheel do
  describe '#calculate_diameter' do
    it do
      wheel = Wheel.new(26, 1.5)
      assert_in_delta(29, wheel.diameter, 0.01)
    end
  end
end
```

---
## 受信メッセージをテストする3
- テストを書いたときに、対象オブジェクトが他のオブジェクトに依存するようになっていたら設計を見直す必要がある

```rb
Rspec.describe Gear do
  describe '#calculate_gear_inches' do
    it do
      gear = Gear.new(
                    chainring: 52,
                    cog:       11,
                    rim:       26,
                    tire:      1.5)
 
      assert_in_delta(137.1, gear.gear_inches, 0.01) # Gearの内部でWheelをnewしている
    end
  end
end
```

---
## 受信メッセージをテストする4
- DIを使ってリファクタリングする
    - GearにWheelを外部から注入する
   
```rb
Rspec.describe Gear do
  describe '#calculate_gear_inches' do
    it do
      gear = Gear.new(
                    chainring: 52,
                    cog:       11,
                    wheel:     Wheel.new(26, 1.5))
 
      assert_in_delta(137.1, gear.gear_inches, 0.01)
    end
  end
end
```

---
## 受信メッセージをテストする5
- 全ページのテストにはまだ課題がある
- Wheelのメソッドを書き換えるとGearのテストが落ちる
    - Wheel#diameterの名前を変えるとGearのテストも落ちる
- 結果的にGearはテストでWheelに依存してしまっている

---
## 受信メッセージをテストする6
- テストダブルを用意する
    - Gearが`wheel`に求めるインターフェースをスタブする
- テストダブルをただ用意するだけだと、テストが意味のないものになる
    - Wheelのインターフェースを変更してもGearのテストは通り続けてしまう→本番でクラッシュする
- ロールとして切り出し、それを用いてテストの文章化を行う
    - 続きは9.5章
- ここで重要なのは、テストダブルに対するテストも必要であるということ
    - 意図したロールのインターフェースを満たしたものになっているか
- 実際のオブジェクトを注入するか、テストダブルを注入するかは実装者次第

---
## プライベートメソッドをテストする1
- 基本的にプライベートメソッドのテストは行わない
    - プライベートメソッドはそのオブジェクトのパブリックインターフェースから参照される
    - パブリックインターフェースのテストさえ行えればプライベートメソッドについても担保される
    - プライベートメソッドはとても不安定なため、テストの変更コストも大きくなる
    - プライベートメソッドのテストを用意することでカプセル化を破壊することになる→プライベートメソッドがドキュメントとして残ってしまう

---
## プライベートメソッドをテストする2
- プライベートメソッドを出来るだけ作らないようにする→プライベートメソッドが増えてきたら別のパブリックインターフェースとして外に切り出す
    - 切り出すインターフェースが安定しているのなら可能
    - 無理やりプライベートメソッドをパブリック化しても課題は何も変わらない
- 例外的にプライベートメソッドのテスト行わなければならないときは、前ページで述べたデメリットを覚悟して行う

---
## おわり


---
## 参考文献
- https://yu8mada.com/2018/06/23/how-to-calculate-fibonacci-numbers-in-ruby/
- Sandi Metz, オブジェクト指向設計実践ガイド