<-- mermaid -->

モバイルのE2Eテストでのリトライ自動化とリリース完全自動化を作った話

NewsPicks Androidエンジニアの sefwgweo です。
今回はモバイルチームで取り入れているE2Eテストでのリトライ自動化とリリース完全自動化を作った話を紹介します。

モバイルチームでは品質担保のために毎晩定時にBitriseでE2Eテストを実行していますが、 端末やクラウドのネットワーク状態など、本来グリーンになるはずのテストでもエラーになることで余計な確認が必要でした。

自動リトライとリリース完全自動化を入れる前後では以下な違いがありました。

Before After
毎朝Slackで失敗したテストを目視後、BitriseのWebコンソールでPassするまで手動でRetryを実行し、テスト結果が正しいかわからないため3回以上失敗した場合にBitriseでエラーログをみて対処 テスト結果が正しい。失敗していた場合は、ほぼ確実にデグレが起きてるので、Bitriseで確認する
リリース当日に担当者がE2Eが全て通っているのを目視で確認後、バージョンとコードを更新してBitriseのワークフローを実行しアプリをストアアップロードし、GooglePlayの審査が終わるまで待つ リリース前日にSlackで自動リリースONのスタンプを押すとZapierがテストを実行し、結果を確認してアプリをストアアップロードするまでを深夜中に行うため、大体審査は終わっている

自動リトライ

本来グリーンになるはずのテストでもエラーになることによって起きる確認作業を解消するための手段として、失敗したテストをリトライ実行し1回でも成功したら成功としたかったのですが、Bitriseの --num-flaky-test-attempts は以下の2点で今回の目的に合わないと判断してZapierを用いたリトライの自動化に至りました。

  • --num-flaky-test-attempts に指定した回数分、成功失敗に関わらず必ず実行される
  • 指定回数実行完了後1回でも失敗していると失敗扱いとなる

自動リトライの大まかなフロー

  1. BitriseでスケジュールされたE2Eテストが実行され、テスト結果がSlackに通知される
  2. Slackで受けた通知をZapierが解析する(後述)
  3. スプレッドシートから必要情報を取得する
  4. リトライ条件を満たしていた場合はSlack経由でBitriseのワークフローを呼び出し、リトライ実行する

自動リトライのZapier内フロー

1. Slackに飛んできたBitriseテスト結果メッセージを検知する

2. メッセージがE2Eテストの結果かどうかはFilter機能を使ってワークフロー名でチェックする
3. スプレッドシートからワークフロー名、リトライ時ディレイ分数、リトライ数、リトライ上限値を取得する

4. リトライ処理時のデータを加工する

// リトライ上限値(スプレッドシートから取得)
const retryDefaultCount = inputData.retry_default_count
// リトライ回数(スプレッドシートから取得)
const savedRetryCount = inputData.retry_count

let retryCount = savedRetryCount - 1
let retryFlag = true

if (retryCount < 0) {
  retryFlag = false
} else if (retryCount < 0) {
  retryCount = parseInt(retryDefaultCount) - 1
}

output = [{
  shouldRetry: retryFlag,
  retry_count: retryCount,
}];

5. 強制リトライストップフラグチェック(弊社ではZapierは他のメンバーと共有しないため、管理者以外でもスプレッドシートから任意のタイミングで強制リトライストップが可能なようにしています)

6. 手動でリトライ実行をしていた時、即時リトライ実行だと失敗が多かったためDelayさせる
7. Slackでリトライするテストコマンドを実行する
8. 失敗の場合は該当行のリトライカウント値を更新する

自動リリース

上記まででZapierによるBitriseの簡易なリトライ実行は可能ですが、このままだと次回使う時にスプレッドシートを手動で書き換える必要があります。
Androidチームではなるべくメンテナンスフリーになるように及び、自動リリースも関係するため以下のようにしています(例えば毎晩0時になったらリセットする、など状況によって他にも方法は色々あると思います)。

0. 事前にリトライ実行可能なインターバルを決めてスプレッドシートに記載しておきます

1. テスト1つ1つに対して、Zapierで実行日時をスプレッドシートの該当行に記載させます

2. 次回実行時に、上記1の時間と現在時刻の差分がリトライ実行可能なインターバルを超えていたらリトライ数をリトライ上限値でリセットします

const nowTimeStamp = parseInt(Date.now() / 1000)
const savedTimeStamp = inputData.updated_at
const retryIntervalSec = inputData.retry_count_reset_interval_minute * 60

// 現在時刻から前回リトライ実行開始時刻を引くことでリトライ間のおおよその時間を算出
const diffSec = nowTimeStamp - savedTimeStamp

// ここから下は既出処理に加筆

// リトライ上限値(スプレッドシートから取得)
const retryDefaultCount = inputData.retry_default_count
// リトライ回数(スプレッドシートから取得)
const savedRetryCount = inputData.retry_count

let retryCount = savedRetryCount - 1
let retryFlag = true

// diffSecとretryIntervalSecも判定内容として追加
if (retryCount < 0 && diffSec < retryIntervalSec) {
  retryFlag = false
} else if (retryCount < 0 && diffSec >= retryIntervalSec) {
  retryCount = parseInt(retryDefaultCount) - 1
}

output = [{
  shouldRetry: retryFlag,
  retry_count: retryCount,
  updated_at: nowTimeStamp,// 日時更新用に追加
}];

リリース自動化の大まかなフロー

  1. BitriseでスケジュールされたE2Eテストが実行され、テスト結果がSlackに通知される
  2. Slackで受けた通知をZapierが解析する(後述)
  3. スプレッドシートから必要情報を取得する
  4. 自動リリース条件を満たしていた場合はSlack経由でBitriseのワークフローを呼び出し、リリースを実行する(アプリがストアにアップロードされる)

自動リリースのZapier内フロー

1. Slackに飛んできたBitriseテスト結果メッセージを検知する

2. メッセージがE2Eテストの結果かどうかはFilter機能を使ってワークフロー名でチェックする
3. スプレッドシートからワークフロー名、自動リリースフラグを取得する

4. Bitriseの通知を解析して加工する

let preText = inputData.preText
let testResult = ""

if (preText == "*Build Succeeded!*") {
  testResult = "OK"
} else if (preText == "*Build Failed!*") {
  testResult = "NG"
} else {
  return [{error:true}];
}

output = [{
  error: false,
  test_result: testResult,
}];

5. 全体像を把握しやすくするため単一Actionで記載していますが実際は3つのActionで以下のようなことをしています

  • スプレッドシートにテスト結果を入力
  • スプレッドシートからテストの成功数を取得
  • スプレッドシートからテストの失敗数を取得

6. 成功と失敗の結果を条件に応じてデータ加工する

let okCount = 0
let ngCount = 0
let totalCount = 0
let ngList = ""

let message = ""
let hasNG = false

okCount = inputData.resultOkList.split(',').length


// NGラベルかつretry_countが-1以下のものをNGとみなす
ngCount = inputData.ngResultRetryCountList.split(',').filter((num) => num <= -1).length

totalCount = inputData.totalWorkFlowCount

ngList = inputData.ngWorkFlowList.split(',').join('\n')

if (okCount == totalCount) {
  // オールグリーンなのでリセットする
  message = "E2E Tests was All Succeeded!"
  hasNG = false
} else if (okCount + ngCount == totalCount) {
  // 規定回数リトライ後もNGがあるためリセットして終了
  message = "E2E Tests was Failed. \n" + ngList +  "\n https://app.bitrise.io/app/xxxxxx"
  hasNG = true
} else {
  // 処理中と判斷できるため何もせず終了
  return [{error:true}]
}

output = [{
  error: false,
  message: message,
  has_ng: hasNG,
}];

7. 全てのテストが完了しているかをFilter機能を使ってチェックする
8. テスト結果が全て成功の場合はAll Successなメッセージを、失敗が1つでもあれば失敗したテスト名をメッセージする
9. スプレッドシートのテスト結果とリトライ数をリセットする

10. 自動リリース必要可否をFilter機能を使ってチェックする
11. 必要に応じて指定したビルドバージョン及びビルドコードをリリース実行コマンドに渡す加工をする

let defaultVersionCodeParams = 'version:patch'
let versionCodeParams = ''

if (inputData.version == null || inputData.version == '' || inputData.version == '-') {
  versionCodeParams = defaultVersionCodeParams
} else {
  if (inputData.code == null || inputData.code == '' || inputData.code == '-') {
    versionCodeParams = 'version:' + inputData.version
  } else {
    versionCodeParams = 'version:' + inputData.version + ' ' + 'code:' + inputData.code
  }
}

output = [{
  versionCode: versionCodeParams.trim(), 
}];

12. Slackでリリースするコマンドを実行する

おわりに

モバイルチームで取り入れているE2Eのリトライ自動化及びリリース完全自動化について紹介しました。
成功率が9割を超えているテストであれば、リトライ回数を3回程度に設定しておけばまず失敗することはありません。
本件導入前後で両OSとも、特にリリース作業がかなり精神的にも作業時間的にも楽になりました。

本記事がBitriseでリトライ処理やリリース処理を自動化しようとしている方に少しでも参考になれば幸いです。

Page top