苦痛を感じさせない単体テストの書き方

# 初めに

私が関わっているプロジェクトでは単体テストを導入しています。
しかし、テストに対するモチベーションが上がらず悩んでいました。
そんな時にSandi Metzt著の[「オブジェクト指向設計実践ガイド」](https://www.amazon.co.jp/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E8%A8%AD%E8%A8%88%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%EF%BD%9ERuby%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B-%E9%80%B2%E5%8C%96%E3%81%97%E3%81%A4%E3%81%A5%E3%81%91%E3%82%8B%E6%9F%94%E8%BB%9F%E3%81%AA%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%82%B2%E3%81%A6%E6%96%B9-Sandi-Metz-ebook/dp/B01L8SEVYI/ref=sr_1_1?adgrpid=54056336555&hvadid=655037911179&hvdev=c&hvlocphy=1009240&hvnetw=g&hvqmt=e&hvrand=15275443394143517108&hvtargid=kwd-334790686516&hydadcr=27268_14669444&jp-ad-ap=0&keywords=%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E8%A8%AD%E8%A8%88%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89&qid=1683605544&sr=8-1)という名著に出会い、私のテストに対する考え方や取り組み方が変わりました。
今回はその本の内容の一部を自分なりにまとめてみようと思います。ただし、その本には書いていないことも所々付け加えていたりします。詳しくは実際に本を買って読んでみることをお勧めします。

:::note warn
あくまでこの記事は「書籍の内容の一部まとめ＋自分の考え」を書き記したものとなります。私の考えをまとめたものではありません。
:::

</aside>

# なぜテストは避けられがちなのか？

本書に興味深い一文があったので引用させていただきます。
> よくあるのは、テストを書きはじめたばかりのプログラマーが、書いたテストから得られる価値よりも、自身がテストを書くのにかかるコストのほうが高い、という満足のいかない状況に置かれていることに気づくことです。そして、それゆえにテストの価値について議論をしたがります。これらのプログラマーは、テストがない以前の生活では、自身の生産性は高かったと信じているものの、テストファーストの壁にぶつかり、つまづいて止まることを余儀なくされたのです。テストファーストでのプログラミングの試みが成果の減少という結果になり、生産性を取り戻したいという欲求が彼らを昔の習慣へと逆戻りさせます。そして、テストを書かなくなるのです。
> 

# 単体テストの価値

単体テストを書く前に、まずその目的や価値を認識する必要があります。

## リファクタリングをしやすくする

単体テストのないリファクタリングがいかに大変で危なっかしい作業であるかは容易に想像がつくのではないでしょうか？
テストがあればリファクタリング前後の挙動の一致を保証することができます。
もしテスト無しでリファクタリングした場合、実際にアプリケーションを手作業で動かしながら確かめるしかありません。これは時間がかかるしバグも見逃しやすいです。
また、不備を運よく発見できたとしても、テストがないためどこで不具合が発生しているか見つけ出すのに一苦労です。

## バグによる不利益を最小限に抑える（個人的見解）

### バグを早期発見できる

以下の理由から、基本的に発見が早ければ早いほど改修コストは小さくなります。
- 記述量や依存が多いコードのバグは発見しずらく修正もしにくいため
- バグへの依存コードが大量に生まれる前に対処できるため

### バグの原因特定がしやすい

どこが壊れてどこが正常なのかという問題の切り分けができるので、問題箇所の特定がしやすくなります。

### バグを減少できる

コーナーケースなどを明示的にテストコードに落とすことで、バグの減少に繋がります（境界値がバグ発生率が一番高い）

## 唯一信用できる設計の仕様書になる

テストが伝える説明は、紙の文書が過去のものとなり、人間の記憶がなくなってずっと経ったあとでも、常に真であり続けます。

:::note warn
他にも「設計への貢献」といった利益もありますが、今回は割愛させていただきます。
:::

# 費用対効果の高い単体テストの書き方

結論、「**テストは一度だけかつ適切な場所**」で実施することです。これは「テスト対象コード（SUT）と実際にテストコードを疎結合にする」と言い換えることもできます。

これを果たすことで、テスト運用のコストを下げることができます。
理由は以下の二つのようなことが挙げられます。

- テストから重複を取り除くことで、アプリケーション変更に伴う変更コストを下げられる
- テストを適切な場所で実施することで、間違いなく必要な時にのみテストが変更されることが保証される

では、これをどうやって実現させればいいのでしょうか？
テスト対象のコードを「メッセージ受信側」と「メッセージ送信側」の二つに分けて説明したいと思います。

`メッセージ受信側`
メッセージ受信側は、クラス外部に公開しているメソッドのみテストすることです。
理由としては、以下のようなものが挙げられます。

- 数と変更頻度が少なく、テストとSUTが密結合になるのを防げる
- メッセージの戻り値に対するテストを一箇所に留めておくことができる（重複排除）
- このテストだけで全体の正しさについて証明できる

具体的なテスト方法としては、戻り値の状態をテストしてあげれば良いです。

`メッセージ送信側`
メッセージ受信側は、副作用の発生するメッセージのみテストすることです。
副作用とは、ファイルの書き込みやデータベースへの保存、オブザーバーによってアクションが起こされるなどがそれにあたります。

理由としては、以下のようなものが挙げられます。

- 副作用の発生しない送信メッセージは、受けて側で返り値の状態をテストすれば済む
- 副作用の発生するメッセージは、アプリケーションの他の箇所に影響を与える可能性があるため、そのメッセージが適切に送られたかを証明する必要がある
    - メッセージが適切に送られたかの証明のみならば、結合の緩いテストになる

具体的な方法としては、モックを利用しメッセージが送られた回数と使われた引数をテストしてあげれば良いです。

# 参考文献

[https://www.amazon.co.jp/オブジェクト指向設計実践ガイド-～Rubyでわかる-進化しつづける柔軟なアプリケーションの育て方-Sandi-Metz-ebook/dp/B01L8SEVYI/ref=sr_1_1?adgrpid=54056336555&hvadid=655037911179&hvdev=c&hvlocphy=1009240&hvnetw=g&hvqmt=e&hvrand=15275443394143517108&hvtargid=kwd-334790686516&hydadcr=27268_14669444&jp-ad-ap=0&keywords=オブジェクト指向設計実践ガイド&qid=1683605544&sr=8-1](https://www.amazon.co.jp/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E8%A8%AD%E8%A8%88%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%EF%BD%9ERuby%E3%81%A7%E3%82%8F%E3%81%8B%E3%82%8B-%E9%80%B2%E5%8C%96%E3%81%97%E3%81%A4%E3%81%A5%E3%81%91%E3%82%8B%E6%9F%94%E8%BB%9F%E3%81%AA%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%82%B2%E3%81%A6%E6%96%B9-Sandi-Metz-ebook/dp/B01L8SEVYI/ref=sr_1_1?adgrpid=54056336555&hvadid=655037911179&hvdev=c&hvlocphy=1009240&hvnetw=g&hvqmt=e&hvrand=15275443394143517108&hvtargid=kwd-334790686516&hydadcr=27268_14669444&jp-ad-ap=0&keywords=%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E8%A8%AD%E8%A8%88%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89&qid=1683605544&sr=8-1)

# 最後に

私たちの会社、ナイトレイでは一緒に自社開発のWebサービスを盛り上げてくれるエンジニアメンバーを募集しています！ 
基本的には直接ユーザーと接することのないポジションですが、セールス部門から
「顧客の声」を教えてもらったり、希望すればユーザーとのMTGに参加することも可能です。
モチベーションの高め方はあなた次第。

このような方は是非Wantedlyからお気軽にご連絡ください（もしくはこちらまで recruit@nightley.jp ）

 ✔︎ 自社Webサービスの開発で事業の発展に携わってみたい
 ✔︎ 自分が開発したサービスで地域活性化に貢献したい
 ✔︎ 位置情報ビッグデータに興味があり、新しい活用方法を提案したい
 ✔︎ 地理や地図が好きで仕事中も眺めていたい 

一つでも当てはまる方は是非こちらの記事をご覧ください 
「受託開発一切なし！自社WEBサービスを社長やセールスチームと一緒に開発！」

 https://www.wantedly.com/projects/467606 

▼ナイトレイとは？

https://www.wantedly.com/companies/nightley