はじめに
「単体テストの考え方/使い方」という本を読みました。タイトルに違わず、考え方から細かい実装テクニックまで幅広い内容がカバーされており、非常に味わい深い本でした。
業務において、単体テストについて悩みがあって手に取ったのですが、知りたいと思っていたことが全部書かれていました。その中で特に印象に残った点について記録しておこうと思います。
印象に残った点が多すぎるので、特に印象に残ったことのみを抽出しています。なので短い記事です。
内容
テストの分類について
「単体テスト」とは、以下の3つの性質を全て満たすテストのことです。
- 1単位の振る舞いを検証すること
- 実行時間が短いこと
- 他のテストケースから隔離された状態で実行されること
このうち1つでも満たさない性質があればそれは「統合テスト」に分類されます。なので、SpringBootTestなどを使って複数のDB操作もまとめて実行するようなテストは単体テストではなく統合テストになります。私はこれを「単体テスト」だと思っていたので、この時点で勘違いしていました。
統合テストがダメなわけではなく、統合テストのほうが単体テストよりもデグレ検出に強いというメリットがあります。一方で、テストの実行が遅い、コードが膨れ上がりやすいのでメンテナンスコストが高くなる、といったデメリットがあります。
基本的には単体テストで多くテストし、統合テストではハッピーパスと、単体テストではテストできなかった異常系のテストのみに絞るのが良いようです。
私は、統合テストばっかり書いていたので統合テストのデメリットが直撃し、それで悩んでいました。どうやらこのあたりの話を理解していなかったのが原因だったようで…
コードは負債
重要なのは「多くのテストを書くこと」ではなく「良いテスト」を書くこととそれに絞ることであり、質の悪いテストなら書かないほうがいいです。コードは資産だと考えていると、質が悪くともないよりはあったほうがいいと考えがちです。しかし実際には、コードが存在するとそれに対するメンテナンスコストが発生するので、コードは資産ではなく負債と考えるべきでした。
負債である以上、それを超える価値をもたらすテストのみをコードベースに含めるべきです。コストを下回る価値しかもたらさないテストはマイナスなので書くべきではないです。
コードをテストするのではなく、観察可能な振る舞いをテストする
私がテストを書こうとすると、ホワイトボックスみたいな感じで「コードをテストする」発想になりがちだったのですが、これだとテストが実装の詳細に依存してしまいます。このようなテストはリファクタリング、つまり観察可能な振る舞いを変更しないコード改善のような修正でも壊れてしまいます。
テストのあるべき姿は、テストの失敗はバグの検出とイコールであることです。バグが埋め込まれたのにテストが失敗しないのは当然問題ですが、バグがないのにテストが失敗するのもまた問題です。そのような状態ではリファクタリングの意欲が削がれますし、バグがないのに失敗するということが繰り返されるとテストの失敗が軽視されるようになるという問題もあります。テストの失敗が軽視されると、バグがあることで失敗したテストがあったとしても、いつもバグがないのに失敗してるから今回もそうでしょ、とバグを見逃すことにつながります。
ドメインにおける重要性が高いテストをテストする
ドメインというのが私はまだよくわかっていないですが、要するにビジネス上の要求に直結する箇所ほどテストする価値が高いです。少なくとも、テストする価値が高い箇所、テストする価値は多少あるけど重要性が高くはない箇所、テストする価値が全くない箇所、などに分類できるのは想像できます。テストを書くべきなのは、テストする価値が高い箇所のみです。
前述のとおりコードは負債なので、テストする価値も多少はある、程度だとメンテナンスコストに見合わない可能性があります。その場合は、それに対するテストは書かないほうがいいということになります。
単体テストの可読性について
テストコードにもメンテナンスコストはかかるので、可読性は重要です。そのためには、一つのテストケースでは一つのことしか検証しない、条件分岐を使わない、長いコードになる場合はヘルパー関数に切り出すといったことに気をつけます。
またテストメソッド名の命名について、テストメソッド名の中にテストから呼び出すメソッド名を含めるようなことをするべきではないです。それは観察可能な振る舞いではなく、コードをテストとする発想です。「どのメソッドをテスト対象としているか」ではなく、「どのような振る舞いをテストするのか」で命名する必要があります。
(このあたりも反省点が非常に多い…)
おわりに
他にもモックの考え方や使い方、良いテストにするためのリファクタリングの実践例、DBのテストに対する考え方やコードの書き方、など多くの知見がありました。知見が多すぎて消化しきれていないので、折に触れてまた読み返していこうと思っています。
今まではとにかく全部テストしようと考えていたので、ドメインにおける重要性が低くてもテストを書いていたし、単体テストではなく統合テストとしてドカッとテストしてしまっていたり、いろいろアンチパターンを踏んでしまっていたんだなあと少ししょんぼりしました。
単体テストではなく統合テストばかり書くことになってしまっていたのは、テストすべきこととすべきでないことの区別をつけず全部テストしようとしていたからでもありますが、そもそもプロダクションコードが適切に分割されていなかったからでもあります。区別してテストすべきと思っていなかったことで、プロダクションコードもそれに適したコードとなっていなかったです。
しかし、この本のおかげでやるべきことがある程度わかったように思いました。テストコードも直す必要があるけど、そもそもプロダクションコードのリファクタリングからですね。