自動テストに対して信頼がだんだん失われていく時とは

和田卓人氏:第2回は偽陽性と偽陰性というような話をしていきます。

我々はコードとしてテストコードを書き日々そのテストを回しながら開発をして、お客さまに価値を迅速に提供しようとするような時代を生きています。

そういった時に、その自動テストの一つひとつが信頼できるかどうかもとても大事になるわけです。自動テストがあるけれど、その結果が信頼できないと(なると)、自動テストの旨味みたいなものがだんだんなくなってきてしまうわけなんですよね。

じゃあ自動テストに対して信頼がだんだん失われていく時はどういうような場合なのかということで、『LeanとDevOpsの科学』という有名な本があります。

この『LeanとDevOpsの科学』というのは、ソフトウェアの開発に関わる一連の能力、例えば自動テストを書けるとか、リファクタリングができるとか、デプロイの自動化ができるとか。そういったいくつかのケイパビリティがそのまま企業の業績、例えば株価とか市場占有率とか、顧客満足度とか、従業員満足度とかに対して統計的に有意なかたちで影響を与えていることを明らかにした有名な本なんです。

その65ページにこんなことが書かれています。「テストの自動化において、ITパフォーマンス、つまり業績の予測尺度となり得ることが判明したのは次の2つである」「1つ、信頼性の高い自動テストを備えること。2つ、開発者主体で受け入れテストを作成・管理し、手元の開発環境で簡単に再現・修正できること」。

この1つ目の「信頼性の高い自動テストを備えることって何だ?」という話になってくるんですよね。

「信頼性の高い自動テストを備える」とは

信頼性の高い自動テストを備えるというのはどういうことか。信頼性が高いとは、簡単に言うと、一連のテストを全部実行した時、その実行したテストの結果、成功と失敗のどちらも信頼できるということです。

テストが全部成功していれば、そのソフトウェアは今すぐデプロイ可能、リリース可能である。テストが失敗している時はデプロイ可能ではない。どこかに不具合がある。このようにチームが確信できるようなテストを実施していること。これが信頼性の高い自動テストという考え方です。

この信頼性が蝕まれるのはどういう時かというと、テストが失敗しているにもかかわらず、別にプロダクトコードが悪いわけではなかった。つまり誤検知ですね。

例えばテストを火災報知器に例えてみるとしましょう。そうすると、火が出ていないにもかかわらず火災報知器が鳴る。これが偽陽性、false positiveですね。それに対して火が出ているにもかかわらず火災報知器が鳴らない。これが偽陰性、見逃しです。

この2つが多いと「テストを書いてもな」「実行してもな」「いまいち目で見ないと信じられないし」みたいな感じになってくるわけです。

じゃあそのテストにおいて誤検知とはどういうものがあるかというと、例えば不安定なエンドツーエンドテスト。よくありますよね。Seleniumとかで動かしているエンドツーエンドテスト。

裏で実際にブラウザを動かしながら実サイトにアクセスして動かしたりするので遅かったり。例えば次の画面に遷移するのを一瞬待ったり、どこかをクリックして画面に要素が出てくるのを一瞬待ったりするところが、たまたまCIのビルドのマシンのパフォーマンスの影響で、待ちがちょっと足りなかったためにテストが失敗してしまうみたいなことがよくあるわけですよね。

そうなると、テストが失敗しているのにもかかわらず、コードを見に行ってもどこも悪くはないわけです。そういった(テストを)業界では、「信頼不能テスト」「flaky test」と呼んだりします。flaky testとは何かというと、コードにまったく手を触れていないにもかかわらず、成功したり失敗したりするテストのことです。このflaky testというのも、誤検知、偽陽性の大きな原因になります。

さらにテストが空振りしてしまう。テストを動かしていると思ったらテストはスキップしていたとか。そういったようなものが、見逃しや偽陰性につながります。

なので偽陽性と偽陰性をどうやって減らしていくか、どれだけ安定して信頼できるテストを運用していくか。だから(それを叶えるために)メンテナンスをしなきゃいけないんですよね。より安定して実行できるようなテストにしていかなきゃいけないということで、継続的な努力と投資が必要になってくるわけです。

どのような時に「偽陽性」「偽陰性」になるのか

(スライドを示して)ということで、今言った偽陽性と偽陰性というやつを2×2の指標で整理してみると、こんな感じになります。プロダクトコードが正しくてテストが成功するのは期待どおりですよね。プロダクトコードに誤りがあってテスト結果が失敗するのも期待どおり。

問題は偽陽性と偽陰性。プロダクトコードが正しいにもかかわらず、テストに失敗しちゃう。これが偽陽性です。これを減らしたい。あるいはプロダクトコードが誤っているにもかかわらず、テスト結果が正しい。これは偽陰性、見逃しです。これも手強い感じなんですよね。

先ほど言いましたが、偽陽性の原因は信頼不能テスト(flaky test)と、あとは脆いテスト。脆いテストというのは、モックオブジェクトとかをジャブジャブ使ってしまって実装にぴったりと寄り添ったような、実装の内部の詳細にかなり深く依存したようなテストを書いてしまうと、ちょっと実装を変えただけでもすぐ失敗するテストになっちゃうんですよね。

インターフェイスレベルでは変わっていないにもかかわらず、内部がちょっと変わっただけで敏感なテストはすぐに失敗しちゃう。こういうのを脆いテスト、brittle testとかfragile testと言ったりします。この2つが偽陽性の大きな原因です。

偽陰性の原因は、例えば空振り。これは「テストを動かしていると思ったら@testが付いていませんでした」とか「特定のファイルをスキップしていました」みたいな見逃し。あとはカバレッジ不足。これはテスト対象に対してテストのカバー率が足りない。

3つ目がテスト対象ロジックのテストコードへの漏れ出しというのがあります。これに関しては次のページで説明しましょう。

テスト対象ロジックのテストコードへの漏れ出しの具体例

(スライドを示して)これがテスト対象ロジックのテストコードへの漏れ出しです。画面上半分がプロダクトコード、テスト対象のコードだと思ってください。画面下半分がテストコードだと思ってください。

通常、これは別のファイルで提供されていることが多いですよね。問題はテストコードの期待値。下から3行目というか、expectedというところの計算が、よく見るとプロダクトコードと同じ計算式が使われているんですね。

それで何が起こっているかというと、実はこの実装にはバグがあって、税額を計算しているにもかかわらず、JavaScriptのNumberは浮動小数点数だからというのがあるんですが、1円未満の端数が発生しているバグがあります。なんですけど、テストコードも同じロジックで計算しているから、同じ端数が発生して完全一致してテストが成功しちゃうんですね。

こういうケースがいろいろな現場でとても多くあります。よくよく見てみたらプロダクトコードと同じロジックで、同じ計算式で期待値を計算しているので、実質的にはこいつはテストになっていないんですね。ただ、同じ誤り方をしているので、テストが成功してしまう。これも見逃し、偽陰性につながるのですごく注意してください。これはいろいろな現場でたくさん見かけます。

信頼不能なテストが多いとテストの失敗に対して鈍感になる

そういった信頼不能なテストがあると、だんだんみんな狼少年みたいになっちゃって。例えば遅いエンドツーエンドテストや不安定なエンドツーエンドテストが失敗すると「あぁ、また失敗か」と。「リトライすればいいや」みたいな感じになっちゃうんですよね。何が怖いかというと、テストの失敗に対してすごく鈍感になってしまうことです。

本当はテストが失敗したら「なにかがおかしい」ということですぐアクションを取りたいんですが、不安定なテストの失敗に慣れきってしまうと「いいや、もう1回再実行しよう」みたいな感じになったりしてしまうんですよね。

これがどのくらいの割合になるかというと、Googleにおいては、全体のテストスイートの1パーセントに接近すると、だんだんみんな結果を信じなくなってくるというようなことが考えられます。

(次回につづく)