July Tech Festa 2020 TrackB https://jtf2020.peatix.com/
テストを⾃動化するのをやめ、⾃動テストを作ろうTakuya Suemura @ Autify, Inc.
View Slide
末村 拓也 (すえむら たくや)Twitter: @tsueeemuraAutify テスト⾃動化スペシャリスト開発者 兼 サポート 兼 エバンジェリスト現場猫 → 底辺PHPer → QAエンジニア → 現職CodeceptJS コントリビュータSoftwareDesign誌 6〜8⽉号『はじめよう,⾼速E2Eテスト』
AutifyについてWebアプリのE2Eテスト⾃動化のためのツールAIによるシナリオの⾃動修復(セルフヒーリング)リアルデバイス & Dockerコンテナでの実⾏に対応コーディング不要で誰でもすぐ使えるhttps://autify.com
今⽇のテーマ
テスト⾃動化
テスト⾃動化をやめて、⾃動テストを作ろう
テスト⾃動化 ≠ ⾃動テスト
テスト⾃動化の成果物 ≠ ⾃動テスト
⼿動テストを素朴に⾃動化しても、⾃動テストにはならない
今⽇話すことなぜテスト⾃動化は失敗しやすいのか⼿動テストと、その⾃動化、そして⾃動テストはどう違うのか⾃動テストとはどうあるべきか⼿動テストの⾃動化に価値はないのかとくに断りがない限りはE2Eテストについての⾔及です(E2Eテスト: システム全てを統合した状態で主にUIからやるテスト)
なぜテスト⾃動化は失敗しやすいのか
本題に⼊る前にコンテキストの説明E2Eテストとはテスト⾃動化とは
E2Eテストとはビルド済みのアプリケーションに対してWebブラウザやモバイル端末から主にGUIを通してテストを実⾏することシステムテストと呼ばれたりもするQA(品質管理)とかテスターとかが⼿動でポチポチやることが多い⼿動のものを「⼿動テスト」、⾃動のものを「E2Eテスト」と呼んだりする⼈もいるが、今回は実⾏⼿段を問わず全てE2Eテストと呼称する
よくある開発の流れローカルで開発プルリクエストを出すメインブランチにマージステージングサーバにデプロイE2Eテスト問題がなければ本番にリリース
テスト⾃動化とは主に⼿動で⾏ってきたE2Eテストを⾃動化することSelenium, Puppeteer, Cypress, TestCafe, Appiumなどを使う主に省⼒化や⾼頻度化が⽬的副次的に実⾏者によるバラツキの削減などもあるテスト⼿順は⼿動テストのものを流⽤する開発の流れそのものは変えない
Checking vs Exploring
テスト⾃動化が失敗する(機能しなくなる)よくあるパターン⼿動で頑張ってたテストを⾃動化してみたはいいが……︖突然動かなくなる (メンテナンスコストが⾼い)アプリケーションは動いてるがテストは落ちる不安定で成功したり失敗したりする思ったより効果が出ない (費⽤対効果が⼩さい)テストしてないところでばかりバグが出る早期発⾒に繋がらない
⼿動テストを⾃動化するとたくさんの観点が失われる# ログインページ## ログインできる1. URL /login にアクセスする2. ユーザー名に takuya と⼊⼒する3. パスワードに insecure と⼊⼒する4. ログインボタン をクリックする5. ホーム画⾯ が表⽰されることを確認
describe('ログインページ', () => {it('ログインできる', () => {browser.get('/login') // URL /login にアクセスする$('#username').setValue('takuya') // ユーザー名に takuya と⼊⼒する$('#password').setValue('insecure') // パスワードに insecure と⼊⼒する$('a.btn.btn-primary').click() // ログインボタン をクリックするassert.equal(browser.url, '/home') // ホーム画⾯が表⽰されることを確認})})ロケータ(要素探索のキー)がセマンティックじゃなくなったユーザー名を⼊れるフィールドっぽいやつ → #usernameログインボタンっぽいやつ → a.btn.btn-primaryアサーション(検証)が弱くなったホーム画⾯っぽいページ → /home というURLである
assert.ok($('a[href="https://yahoo.co.jp"]').isExisting())CSSが読み込まれてないけどAタグが存在するからヨシ︕
なぜ⾃動化したテストだけが「腐る」のか︖実はユニットテストでも同じことは起きている違いは開発サイクルユニットテストは「開発の中で」実⾏・修正される⾃動化したテストは「開発が終わってから」実⾏・修正されるもともと⼿動テストがそういう流れでやってたことの名残リリース直前にテストが壊れているのが分かった場合、壊れたままリリースすることがある結果、メンテナンスが後回しになってしまうこれが「腐る」の正体実⾏頻度が⾼く、開発中も実⾏されるようになると、テストは腐りにくくなる
単純に頻度を⾼めればいいのか︖頻度を⾼める(例えば1回/⽇→10回/⽇)にしても問題は解決しない回数ではなく実⾏環境の幅を増やさないといけない例えば、もともとステージングだけで実⾏していたなら、それを開発環境やCI環境でも実⾏できるようにする⼿動テストを単純に⾃動化しても、 前提条件 や 事前準備 は⾃動化されない特定の環境に依存したテストケースになってしまう環境や特定のテストデータに依存している限り、開発サイクルに寄せることはできない依存している前提条件をクリアにし、どの環境でも実⾏できるテストにする
(まとめ)テスト⾃動化が失敗してしまう理由開発サイクルの中でテストが実⾏・メンテナンスされていないテストコードのアップデートが遅れ、製品に問題はないのに「E2Eテストだけ失敗する」になりがち実⾏タイミングが遅く、バグの早期発⾒に繋がらない観点が不明瞭なままテストコードに落とし込んでしまう「ホーム画⾯が表⽰されることを確認」→「URLが /home であることを確認」のような不正確な翻訳が⽣まれる⼈間がテストする場合にくらべ発⾒が少なくなってしまう
テスト⾃動化と⾃動テストの違いとは︖
⾃動テストの⼀般的なイメージコードで書かれるxUnitとかRSpecとかTDD・リファクタリング安全な開発のためにやるもの(なんとなく)QAとかテスターがやるやつとはちょっと違う
⾃動テストと型検査、Linter⾃動テスト: アプリケーションが期待した動きをしないとリリースできない型検査: 型に不整合があるとビルドが通らないLinter: 不適切な書き⽅をすると警告が出るそれぞれの対応する領域に対して強制⼒を持つ
アプリケーションと⾃動テストの関係どちらもプロダクトの⼀部どちらも仕様を具現化したものアプリケーションはそれ単体では意味をなさない誰かに使われてはじめて価値を⽣み出すアプリケーションは振る舞いを具現化したもの⾃動テストは使い⽅を具現化したもの⾃動テストはアプリケーションを束縛するアプリケーションの特定の挙動に依存することでその挙動を束縛する互いにコードとして書かれ、実⾏可能な形で記述されていることで強制⼒を発揮する
⾃動テストとはプロダクトの⼀部であり、同時に開発される開発サイクルの中で実装される(テストフェーズの⾃動化ではない)アプリケーションの挙動に強く依存し、アプリケーションをより強固にする静的解析などと同様に頻繁に実⾏され、仕様からの逸脱を常に防ぐ
素朴なテスト⾃動化は部分的な変化のみをもたらす- ⼿動テスト テスト⾃動化 ⾃動テスト開発サイクル 開発とは別 開発とは別 開発と同時頻度 少 少 多実⾏⼿順 あいまい・暗黙的 厳密・具体的 厳密・具体的検証⽅法 発⾒的 保証的 保証的太字は⾃動化によって変わってしまうもの⾚字は⾃動化しても残り続けるもの
おれたちが本当に欲しかったものは︖
テスト⾃動化の動機⼈間からの脱却単純作業からの解放リソース不⾜の解決属⼈化防⽌検証作業のバラツキを減らす⾼速化・⾼頻度化リリーススピードの向上いつでもテストリファクタリング上司のご意向
(再掲)⼿動テストとテスト⾃動化、そして⾃動テストの違い- ⼿動テスト テスト⾃動化 ⾃動テスト開発サイクル 開発とは別 開発とは別 開発と同時頻度 少 少 多実⾏⼿順 あいまい・暗黙的 厳密・具体的 厳密・具体的検証⽅法 発⾒的 保証的 保証的太字は⾃動化によって変わってしまうもの⾚字は⾃動化しても残り続けるもの
なぜテスト⾃動化では⾼速・⾼頻度が実現されないのか
⾼頻度化に向けて(1) シナリオの最適化⼿動テスト向けに最適化された状態あるテストで作られたデータを別のテストで再利⽤している「新規作成」のシナリオで作ったデータを「更新」で再利⽤特定の環境にしか存在しないテスト⽤データを使っている
⾼頻度化に向けて(1) シナリオの最適化⾃動テスト向けに最適化する⽬的︓CIでの実⾏や並列実⾏との相性を良くする常に新しいデータを作成するGUIではなくAPIやコマンドで作成する実⾏が他のテストに影響しないようにする例えば、テストデータの作成時にランダムな(重複しない)⽂字列を使う環境依存の箇所を排除する環境変数を使う
⼿動テスト最適化機能1機能2機能3ログイン機能1ログイン機能2ログイン機能3機能1のデータ作成機能1, 2のデータ作成ログインログアウトログインログアウト⾃動テスト最適化テスト⽤のユーザー作成事前準備実⾏事後処理機能1のデータ作成テストデータの削除
⾃動テストらしいテスト⾃動化開発中も含めさまざまな環境でテストされている⾼頻度(理想はコミットorプッシュごと)に実⾏され不具合を防ぐ安定性が⾼い他のテストシナリオからの影響を受けない何度も、同時に実⾏されても問題ない(冪等性がある)テストシナリオが短く保たれているテスト観点以外の操作はAPIやコマンド(サーバサイドスクリプト)で⾏う
HAPPY END! (?)
⾼頻度は実現できたが、⼈間からは脱却できたか︖⼈間からの脱却 ← こっちがまだ単純作業からの解放リソース不⾜の解決属⼈化防⽌検証作業のバラツキを減らす⾼速化・⾼頻度化リリーススピードの向上いつでもテストリファクタリング上司のご意向
参考: テスト⾃動化の8原則1. ⼿動テストはなくならない2. ⼿動でおこなって効果のないテストを⾃動化しても無駄である3. ⾃動テストは書いたことしかテストしない4. テスト⾃動化の効⽤はコスト削減だけではない5. ⾃動テストシステムの開発は継続的におこなうものである6. ⾃動化検討はプロジェクト初期から7. ⾃動テストで新種のバグが⾒つかることは稀である8. テスト結果分析という新たなタスクが⽣まれるhttps://sites.google.com/site/testautomationresearch/test_automation_principle
参考: テスト⾃動化の8原則3. ⾃動テストは書いたことしかテストしない⼈間による⼿動テストは、テストケースの記述に対して驚くほど広範な要素を暗黙的にテストしている。これに対し、操作、合否判定を厳密に記述する必要がある⾃動テストにおいては、おのずと視野は「記述された様に」限定される。ユーザ名とパスワードを⼊⼒してログインする、といったテストが⾃動化されていたとして、その⾃動テストは仮にログイン画⾯に表⽰されているロゴが競合他社のものであったとしても、それに気づくことはない。(太字は発表者によるもの)https://sites.google.com/site/testautomationresearch/test_automation_principle
アサーション地獄もまた「素朴な⾃動化」の⼀つ
再掲assert.ok($('a[href="https://yahoo.co.jp"]').isExisting())CSSが読み込まれてないけどAタグが存在するからヨシ︕
参考: テスト⾃動化の8原則7. ⾃動テストで新種のバグが⾒つかることは稀である運⽤に乗った⾃動テストは基本的に「枯れた」テストケースを対象とするため、ほとんどの種類のバグはテストケースを枯らす過程、あるいは⾃動テストを実装する過程で既に⼈間によって発⾒されているはずである。 多くの運⽤に乗った⾃動テストの意義は「⼀度動いたはずの機能がうっかり壊れる」ことを最速で発⾒することにある。 ただし、⼿順が同じでデータの種類が膨⼤なテストを⾃動化する場合、ファジング、テストパターンを有機的に⽣成できるAPIレイヤのテスト、ブラウザやRDBなどのバージョンアップの影響を受けていないことを確認するテストなど、いくつかの例外もある。(太字は発表者によるもの)
(再掲)テスト⾃動化が失敗するよくあるパターン⼿動で頑張ってたテストを⾃動化してみたはいいが……︖突然動かなくなる (メンテナンスコストが⾼い)アプリケーションは動いてるがテストは落ちる不安定で成功したり失敗したりする思ったより効果が出ない (費⽤対効果が⼩さい)テストしてないところでばかりバグが出る早期発⾒に繋がらない
開発サイクル寄りのかっちりしたテストとは別のアプローチが必要
ふんわり
「ふんわり ≒ ⼿動テストよりの」⾃動テスト- ⼿動(ふんわり)テスト テスト⾃動化 ⾃動(かっちり)テスト開発サイクル 開発とは別 開発とは別 開発と同時頻度 少 少 多実⾏⼿順 あいまい・暗黙的 厳密・具体的 厳密・具体的検証⽅法 発⾒的 保証的 保証的
「ふんわり」が必要な理由「かっちり」では⾒落としてしまうものがある環境依存のデータに依存するものを検知できない例)⼤量の関連データを持ったユーザーのみ正常に動作しないアサーションを書いてないものを検知できない明らかにおかしいけど⼿順書に書かれてないからヨシ︕特にレイアウト崩れとかは弱い全部 getComputedStyle でやっちゃう︖
「ふんわり」が必要な理由「かっちり」はそもそもE2Eらしくないかっちりしたテストで検証するのはクリーンで焦点を絞ったテストE2Eはそもそもダーティでリアルな状況下でのテストが⽬的のはずステージングや本番にしかないデータブラウザやデバイスなどの実⾏環境リアルな環境で「正しそうに」動くことを確認するものが欲しい
「ふんわり ≒ ⼿動テストより」の⾃動テストは実現可能か︖
ふんわりしたロケータかっちりテストとは異なり、開発サイクルの中でメンテされるとは限らないのでふんわり≒宣⾔的なロケーティングが必要// これはよくない$('#username').click()// これは次善の策$('[data-test=username]').click()// こう書けると良さそう$('ユーザー名').click()
ふんわりしたロケータ(1) セマンティックロケータ⽂⾔や構造による「意味のある」ロケータ#username ではなく ユーザー名 のように表⽰されている⽂⾔で指定例えばinputならラベルやプレースホルダを使うxxという⽂⾔を持つモーダルの中のyyというボタン のようなUI構造で指定// CodeceptJSでは⽂⾔や構造による指定をサポートしているI.click('ユーザー名')// 「本当に送信しますか︖」という⽂⾔のあるモーダルの中からwithin(locate('.modal'.withText('送信先は間違いありませんか︖'), () => {// 「はい」と「送信」をクリックI.click('はい')I.click('送信')})
ID: not matchedclass: matchedalter text: matchedcoodinate: not matchedimage source: matched.... Confidence: 80%ふんわりしたロケータ(2)AIによる要素探索(マルチロケータ、セルフヒーリング)複数のロケータで複合的に探索⾒つかった要素の特徴でシナリオをアップデート開発サイクルとテストのサイクルのズレを吸収
ふんわりした検証アサーション地獄を回避したい全ての検証項⽬を地道に定義するのはメンテナンス地獄にも繋がる⼈間がパッと⾒て分かるレベルのエラーを(定義せずに)回避したい表⽰されるべきものが表⽰されていないレイアウトが崩れている
ふんわりした検証画像⽐較(ビジュアルリグレッション)テスト実⾏ごとのスクリーンショットを⽐較するスタイル崩れなどの検出に有効⽐較から除外する箇所の定義が別途必要広告検索結果撮影時の座標がズレると全てが終わる位置ずれ補正Awesome Visual Regression Testing: 画像⽐較テストツールのリンク集https://github.com/mojoaxel/awesome-regression-testing_
ふんわりした検証その他考えうる施策ページパフォーマンス、エラー監視デッドリンク検知⾃然⾔語解析テキストの意味が⼤幅に変わっていないか⽂法の誤りetc...
かっちりとふんわりどっちがいいの︖
テストを「網のように」考えるバグはなるべく上層でキャッチするようにしつつ、下にも網を広げるユニットテスト統合テストAPIテストUIコンポーネントテストかっちりしたE2EテストふんわりしたE2Eテスト⼿動での探索的テスト
テストタイプによる使い分け例かっちりテスト向け機能テスト回帰テストふんわりテスト向け互換性(クロスブラウザ・マルチデバイス)テストユースケーステストスモークテスト
今⽇伝えたかったこと⼿動テストを素朴に⾃動化しても、⾃動テストにはならない素朴なテスト⾃動化はどっちつかずのものになってしまう新規バグは⾒つからず、そのくせメンテ⼯数はガッツリ持っていく⾃動テストらしい具体性をもったかっちりした⾃動テストと、⼿動テストのようにファジーなふんわりした⾃動テストというアプローチがあるテスト⾃動化をゴールにするのではなく、エントリーポイントとして使い、さらに発展させてほしい
Enjoy Testing!spatial.chatの H1 で僕と握⼿︕