E2Eテスト自動テストのFalse PositiveとFalse Negative

はじめに

本稿は、ソフトウェアテスト Advent Calendar 2019の12/05の投稿になります。

先にまとめ

  1. E2E自動テストのFalse Positiveは依存関係が多すぎることが原因なので、それを見つめ直しましょう
  2. E2E自動テストのFalse Negativeは多くの場合、assert命令する場所あるいは条件を書き間違えていることが原因なので、それを見つめ直しましょう

目次

さて、目次です

  1. 背景
  2. False PositiveとFalse Negativeとは
  3. 具体例
    3.1 題材(ホテル予約サイトを模したテストページ)
    3.2 具体例(False Positive)
    3.3 具体例(False Negative)
  4. 原因
    4.1 False Positive -> 依存するものが多すぎる
    4.2 False Negative -> assertする条件あるいは場所が間違っている
  5. 対策
    5.1 False Positive対策
    5.2 False Negative対策
  6. まとめ

1. 背景

第三者検証の会社でWebのE2Eテスト自動化をやってるとたまに「自動テストの品質保証はどうしてるんですか?」という質問を頂いたりします1最近は減ったかな?まぁ転職したというのもあるけれど。自動テストの価値とは何かという話はこちらがとても良さげなので、それは今度お時間あるときに読んでいただくとして、今日はFalse PositiveFalse Negativeとそれの対策を考えてみようかと思います。

2. False PositiveとFalse Negativeとは

こちらに詳しく書いてくださってる方がいるので詳細は省きますが、False Positiveは「間違って陽性と判定する」こと、ようは誤検知です。追加の作業コストがかかるやつですね。一方、False Negativeは「間違って陰性と判定する」こと、ようは検知漏れです。リリースされた後におろおろするやつですね。「自動テストの品質保証」という言葉で質問される方は、大体の場合、False Negativeの方を気にしてるんじゃないかなと思います。また、False Positiveに関しても多くの方が苦労されてるかなと思います。

3. 具体例

さて、具体的にはどんな問題でしょうか。ここで、題材を用意します

3.1 題材(ホテル予約サイトを模したテストページ)

日本Seleniumユーザーコミュニティで用意されているテスト用サイトをAUT(Application Under Test, テスト対象)として使うことにします。次のような正常系の1本のテストシナリオを書いてみます。

from selenium import webdriver
from time import sleep


def test_make_successful_reservation():
    # Set up: 自動テストを開始する準備だぁ!
    driver = webdriver.Chrome(executable_path="path_to_chromedriver")
    driver.get('http://example.selenium.jp/reserveApp/')
    sleep(3)

    # 最初の画面の操作: 日付と予約者名を入力してる。簡単化のため、本当は考えないといけない諸々を省いてる
    driver.find_element_by_id("reserve_year").send_keys("2019")
    driver.find_element_by_id("reserve_month").send_keys("12")
    driver.find_element_by_id("reserve_day").send_keys("9")
    driver.find_element_by_id("guestname").send_keys("mwakizaka")
    driver.find_element_by_id("goto_next").click()
    sleep(3)

    # 次の画面の操作: 料金を確認(assert)して、前へ進む
    assert driver.find_element_by_id("price").text == "8000"
    driver.find_element_by_id("commit").click()
    sleep(3)

    # 最後の画面の操作: 予約が受け付けられたことを確認(assert)する
    assert driver.find_element_by_css_selector("body.final_form > div.container > h1").text == "予約を完了しました。"

    # Teardown: 自動テストおわり
    driver.quit()


# 余談: 待機処理(sleep(3)のこと)を入れると安定するかもだが、その秒数どっから導き出してきたねん的な問題はある。
#      ようはヒューリスティックに過ぎない

Seleniumを使ってます。Seleniumはブラウザ操作を自動化するツール2公式も「Selenium automates browsers. That’s it!」と言ってます。素晴らしく端的ですねです。簡単に説明すると、find_element_by_id("XXX")で操作対象の画面要素を指定して、続くsend_keys("YYY")が実現したい操作を表現しています。またassertで始まる行は、そのテストシナリオにおける確認項目になります。

3.2 具体例(False Positive)

さて、ご存知の方もいらっしゃるかもしれませんが、日本Seleniumユーザーコミュニティでは、もう1つのテストサイトを用意3リニューアルされた想定とのこと。http://www.selenium.jp/test-siteしており、それがこちらです。ここに先ほどの自動テストを実行するとどうなるかというと、おそらく12行目でfailすることでしょう。これがFalse Positiveです。人が同じテストシナリオを実行したら、すんなりテストOKになることでしょう。

3.3 具体例(False Negative)

では、False Negativeはどんな感じでしょうか。
さきほどのテストシナリオ、一見「2019/12/9」と入力するかのように見えますが、実は違ってまして、実際には「2019/12/19」とか「2019/12/29」とか入力してくれます。本来は入力窓の中身を綺麗にして差し上げないといけないわけです。4おそらく「2019/12/39」になったあたりで気づくと思いますがこのように、意図しないテスト条件でテストシナリオを流した結果、本来意図していたテスト条件での欠陥を見逃しまうことって、なくはないですよね

4. 原因

4.1 False Positive -> 依存するものが多すぎる

そもそもE2Eの自動テストは全てが意図した通りに動いている前提のもと、初めて実施可能なんですよね。先ほどは画面レイアウトが変わったのでテストが失敗していましたが、それ以外にもネットワークの疎通も気にしなければいけない、AUTが正しくデプロイされてるかどうかとか、目的のバッチ処理が起動してるかどうかとか、あるいは先のSeleniumだったら、テスト実行に使ってるChromeのバージョンがアップデートされてChromedriverと合わなくなったとか。5余談ながらも、個人的に1つ面白いな思うのは、ほとんどの場合、Assertの処理に差し掛かる以前で失敗することです。つまり、assert命令(assert XXX)自体がFalse Positiveな振る舞いをすることはごくごく限られていて、私個人のない知恵絞る限り、例えば、今お使いのテストツールに残念ながら欠陥が仕込まれていたり、あるいは宇宙から降り注いだ中性子が今お使いのPCやCI環境でメモリエラーを引き起こしたりする場合ぐらいしかないわけです。https://wired.jp/2018/07/05/cosmic-ray-crash-supercomputers/

4.2 False Negative -> assertする条件あるいは場所が間違っている

従来的なE2E自動テストは非常にピンポイントでしかモノを見れないわけですが6こちらの第3原則にも記載されてますね、assert命令の条件(assert XXX のXXXの部分)を書き間違えると、当然ですが検知してくれません。assert命令はまさに、「何を以ってテストOKとするか」を決めてるわけですね。

あとは間違った前提のもと、テストシナリオが進んだが、結果的にassert命令が通ってしまう場合もあります。この場合、実際のところシュレティンガーの猫よろしく、テスト結果がOKだったのかNGだったのかよくわからないわけですが、もし本当はNGだったなら、それはまずいわけです。

5. 対策

さて、やっかいですね。これといった銀の弾丸は特にないですが、いくつか書いていきます。

5.1 False Positive対策

1. テストレベルを落とす、依存するものを減らす

テストオートメーションピラミッドってこれのことだったんですね!7まぁあれ作って維持するの、別の苦労があるのですが、その辺りは別の機会にまとめられればいいなと。

2. 前提を確認するテストを前段で行う

例えば「ログインして〇〇する」(以下、テストA)のようなテストシナリオはよくあると思うのですが、 「ログインする」という部分をテストBとして先にテストするようなイメージ。8昔いた現場でやってました。ただ、そこはW/Fだったので、CI/CDのプラクティスに合わせるなら、テストAとテストBの実行頻度を変えてあげるとかですかねー

3. E2Eテストの量を減らす

1と言ってることはそんな変わらないですが、思い切りは肝心。結局回らないんだったら意味がない

4. E2Eテストの粒度を下げる

要は品質過剰になってないか、というお話です。例えば、先ほどの先ほど自動テストでは、「予約を完了しました。」という文言を完全一致で見てましたけど9当たり前かもだけど、文言って案外変わりやすい、もうちょっと緩い手法で見たりとか

5.2 False Negative対策

1. 安全装置をつける

assert命令を増やすようなイメージですね。例えば意図しない画面でassertしたく無いから、遷移するたびに、urlとかassertする

2. 状態の変化に着目して、それぞれassertする

雑な説明であれですが、WhenとThenの間10(追記)いや、GivenとWhenなのかな、ようはassert直前の操作の前にassertいれるにassert入れるような感じ

3. 意図的にテストが失敗することを確認する

効果抜群では無いけれど、なんかこう、意外とじわじわ効く感じがする

4. assert条件をテストする

テストをテストするというやつでしょうか。あまりこの考えのファンではないんですけど、必要に迫られて試したことはあります。残念ながら、結局テストなので条件次第11ソフトウェアテストの7原則 第6番と思うと、あまりそれ以上の答えはないです。12やったときのことを少し書こうかな。とりあえずまずはお手持ちのテスト勘(?)を働かせます。網羅的な条件でやってみるも良いのですが、どちらかというとたぶん探索的テストの方が良いですね。エラーを見つけるつもりでプログラムを実行する過程(G.J. マイヤーズ (1980). ソフトウェア・テストの技法 近代科学社)というやつでしょうか。そして、assert命令が間違って通りうる条件を、そのテストシナリオが依存している各要因から検討します。そのassertに対するテストは自動化しなくていいと思います。何を思ったか、自動化するとそれっぽさが出るという理由だけで、その当時は自動化しましたが、あの自動テストは今も元気にやっているでしょうか。

6. まとめ

False PositiveとFalse Negativeについてつらりつらりと書いてみました。どう折り合うかは人それぞれですけど、個人的にはFalse Negativeってあまり見ないORそもそも簡単に可視化できないので、まずは明らかに目に見えてるFalse Positiveの問題解決から取り組めばいいかなと思いました。長くなりましたが、以上です。

あとがき

  • 長くなったのでリストラした小技たち一覧
    インフラ、ミドルウェアは監視しとく、Seleniumならロバストなロケータとページオブジェクトパターン使う、テストデータと環境整える、テストすることしないことの認識あってる?、テストコードレビューしてもらう、とか
  • がんばって新しいこと書いたつもりだけど、大して新しいこと書いてない感ある

参考

  • https://abstracta.us/blog/test-automation/avoid-false-positives-false-negatives-test-automation/