単体テストは難しい

プログラミングを仕事とする場合、避けては通れない単体テスト。それは自分の実装に対して自動で動作確認を行う仕組みな訳だが、これが結構難しいのかな、という印象を持った。

仕事では、全てが自分のコードではない。既存の複雑に絡み合った実装の中に針を通すような改修を行うこともある。さて、その改修が正しいことを示すために単体テストを書こうとした時、まずはその単体テストできるための環境を作るのが大変だったりする。前提となるインスタンスを作成するために様々な依存関係を解決しなくてはいけなかったりする。まぁ最近はモックライブラリがかなり優秀なので mock[TargetClass] みたいにすればだいたいうまいこといくことが多い(いかないこともあるが...)。さて、問題はここからだ。単体テストの本質というか、ここからが書き手のセンスが問われる。つまり、自分は何を動作確認したいか、だ。

まず、これをするためには自分が何を動作確認したいかを適切に捉える必要がある。仕様をきちんと理解して、それを言語化する必要がある。加えて、言語化した情報をプログラミング言語に翻訳する必要がある。この部分は実装を書くよりもクリエイティブな作業になることが多い。改修作業は、求められる機能が決まっているのでブレることが少ないが、単体テストを書く時は自分が確認したい動作をきちんと捉えられているかどうか、もしくは自分がその問題をどのように捉えているのか、が如実に実装に反映される。改修時よりも余白があって、1つの正解、というものがない。

私は以前、単体テストといえばラインカバレッジを満たすためのものという感覚があったが、そういうホワイトボックステストは脆弱なテストと言われる。それは改修の余白を生まなくなり、改修時に単体テストも一緒に修正することになるので、デグレ確認としての単体テストが機能しづらい。そこから実装の余白を生むためにBDDのようなテスト技法が生まれたと思う。ラインカバレッジを重視するのではなく、仕様(機能の振る舞い:Behaviour)を重視する姿勢だ。ただ、これが既存実装に対して適用するのはホワイトボックステストよりも骨が折れる。なぜなら、仕様はコードに現れないことがあるからだ。もしくは改修に改修を重ねたコードが表現する仕様は、1つの仕様書としてまとまっておらず、複数の仕様書をパッチワークのように適用した結果できることがある。それを読み解いて、言語化し、プログラミング言語として実装する必要があるからだ。そんなのができるのはそのサービスにとても精通しているベテランしかできない。少なくとも2〜3日でできる技じゃない。

だから単体テストは難しい。単体テストを書くとはもはや実装を見ることではなく、仕様を掘り起こす作業になるからだ。仕様を把握して動作確認すべき箇所をピンポイントに言語化して実装する、これはプログラミング言語だけを勉強してても身につかない。そのサービスをちゃんと理解していないと書けない。難しい。

そういったものをチームで解決するためにコードレビューを通してベテランの知識を提供することは良いかもしれない。ただ、レビュワーに対しても同じだけの負荷、つまり仕様に対する背景等を持った上で、目の前のプログラミング言語で書かれた動作確認書を読む必要があって、なかなかしんどい。難しい。上手い解決方法はないものか...


最近は単体テストを書いていると、Copilotがかなり適切にサジェストしてくれるようになって、単体テストの実装を書く上ではかなり省力化された感じがする。Copilotがそのプロジェクトのコードを全て理解するようになれば、より適切な単体テストもサジェストしてくれそうだなという感覚もある。実装者は動作確認の言語化を適切にできる能力を磨けば良い未来が来るかもしれない。もしくはExcelやConfluenceとかで書かれた日本語での仕様書も全部読み取って、Copilotがその動作確認の言語化もサポートできるようになると、単体テストを実装する転換点になると思った。