不安定な自動テストは QA エンジニアにとって悩みの種となり、不確実性をもたらし、テスト スイートの信頼性を損ないます。この記事では、SDET および QA 自動化の指導者としての私の経験に基づいて、不安定さを克服するための実践的なアドバイスを提供します。 JavaScript と Playwright の例を紹介しますが、これらの普遍的なヒントはあらゆる言語やフレームワークに適用でき、堅牢で信頼性の高い自動テストを作成するのに役立ちます。早速、テストがしっかりと機能することを確認してみましょう。
場合によっては、テスト シナリオを続行するために、要素が DOM に表示されるか、アプリケーションが特定の状態に達するまで待機する必要がある状況に遭遇することがあります。 Playwright の自動待機機能のような最新のインテリジェントな自動化フレームワークを使用しても、カスタムの待機メソッドを実装する必要がある場合があります。たとえば、テキスト フィールドと確認ボタンがあるシナリオを考えてみましょう。サーバー側の特定の動作により、確認ボタンはフォームに入力してから約 5 秒後にのみ表示されます。このような場合、2 つのテスト ステップの間に 5 秒間の暗黙的な待機を挿入するという誘惑に抵抗することが重要です。
textField.fill('Some text') waitForTime(5000) confirmationButton.click()
代わりに賢いアプローチを使用し、正確な状態を待ちます。これは、何らかの読み込み後に表示されるテキスト要素である場合もあれば、現在のステップが正常に完了した後に消えるローダー回転要素である場合もあります。次のテストステップの準備ができているかどうかを確認できます。
button.click() waitFor(textField.isVisible()) textField.fill()
もちろん、スマートに確認できないものを待つ必要がある場合もあります。そのような場所にコメントを追加するか、待機メソッドなどのパラメータとして理由の説明を追加することをお勧めします。
waitForTime(5000, {reason: "For unstable server processed something..."}
これを使用することのさらなる利点は、そのような説明を保存できるテスト レポートです。これにより、ここで 5 秒間待機する内容と理由が他の人や将来のあなたにとっても明らかになります。
waitForTime(5000, {reason: "For unstable server processed something..."} button.click() waitFor(textField.isVisible()) textField.fill()
ロケーターは自動テストの重要な部分であり、誰もがそれを知っています。ただし、この単純な事実にもかかわらず、多くの自動化エンジニアは安定性を重視し、テストでこのようなものを使用しています。
//*[@id="editor_7"]/section/div[2]/div/h3[2]
DOM 構造はそれほど静的ではないため、これは愚かなアプローチです。いくつかの異なるチームがそれを変更することがあります。これは、テストが失敗した後に行われます。したがって、堅牢なロケーターを使用してください。ところで、XPath 関連の記事をぜひ読んでください。
単一スレッドで複数のテストを含むテスト スイートを実行する場合、各テストが独立していることを確認することが重要です。これは、次のテストが予期せぬ状況によって失敗しないように、最初のテストでシステムを元の状態に戻す必要があることを意味します。たとえば、テストの 1 つが LocalStorage に保存されているユーザー設定を変更する場合、最初のテストの実行後にユーザー設定の LocalStorage エントリをクリアすることは良い方法です。これにより、2 番目のテストがデフォルトのユーザー設定で実行されるようになります。また、ページをデフォルトのエントリ ページにリセットしたり、新しいテストが同じ初期条件で開始されるように Cookie やデータベース エントリをクリアしたりするなどのアクションを検討することもできます。
たとえば、Playwright では、beforeAll()、beforeEach()、afterAll()、および afterEach() アノテーションを使用してこれを実現できます。
いくつかのテストを使用して次のテスト スイートを確認します。 1 つ目はユーザーの外観設定を変更することで、2 つ目はデフォルトの外観を確認することです。
test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goTo(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
このようなテスト スイートを並行して実行すると、すべてがスムーズに進みます。ただし、これらのテストを 1 つずつ実行すると、そのうちの 1 つが別のテストと干渉し、テストが失敗する可能性があります。最初のテストでは、外観が明るいものから暗いものに変わりました。 2 番目のテストでは、現在の外観が明るいかどうかを確認します。ただし、最初のテストでデフォルトの外観がすでに変更されているため、テストは失敗します。
このような問題を解決するために、各テストの後に実行される afterEach() フックを追加しました。この場合、デフォルトのビューに移動し、ユーザーの外観をローカル ストレージから削除してクリアします。
test.afterEach(async ({ page }) => { await user.localStorage(appearanceSettings).clear() await user.goTo(views.home) }); test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goto(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
このアプローチを使用すると、このスイート内の各テストは独立します。
不安定なテストに遭遇しない人はいないため、この問題に対する一般的な解決策には再試行が含まれます。 CI/CD 構成レベルでも、再試行が自動的に行われるように構成できます。たとえば、最大再試行回数を 3 回に指定して、失敗したテストごとに自動再試行を設定できます。最初に失敗したテストは、テスト実行サイクルが完了するまで最大 3 回再試行されます。利点は、テストがわずかに不安定な場合は、数回の再試行で合格する可能性があることです。
ただし、欠点は、失敗する可能性があり、その結果、余分なランタイムが消費されることです。さらに、この方法では、多くのテストが 2 回目または 3 回目の試行で「合格」したように見え、誤って安定していると判断してしまう可能性があるため、誤って不安定なテストが蓄積される可能性があります。したがって、テスト プロジェクト全体に対して自動再試行をグローバルに設定することはお勧めしません。代わりに、テストの根本的な問題をすぐに解決できない場合には、選択的に再試行を使用してください。
たとえば、「filechooser」イベントを使用していくつかのファイルをアップロードする必要があるテスト ケースがあるとします。 <イベント 'filechooser' を待機中にタイムアウトを超過しました> エラーが発生します。これは、Playwright テストが 'filechooser' イベントの発生を待機しているものの、タイムアウトに時間がかかっていることを示します。
async function uploadFile(page: Page, file) { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); }
これには、アプリケーションの予期しない動作や環境の遅さなど、さまざまな理由が考えられます。これはランダムな動作であるため、最初に考えられるのは、このテストに自動再試行を使用することです。ただし、テスト全体を再試行すると、余分な時間が無駄になり、uploadFile() メソッド自体の最初のエラーで発生したのと同じ動作が発生する危険性があるため、エラーが発生した場合は、テスト全体を再試行する必要はありません。
これを行うには、標準の try…catch ステートメントを使用できます。
async function uploadFile(page: Page, file) { const maxRetries = 3; let retryCount = 0; while (retryCount < maxRetries) { try { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); break; // Success, exit the loop } catch (error) { console.error(`Attempt ${retryCount + 1} failed: ${error}`); retryCount++; } } }
このようなアプローチにより、余分な時間が節約され、テストの不安定さが軽減されます。
最後に、信頼性の高い自動テストへの道は簡単です。一般的な課題を最小限に抑えるか積極的に対処し、インテリジェントな戦略を実装できます。この旅は進行中であり、永続的にテストの信頼性が高まることを忘れないでください。不安定さには別れを告げ、安定性を歓迎しましょう。品質への取り組みがプロジェクトの成功を確実にします。テストを楽しんでください。
ここでも公開されています。