🤖

Android / iOSアプリのE2Eテスト全部で250シナリオを自動化しました! 第2回 不安定なテストと向き合う

2022/12/02に公開

こんにちは。
株式会社ココナラで技術戦略室を担当しているKと申します。

ココナラアプリのE2Eテストシリーズ第2回目です。

前回は、ココナラアプリのE2Eテスト自動化の全体像をお話ししました。
今回は、E2Eテストの不安定さとその対応についてお話ししたいと思います。

https://zenn.dev/coconala/articles/a3a5e33cd1d981
第2回. E2Eテストの不安定さにどう対応しているのか ←今回
第3回. E2Eテストの壊れやすさにどう対応しているのか

要約

本記事を要約すると、以下のようになります。

  • E2Eテストは本質的に不安定です。
  • ココナラでは、不安定さに対して現在以下の3つの対応を行っています。
    • ✅ 自動リトライさせる
    • ✅ 不安定なテストを可視化して見つけやすくする
    • ✅ 待ち処理を最適化する

以降、それぞれの詳細をお話しします。

E2Eテストは本質的に不安定

E2Eテストは本質的に不安定です。

テストの失敗には多数の要因があります。
それが互いに影響しあうことでかけ算のように複雑性が増大し、不安定性に繋がります。

このかけ算には次の2つがあります。

1つ目のかけ算

E2Eテストは様々なものの組み合わせで動作します。
その中のどれか一つに一瞬でも問題が起きると、テストが失敗します。

かけ算
すべてがうまくいく確率は?

具体的に上図のそれぞれが不安定さを生み出す要因をいくつか見てみましょう。

No. 項目 内容
1 テストデバイス ● たまに表示や動作に時間がかかり、次の操作ができないことがあります。
● 画面に重なるようにプッシュ通知が表示され、タップが失敗することがあります。
● デバイスの解像度によって、うまく動かないことがあります。
● Android エミュレーター、iOSシミュレーターは少し不安定なところがあります。
2 内部テスト環境 ● 画面のコンポーネント数が多い場合、AppiumやWebDriverAgentが不安定になることがあります。
● ネットワークやサーバーの負荷状況により、いつもより時間がかかることがあります。
3 外部テスト環境 ● 外部テスト環境は、内部テスト環境以上に不安定な場合があります。
4 仕様のランダム性 ● そもそもアプリの仕様がランダムな場合があります。例えば、過去の行動によって各種訴求ポップアップがランダムで表示されることがあります。

これらをすべて乗り越えてやっとE2Eテストは成功します。

2つ目のかけ算

通常、E2Eテストには複数のテストシナリオがあります。
例えば、現在ココナラではAndroid、iOS、合わせて250シナリオほどあります。
シナリオが多ければ多いほど、シナリオが全て成功する確率は低くなります。

仮に1シナリオの成功確率が95%、シナリオ数が100としてすべてのテストが成功する確率を求めてみましょう。

0.95 ^ {100} = 0.00592

なんと、すべて成功する確率は 0.592% しかありません。
これは絶望的な数字です。

確率

E2Eテストの不安定さにどう対応するか

以上の数字を踏まえて、対応方法を考えます。
ココナラでは以下の3つの対応を行っています。

  1. 自動リトライさせる
  2. 不安定なテストを可視化して見つけやすくする
  3. 待ち処理を最適化する

✅ 1. 自動リトライさせる

まずはリトライです。

上の計算では100個のシナリオがすべて成功する確率は 0.592% でした。
リトライをすることで、この確率がどのように変わるか見てみます。

  • リトライしない場合: (1 - 0.05 ^ 1) ^ {100} = 0.00592
  • 1回リトライする場合: (1 - 0.05 ^ 2) ^ {100} = 0.779
  • 2回リトライする場合: (1 - 0.05 ^ 3) ^ {100} = 0.988
  • 3回リトライする場合: (1 - 0.05 ^ 4) ^ {100} = 0.999

(1 - 0.05 ^ n)は、n回実行した場合にそのうちいずれか一回でも成功する確率です。

それぞれのテストを3回リトライすると、成功する確率は 99.9% にまで上昇します。
これがリトライの効果です。

確率

実例として、ココナラでは夜間に全シナリオのE2Eテストを流しています。
その際、Jestのリトライ機能で自動的に最大3回までリトライさせています。

jest.retryTimes(retryTimes); // 大量のシナリオを流す場合は3

✅ 2. 不安定なテストを見つけやすくする

上のリトライの計算では、成功確率を95%と仮定していました。
しかし、中には以下のようなシナリオが存在する場合もあります。

  • 成功確率がもっと低い(例えば80%)
  • リトライしても成功しない

このようなシナリオは修正が必要です。
エラーになり続ける状態を放置すると、テストの信頼性が失われます。
狼少年のように、バグを検出したにもかかわらず、よくあることだと見逃されてしまうかもしれません。

では、成功確率が低いテストをどうやって特定すればよいのでしょうか。
ココナラでは、ReportPortalで不安定なテストを可視化しています。

こちらが実際のReportPortalの画面です。
このような可視化の仕組みがあることで、不安定なテストを特定しやすくなります。

ReportPortal
ReportPortal

✅ 3. 待ち処理を最適化する

最後は、待ち処理の最適化です。

E2Eテストは、一つ前の操作が完了する前に次の操作をしてしまうと失敗します。

操作

したがって、待ち処理の最適化が重要になります。
ココナラでは以下の2種類の最適化を行っています。

  • プログレスが表示されている場合、消えるまで待つ(Androidのみ)
  • 画面描画が止まるまで待つ

最適化1. プログレスを待つ

ココナラアプリでは、何か操作を行うとこのようなプログレスが表示されます。
このプログレスが表示されている間は、次の操作をしても失敗します。

ReportPortal

このような失敗を回避するため、プログレスを検出した場合は消えるまで待つ最適化を行っています。

最適化2. 画面描画を待つ

画面描画が完了する前に次の操作をした場合、失敗する可能性が高いです。

画像待ち

このような失敗を回避するため、画面描画が止まるまで待つ最適化を行っています。
具体的には、100ms間隔でスクリーンショットを2枚撮り、差分がなくなるまで待っています。

画像待ち

透過的な実装

このような待ち処理を各テストコードに実装していたのでは、テストコードが冗長になってしまいます。
そうならないように、これらの最適化はJavaScriptのProxy機能を使用して透過的に行っています。

実際のコードを例に挙げます。

// タップ前に、自動的に最適化された待ち処理が実行されます
await by.text('次へ').click();
  • by.text('次へ')
    • WebDriverIOのChainablePromiseElementをProxyで再定義したオブジェクトを返します。
  • click()
    • Proxyで再定義されており、以下の処理を行います。
      • 上記の待ち処理を実行する
      • その後、タップする

JavaScriptのProxy機能の詳細については、こちらをご参照ください。
オブジェクトのプロパティアクセスやメソッド呼び出しを再定義し、動作を変えることができます。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy

3つの対応の結果

ココナラでは以下の3つの対応を行うことで、完璧ではないものの比較的安定してテストが動作しています。
特に3については、最適化なしではテストを実行する気が起きなくなるほどの効果がありました。

✅ 1. 自動リトライさせる
✅ 2. 不安定なテストを可視化して見つけやすくする
✅ 3. 待ち処理を最適化する

まとめ

本記事では、E2Eテストの不安定さに対するココナラの取り組みをご紹介しました。

本記事が同じようにE2Eテストの不安定さと日々戦われている方の目にふれ、その布石となれば幸いです。

次回は第3回として、E2Eテストの壊れやすさというテーマでお話しできればと思います。
ココナラが壊れやすさにどのように対応しているのか、具体例を含めてお話しします。

実際にこの仕組みをご覧になりたい方は、ぜひ以下フォームよりお気軽にご連絡ください!
ココナラに少しでも興味が湧いたという方も大歓迎です。
入力時間は1、2分です。
https://open.talentio.com/r/1/c/coconala/pages/70417

ココナラのエンジニアについてもっと知りたい!という方はこちらをご確認ください。

https://coconala.co.jp/recruit/engineer

Discussion