先日、とあるテスト手法に関する技術ブログ記事を読んでいて、かなり違和感を感じてしまった点について、雑感というかコメントをしてみます。
天下のメルカリ様のブログ記事ということで、多少の表現の強さについてはご容赦くださいまし。
テストコードの改革を進めている話
https://engineering.mercari.com/blog/entry/20230623-f8bcbed100/
以下、インラインにて。
テストの粒度は一般に「単体テスト」「結合テスト」等と2つ程度に別けられることが多いかと思いますが、これらは実質的に大小関係の定義のみで実際に何をどこまで担保するのかについては人・組織によりバラつきがあります。
「単体テスト」と「結合テスト」は単なる大小関係などではなく、それぞれに検証すべき観点があり、明確な違いがあります。
どこまで担保するのかについてバラつきがあるのは、納期や人員リソースといったプロジェクトの問題であり、品質を確保する上で本来網羅しなければならない観点というものは必ず存在し、その上でどこを削るのか(例えば今回は時間がないので最大値・最小値の観点は外すなど)を決めるため、バラつきが発生するのです。
当然、削られた観点に関わる部分は品質が低いままリリースされますが、プロジェクト全体でこのリスクを共有し承知した上でサービスを提供するのです。
むしろプロジェクトのスケジュールはこれらを踏まえて見積られるべきで、プロジェクトの開始時点でテストの網羅率はすでに決定されているべきです。
私のチームのテストコードも例に漏れず主に「ユニットテスト」「E2Eテスト」が存在しますが、ユニットが簡素な代わりにE2Eで非常に多くのパターンを実装して網羅していたり、反対にユニットは充実している代わりにE2Eが非常に簡素であったりと統一感がありません。
「統一感」という言葉に違和感を感じます。
そこにはユニットテストの網羅率と E2E テストの網羅率が存在するだけであり、網羅率が低い場合にはそうである(プロジェクト全体的な)理由が存在するだけです。
本来網羅すべき観点はすべからく検証されるべきであり、どこを削るかはプロジェクトの都合により決定されます。
ユニットは個々の関数やメソッドを対象としたテストで、データや条件分岐の面で高い網羅性を持たせることを主眼においています。
(中略)
基本的な考え方としてはカバーする範囲が狭い低コストなテスト(個々の関数・メソッド等の粒度)ほど網羅性を高くし、反対に範囲が広く高コストなテスト(API・バッチ等の粒度)ほど網羅性を低くするピラミッド型としています。
ユニットテストの主な目的は論理矛盾(ロジックミス)や単純ミスをなくすことです。
開発者の意図通りに動作するかどうかが主眼になるため、ソースコードに基づいてテスト項目(テストコード)が作られます。
ここでは製品としての機能性や整合性は問われないため、ユニットテストだけを高い網羅率にしたところで、製品全体としての品質が良くなるわけではありません。
開発者の仕様理解誤りやインターフェースミス、タイミング制御不良などの障害はユニットテストでは検出できません。
品質を確保するためには、ユニットテストも結合テストも等しく網羅性を担保する必要があります。
範囲が広いテストで網羅性も担保しようとするとコードの複雑性が増してメンテナンスが困難になるだけでなく実行時間が伸びます。
ユニットテストでも同様にメンテナンスが困難です。
必要十分な網羅率を担保するために必要なテストコードの量は膨大であり、プロジェクトの寿命が長くなるにつれて増え続けます。
ソースコードがテストの観点と密接に関わっているため、本コードに修正が入ればテストコードにも修正が入ります。
修正が発生する率は他のテストよりも高く、リファクタリングですらテストコードの修正が必要になります。
テストコードの修正にかかる時間は、下手すると手動テストとほとんど変わらないかもしれません。
当記事は「どのような観点でテストすべきか」という視点が非常に弱いように見受けられました。
単体テストも結合テストも同じテスト、規模が違うだけ、みたいな認識がなされているようです。
実際は、単体テストは論理矛盾、結合テストは機能性、システムテストは製品妥当性の検証というようにざっくり分類されるかと思います。
また、テストコードによるリグレッションテストに単体テストを含めるのは効率が悪いので、すべてを含めるのではなく観点を絞って厳選したものだけにした方が良いかなという気がします。
少なくとも最初に現れるピラミッドは逆の形にすべきではないかと個人的には思います。
参考: 単体テスト(ユニットテスト)とは
参考: 単体テストのテスト項目の観点
参考: 結合テストとは
参考: 障害区分と障害原因(バグの分類)
コメント