単体テストの目的

※「単体テストJUNIT 等を使った自動テスト」という意味で書いている。


単体テストを書くことに意味があるのか?という話題は定期的に繰り替えされてるように思う。というかうちの職場では繰り返されている(あくまで個人の愚痴レベルとしてだけど)


職業プログラマで無いなら、意味があるか無いかは自分で判断すればいい。あると思えば単体テストを書けばいいし、無いと思えば書く必要はない。


職業プログラマであるなら、単体テストを書くかどうかは各人の判断ではなく、規約による。規約で単体テストが要求されない、納品物に含まれもしない場合、単体テストを書くのか。書かないのが基本だろう。時間や成果物に対してお金をいただく職業プログラマが要求されないものを作成してしまうのは職業倫理的には問題もある。


しかし、納品せずに自分の懐に置き実行するだけのためにでも単体テストを作った方がかえって作業効率が良い場合もあり、そういう場合には勝手に作る人も居るに違いない。職業倫理的にも問題はないんじゃないか。たぶん。誰が作業効率が良いことを証明できるのか?というのはシランけども。


・・・と別に職業倫理の心配なんてしなくてもこのご時世、大抵の職場では単体テストは規約で要求されているはず。


大抵の職業プログラマは望むと望まざるとに関わらず、単体テストを書くと思っていいと思う。


で、単体テストがオールグリーンのコードを結合すると、うまく動かない場合がままある。これは当然で、環境設定の問題であったり、テーブル定義の不備であったり、想定している入力値違い(これは単体テストの問題である場合もあるだろう)であったりと、単体テストで検証していないことはいっぱいあるのだ。


結局単体テストというのは「単体に対して」「想定している範囲の入力に」「相応する出力を返すか?」ということをチェックしているだけなのだ。


2重3重にテストし、フェイルセーフが実現されているはずの原子炉に事故がまれに発生してしまうのは、テストが「想定している範囲の地震」とか、「想定している範囲の人的なミス」に対して、セーフに倒れるか?をテストしているに過ぎないからだ。「震度7でも15でも、どんな地震が来ても安全です!」とは誰も言ってないのだ。「想定している範囲内の地震が起きても安全です!」と言っているのだ。


つまり、結合テストで障害が発生するかどうかで、単体テストの意味を図ることはできない。


「なんだ、結合で問題が出るなら単体テストなんて意味ないよ」


うーん。例えが適切かどうかは分からないが、一生一緒に幸せに暮らそうね、と誓い合ったカップルが居たとする。夫は妻をいつも気遣い、子宝にも恵まれた。ところが、大戦が勃発し、夫の元に召集令状が。夫は戦地で帰らぬ人となった。


妻を気遣うことは単体の範囲であると言える。しかし、召集令状が届くことを想定していなかったのは夫のケース漏れだったのだろうか?まあ、場合によっては想定できるかもしれないが、この件で夫を責めるのは酷なんじゃないか?・・・あれ、この例えいまいちすか?


まあいいや、ともかく、単体テスト結合テストで問題が出ないことを目的として行うものではないということだ。結合テストで問題が出ないようにするのは結合テストでやればいいし、予防的には単体同士のインターフェイスの定義の確認を行うことだろう。


じゃー、単体テストの目的って?


メンテナンス性だと思う。単体テストはその単体の品質をある程度保証するためのものであるのは確かだけど、そのためだけに行っているとしたら無駄の多い行為で、それに疑問を感じるのはもっともだと思う。結合のタイミングで単体同士を結合して一気通貫でテストしたほうが効率が良かったりすることなんて普通にある。


しかし「結合テスト」で単体というレベルでのメンテナンス性は向上しない。将来のメンテナンス性を担保できるのは単体テストなのだと思う。*1


メンテナンス性というのは、リファクタリングや仕様変更、仕様追加がしやすいということだ。「単体テストさえちゃんと書いてあれば、単体テストが通る限りにおいて、大胆にリファクタリングしても大丈夫」というような XP (エクストリームプログラミング)なんかでよく言われたやつだ。


ほんとうか?理想論じゃないの?


・・・確かに理想論なんだけども、テスト対象となる「単体」が、


(1) カバレッジが高い
(2) 小さく簡潔
(3) リエントラント


である場合、限りなくそれはほんとうに近いと思う。「カバレッジ」とか、大雑把な言い方なんすけど。まあ、細かいこと言い出すと長くなるんでざっくりとくくって言ってます。


で、特に(3)は重要だと思う。リエントラントの意味はリエントラント - Wikipediaあたりを見ていただくとして、この3点を満たすように書かれた単体がメンテナンスしにくい状態が逆に想像しにくい気がする。


うちの職場でのことを言うと、(1) カバレッジばっかり意識されている気がする。いくらカバレッジが高くても、スパゲッティーな巨大メソッドは修正困難なのだ。リエントラントでなければ、先日テストされ、オールグリーンだったはずのテストケースは今日動かしたらまっかっかなのだ。まっかっかのテストケースを拠り所にリファクタリングができるだろうか?できるわけない。


小さく簡潔であることはカバレッジを上げたり、リエントラントにするための難易度に密接に関わってくる。また、単体テストでは通常、マルチスレッド下での動作についてはなかなかテストしにくいが、リエントラントであればそこは心配する必要が無くなる。他にもリエントラントであることのメリットはいろいろあるが、まあそこは略。


カバレッジばっかり重視しても、メンテナンス性が確保されない」と言われるのはこういう理由からだと私は思う。要するに片手落ちなのだ。


カバレッジが高いのに、メンテナンスもリファクタリングもやりにくい!カバレッジなんて意味がないんだ!


それは違うということだ。カバレッジには意味がある。ただしそれだけじゃなく、他の2点を満たさない限り目的は達せないということに過ぎない。この3点を満たした設計なり、実装なりをするのは(特にリエントラントに関しては)それなりに経験は必要だろうと思う。だろうとは思うけど、だからっていって、全部投げることないんじゃない?100%メンテしにくいコードより、50%メンテしにくいコードの方がいいと思うんだけど。


まとめると、


単体テストの目的は「メンテナンス性」。
・手段(方法)は (1)カバレッジが高い(2)小さく簡潔(3)リエントラント。
・でも満たすのは結構大変なんで、100か、0か、じゃなくてできる限りでやりましょーよ。


というところっすかねぇ。あれっす。なんか偉そうに書いてるわりに自分も全然出来てないんだけども。うん。

*1:逆に言うと、作り捨てのシステムで単体テストを作り込むのはあんまり意味がない場合が多いと思う。