77-偶然の仕様ではなく本物の仕様のためのテストを書く

プログラマが知るべき97のこと」の77個目のエピソードは、テストに関する話です。自分にとって、このエピソードはきのこ本の中で最も重要で、今後実践していく課題の1つです。テストは、ソフトウェア開発を進めていく中で、避けられない一番の問題です。そして、最も難しいものです。テストというものは、学べば学ぶほど難しいものであると感じます。
テストが難しい最大の理由は、テスト自体が目的によってたくさんの種類があり、それぞれの状況で適切に使い分ける必要がある、という事です。このエピソードで扱うテストは、主にユニットテストから結合テストあたりまでのプログラマが主導で行うテストですが、それ以外にも品質保証に関するテストなどもあります。ユニットテストに関しては、JUnitなどのテスト手法を使うことで品質があがると雑誌などで知り、開発に組み込んだ組織は多いでしょう。しかし、実際にユニットテストが効果的に作用している組織は多くはありません。ほとんどの組織では、このエピソードにある「偶然の仕様」のためのテストをしているため、ユニットテストが自分たちの首をしめている事もあります。

実装コードには、元々の設計でそうしようと意図したわけではなく、実装の都合でたまたまそうしている、という部分が多くあります。そういう、「偶然の仕様」までテスト対象としてしまうのは問題なのです。

「偶然の仕様」、すなわち詳細な実装に深く依存したユニットテストは非常に脆いものです。酷いものになるとprivateメソッドまでテストを行うことがあります。しかし、そのprivateメソッドは偶然に出来たものでしかないのです。仕様変更の度にユニットテストのコードに大量の修正が入りメンテナンス不能になります。また、カバレッジ信仰もやっかいな代物です。カバレッジを高くする目的でホワイトボックステストを行い、気付くと目的と手段が逆転しています。どちらも、ユニットテストの本来の目的を見失っています。

テストはきめの細かい、厳密なものでなくてはなりませんが、同時に、本当にテストすべきことは何かをよく考え、的確なものにする必要があるのです。

ユニットテストの目的は、コードの品質を高めることではありません。ユニットテストを行うことにより、そのクラス(モジュール)がどのような振る舞いを期待されているかと、それが正しく実装されているかについて担保されます。したがって、期待される振る舞いが間違っているのであれば、テストが通ったとしても、適切なユニットテストではありません。
「本物の仕様」は適切な設計によって生まれます。これは、言い換えると、ユニットテストの対象はコード本体ではなく、プログラムの設計であると言うこともできます。そのモジュール(クラス)が適切な粒度で、適切な責務を持ち、適切なAPIを持つ事がテストされるのです。このような背景からテスト駆動開発を行っていけば、「偶然の仕様」にテストを行う事は減る事が期待できるでしょう。

テストを効果的なものとするには、実装で生じた偶然の仕様を確認するのではなく、あくまでコードが元々の要求にあっているかを確認すべきです。それにはブラックボックステストが有効です。テスト対象のコードの中身ではなく、外から見た動きに注目してテストを書くのです。動きが元々の要求に合っているかを見るのです。

プログラムはユーザの要求を満たすために作られるものであり、プログラマの自己満足で書くものではありません。コードが書けるだけでも設計ができるだけでも「77-偶然の仕様ではなく本物の仕様のためのテストを書く」は実現するのは難しいのです。要求分析や基本設計、テストにいたるまでソフトウェア開発全体を深く学んでいく必要があります。本物のテストを書けるようになったと自信を持って言えるようになったならば、プロあるいはエキスパートとして名乗ることが出来るのかもしれません。そのためには、継続して学習していくしかありません。

プログラマが知るべき97のこと

プログラマが知るべき97のこと