意訳:「 Just Say No to More End to End Tests 」 - oomichi/try-kubernetes GitHub Wiki
要約
- e2e テストは Flake しやすい(テスト結果が安定しない)し、テストが失敗したときのデバッグが大変(調査対象コードが大量にあるため)
- Unit Tests、Integration Tests(2つ以上のUnitを組合わせたテスト) はこれらの欠点を解消する
- Unit: 70%、Integration: 20%、e2e: 10% くらいの割合でピラミッド構成にするとバランスが良いのでお薦め
https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html を意訳
期待して見に行った映画がクソだったり、「最高の機能だ」と思って商用リリースした機能が後で爆弾化した、なんてことは多々ある事だ。 良いアイデアはしばしば実際に失敗し、end-to-end テストに関するテスト戦略を構築することも高い確率で失敗する。
テスターは自分の時間を使って懸命に色々な種類の自動テスト、unit、integration、end-to-end テストを書くことが出来る。 しかし、この失敗するテスト戦略では end-to-end テストに多くの時間を投資する傾向にある。具体的にはそれらのテストは実際のユーザシナリオをシミュレートするものだ。
End-to-End Tests in Theory
end-to-end テストに最優先に品質的に依存することは悪い考えではあるが、人々にそれを良い考えだと説得することが出来てしまう。
- 開発者としては、テスト関連の作業を他の人に押し付けることが可能だからだ。
- 管理者や決定者としては、実際のユーザシナリオをシミュレートするテストが失敗するかどうかを見ることで、ユーザへの影響を確認できるからだ。
- テスターとしては、実際世界の振舞いを確認していないテストを書いたり、バグを取り逃したりする心配をしないで済むからだ。
End-to-End tests in Practice
もしもこのテスト戦略がセオリー上良いように聞こえるのであれば、実際に何が問題となるのだろうか? いくつかの経験を基にこの問題を説明しようと思う。
まずはじめに、開発チームがすばらしいテストインフラを持っていることを想定する。 それは毎晩
- 最新バージョンのサービスがビルドされ
- このバージョンはチームのテスト環境にデプロイされ
- 全ての end-to-end テストがテスト環境に対して実行され
- そのテスト結果のサマリがチームにメールで送られる
というものだ。 我々のチームが次のリリースに向けて新しい機能をコーディングしている間にも、その締め切りは近づいてくる。 高い品質を保つため、機能開発が完成されたと認められるには、彼らは少なくとも 90% のend-to-endテストが通過することを要求した。 現在、その締め切りは残り1日だったとする。しかし、ほとんど全てが壊れており5%しかテストが通っていない状況だったとする。 原因は、我々が依存していたパートナーチームが誤ったビルドを彼らのテスト環境にデプロイしていたことだった。 修正を行い54%までテスト成功率は上がったが HW故障がおきテスト環境が丸一日使えなくなってしまった。 大きなバグ修正によって84%まで改善したが、大きなバグの後ろに小さなバグが複数隠れており、修正に多くの時間を費やした(89%まで改善) 引き続き flake テストが残っており、90%は未達成。この時点で締め切りを7日間超過していた。
分析
多くの問題にも関わらず、テストは実際のバグを顕在化させた
何がよかったことだろうか
顧客に影響のあるバグを認識し、それをリリース前に修正できた
何が悪かったことだろうか
- 1週間遅れのリリースになってしまったこと(しかも多くの残業を行った)
- End-to-End テストが失敗する根本原因を見つけることは大変であり、多くの時間を要する
- パートナーの失敗、そして環境のHW故障は複数日に渡ってテスト実施を遅らせることになった
- 大きなバグの後ろに複数の小さなバグが隠れてしまった
- End-to-End テストは flaky (安定しない)ことが多かった
- 開発者は修正が正しいかどうかを1日待つ必要があった
上記の例のとおり、この e2e 戦略の何が悪かったのかは理解できたと思う。 これらの問題に対応するため、テストのアプローチを変更する必要がある。 何が良いアプローチだろうか。
テストの真価
一般的に、1つのテストジョブは1つの失敗テストがあると終了する。バグは報告され、そして開発者の仕事はそれを修正することになる。 e2e テスト戦略の何処が失敗したのかを認識するため、最初の原理に戻り考える必要がある。 つまり、「ユーザに集中すること」だ。そして我々は「失敗したテストがどのようにユーザに利益を与えることにつながるのか」を自分自身に問う必要がある。 以下が、その答えだ。
失敗したテストは直接にはユーザに利益を与えない
この分は少しショッキングかもしれない。しかし真実だ。 失敗したテストがユーザの利益につながらないのであれば、実際に何がテストの利益なのだろうか?
バグ修正がユーザへの直接の利益につながる
つまり、これだ。ユーザはバグが解消された場合のみ幸せを感じる。 もちろんバグを修正するためには、開発者はバグが存在することを認識する必要がある。 バグが存在することを認識するため、理想的にはバグ1つに対して1つのテストを持つ必要がある。 しかし全体のプロセスにおいて、失敗したテストからバグ修正を行うことが、価値を与える最後の方法だ。
テストが失敗しても、バグチケットを登録しても価値があるわけではなく、バグが修正されて始めてユーザの利益につながる。 つまり、テスト戦略を進歩させるには、単にバグの検出方法を改善するだけでなく、開発者がバグを修正できるようにプロセスを改善する必要がある。
正しいフィードバックループの作り方
テストは開発者に製品が動作するかどうかを伝えるフィードバックループを作る。理想的なフィードバックループはいくつかの性質を持っている。
- 速い 開発者は彼らの変更が動作するかどうかを知るために何時間、もしくは何日も待ちたいとは思わない。しばしば、それらの変更は動作しない(完全な人間などいないのだから)そしてフィードバックループは複数回実行される必要がある。フィードバックループが速ければ速いほど、修正される速度は速まる。もしもフィードバックループが十分に速いならば、開発者は1つの変更のたびにテストを走らせられることになるだろう。
- 信頼できる 失敗したテストが Flaky であったことを知るためだけに、何時間も時間を費やしたい開発者はいないだろう。Flaky テストは開発者のテストへの信頼を失わせ、そしてしばしば Flaky テストの結果は無視されることになる。その結果、実際の製品の問題も無視される状況になるのだ。
- 失敗を分離している バグを修正するため、開発者は問題を起こしているコードの行を特定する必要がある。製品が何百万行に渡るコードを含んでおり、バグがどこにあるのかわからないのであれば、草の山から針を見つけ出すくらい大変なことになるだろう。
小さく考える
さて、どうやって理想的なフィードバックループを作るのか。それは小さく考える必要がある。
Unit Tests
Unit Testsは製品の小さな部分を取り出し、それを分離してテストするものだ。それらは理想的なフィードバックループを作る傾向にある。
- Unit Testsは速い 我々は小さなテストのためのユニットを作るだけでよく、テスト自体も小さくなる傾向だ。実際、実行時間が10秒を超えるUnit Testは遅いと考える必要がある。
- Unit Testsは信頼できる シンプルなシステムと小さなユニットは一般的に Flakiness を小さく出来る傾向にある。
- Unit tests は失敗を分離できる 製品が数百万行に渡るコードを含んでいたとしても、そして一つのUnit Testsが失敗した場合、テストに対応する小さなユニットの中からバグを見つけることが出来る。
効果的な Unit Tests を書くためには複数のエリアに関するスキルが必要となる。依存管理、モッキング、Hermetic テスト(密封テスト、たぶん Air-gapped と同意)など。これらのスキルをここでカバーするつもりは無いが、最初に一つだけ、典型的な例を新しい Googlers もしくは非Googler に Google がストップウォッチのビルドとテストをどのように行っているのかお伝えしたい。
Unit Tests と End-to-End Tests
End-to-End テストにおいて、あなたは次のことを待たなくてはならない。
- 全ての製品がビルドされること
- それらがデプロイされること
- 全ての e2e テストが実行されること
テストが実行されたとき、Flakyテストは発生しやすい傾向にある。そしてテストがバグを検出したとしても、バグが何処にあるのか分かりにくい。
e2eテストがユーザシナリオをシミュレートする良い仕事をしたとしても、この利点はe2dのフィードバックループの欠点によって帳消しにされる。 つまり
- Fast: Unitのほうが勝る
- Reliable: Unitのほうが勝る
- Osolates Failures: Unitのほうが勝る
- Simulates a Real User: e2e のほうが勝る
ということだ。
Integration Tests
Unit Testは大きな欠点がある。たとえ Unit が分離された状態でよく動作しても、それらが組み合わさったときに正しく動作するかどうかがわからない。だからといって、対応するe2e テストが必要だというわけではない。Integration Testsを使う方法がある。 Integration TestはUnitの小さな組合せグループ(多くの場合は2つのUnitからなる)に対してテストを行うものだ。 もしも2つのUnitの組合せが動作しないのであれば、そもそも e2e テストを書く必要は無いだろう。小さなテストを行っていったほうが、同じバグの検出は容易になる。
テストピラミッド
Unit tests, Integration tests両方があったとしても、システム全体を確かめるために小さな数の e2e テストが欲しくなるだろう。 Unit, Integration, e2e のテスト種別の良いバランスを見つけるため、テストピラミッドを提案したい。 これはGoogle Test Automation Conference 2014の基調講演で発表したものだ。
ピラミッドの下から Unit、Integration、e2e で構成され、上に上がるにつれて1つ一つのテストの範囲は広くなる(合わせて実行時間も長くなる)が、テスト数は少なくなっていく。
Googleではそれぞれの割合を Unit: 70%、Integration: 20%、e2e: 10% くらいを提案している。実際の値は各チームによって異なるが、一般的にこのようなピラミッド型になる。これらのアンチパターンは避けたほうがよいだろう。
- アイスクリームコーン型 開発チームはe2e テストを最優先として依存し、Unit、Integrationの数は逆に少ない。
- 砂時計型 大量のUnit Testsを実装することから始まり、その後Integration Testsで実装すべきところを e2e テストで実装してしまうパターン。つまりIntegration Testsが少なく、砂時計型になってしまう。
正しいピラミッド型は実世界で安定した構造であり、それはテストピラミッドでも同様、安定したテスト戦略である。