背景
コードの品質を高めるため、CI(Continuous Integration) が提言されてから久しくなりました。 いまやオープンソースのライブラリや個人レベルの開発でも Travis CIやJenkinsなどのCIツールが導入され、そのCIツールでちゃんとテストが成功したコードでなければ、ライブラリに追加しないという開発手法が標準になりつつあります。
しかし、すべてのコードに対してテストを書くのは(良いとはされるものの)現実的には時間や人手が足りなかったり、またコードによってはテストが非常に書きにくいものがあります。 そこで、実際のところテストってどこまで実装すれば良いんだろうと点について書きたいと思います。
実行結果によるリスクを減らすためにテストする
例えば、個人で開発していて自分しか使わないコードにテストコードを書くべきでしょうか? もちろん、そのコードをテストしなかったばかりに、実行したらPCが壊れる可能性があるなら、テストを書く必要があります。 しかし、そんな危険なことをしない限り、テストコードを書くことはあまりないと思います(デグレードしない、設計を整えたいために書くことはあるかもしれませんが)。なぜなら、実行結果の影響範囲が自分だけで、しかもその変更が軽微(リスクがほとんどない)のに、追加の実装であるテストを書いてもほとんど意味がないからです。
では、ロケットの打ち上げを行うプログラムにテストコードを書くべきでしょうか? 一度の打ち上げに何十億とかかるプログラムにテストがされていないということは考えられません。もし打ち上げに失敗したら、金銭・時間的な面でのリスクが大きすぎるので、それを減らすためにテストコードの実装や検証を行います。
つまり、__テストをどこまで実装するかは、実行結果によるリスクに応じて変化する__ということになります。よって、境界値チェックや C0/C1/C2カバレッジをどこまで行うかという議論よりも前に、「そのコードが動かなかったらどういうリスクがあるんだっけ?」ということが決まらないとどこまでテストすべきかは決まりません。
テストを書くことのリスク
このとき大切なのは、テストを書かないことのリスクだけではなく、__テストを書くことのリスク__もあるということです。納期が3ヶ月後のプロダクトでとても細かいところまでテストを実装していた結果、納期が3ヶ月遅れになり、賠償請求がされたりビジネスチャンスを逃すというリスクもあるわけです。
これらの判断はプロダクトマネージャレベルで定義できれば良いのですが、実際は現場のエンジニアが頭の中で定義していることが多いように思います。
落ちたら怖い(リスクの高い)箇所を実装する
では、具体的にはどうやってテストを実装すればよいのでしょうか。まずはコードの中でも「ここが落ちたら全体に影響がある、必須な機能が使えなくなる」という箇所を集中的にテストします。この箇所は特に境界値やC2カバレッジ、複数のデータパターンを使ったテストコードで確認してもよいです。
つまり、テストコード自体もそのリスクに応じて実装量が変わりますが、テストコードの中でもその関数が使えなかった際のリスクに応じて実装する量を変えます。