スモークテストの実行時間を1/5にして、リリースまでにかかる時間を改善した話|Offers Tech Blog
こんにちは、Offers を運営している株式会社 overflow CTO の 大谷旅人 です。
連続投稿 したテンションで続けて💪と息巻いたものの、ハードなコンテンツ書ききる体力なかったとです。
というわけで、小ネタです。
はじめに
スモークテストとは?
スモーク テストはアプリケーションの基本機能を確認する基本的なテストです。このテストは迅速に実行できるようになっており、その目的はシステムの主要な機能が期待どおりに動作するかを確認することです。
スモーク テストは、新しいビルドが作成された直後により高価なテストを実行できるかどうかを決定したり、デプロイ直後に新しくデプロイされた環境でアプリケーションが適切に実行されていることを確認したりする場合に使用します。
Atlassianさんからの引用 ですが、スモークテストは E2E テストなどを通して基本的な機能が動作するか、既存機能に不具合やデグレが起きてないかを確認するテストのことです。
弊社でのやり方
Datadog Synthetic Testでの実行
弊社では、ProductionDeploy が行われる前(リリースタグを切った後)に専用環境へ自動デプロイし、基本機能がエラーなく動作しているか、表示不具合などは発生してないかを E2E テストを実行することで確認しています。
改善前の問題点
当初は基本機能を確認するシナリオが数個しかなかったものの、あれもこれもと追加するうちにいつしか数十シナリオまで膨れ、完了までの時間が非常にかかるようなってきていました。
改善前の状況を羅列すると以下な感じです。
- 1 つのシナリオテスト完了にかかる時間が平均 5 分
- 総シナリオ数は 20~、それぞれテストケースが 30~50 個ほど。総実行完了までは 40,50min ほどかかっており PR マージして放置していた
- 専用環境は運用コストを考えてコンテナ数やリソースは潤沢に割り当ててなく、1 つずつ直列に実行していた
改善方法
並列実行数上げ
まずは、テスト実行の並列化を行いました。割当リソースはそのままに同時に実行できるテスト数を増やしていきました。
我々が利用している DatadogSyntheticTest は、cli に複数テスト ID 食わせれば簡単に並列実行できるので、↓な感じで実行するように変更しました。(zx は操作関数も揃っているし、コマンド実行も手軽にできて良いです。zx の紹介記事は こちら)
#!/usr/bin/env -S npx zx
const chunk = (arr, chunkSize = 1, cache = []) => {
const tmp = [...arr];
if (chunkSize <= 0) return cache;
while (tmp.length) cache.push(tmp.splice(0, chunkSize));
return cache;
};
// WARNING: disabled escaping special characters during command substitution
$.quote = (str) => `${str}`;
const publicIds = data.tests
.filter(
(test) =>
test.status === "live" &&
test.tags.includes("xxxxxxxxxxxxx")
)
.map((test) => test.public_id);
// 10個並列実行
const chunkSize = 10;
const testChunks = chunk(publicIds, chunkSize);
let failed = false;
for (let i = 0; i < testChunks.length; i++) {
const testChunk = testChunks[i];
const publicIdArguments = testChunk.map(
(publicId) => `--public-id ${publicId}`
);
const result = await nothrow(
$`npx @datadog/datadog-ci synthetics run-tests ${publicIdArguments} --config ${__dirname}/config.json`
);
if (result.exitCode !== 0) {
console.log(`Failed to run tests for publicIds: ${testChunk}`);
failed = failed | true;
}
}
if (failed) {
console.log("Any of the tests are failed :<");
process.exit(1);
} else {
console.log("All tests are passed :)");
process.exit(0);
}
並列実行することで完了までの時間は短縮されました。
が、割当リソースはそのままなので、並列実行される内容によっては負荷が激上がりし、逆に遅くなるなど不安定な挙動が目立つようになりました。
テスト実行前の手動スケーリング
不安定な挙動を解消するために、テスト実行前に手動スケーリングを行い一時的に十分なリソースを割り当てるようにしました。
AutoScaling で解決しない理由は、単に増加求められるタイミングが明確でコントロールできるからです。
↓な感じです(zx 見せたいだけの記事というのがお分かりになってきたでしょうか)
#!/usr/bin/env -S npx zx
async function updateContainerSize(desiredCount, waitOnCompleted = true) {
$.verbose = false;
await $`aws ecs update-service --cluster xxxxxxxxxxxxx --service yyyyyyyyyyyy --desired-count ${desiredCount}`;
if (waitOnCompleted) {
for (let i = 0; i < 100; i++) {
const serviceStatus =
await $`aws ecs describe-services --cluster xxxxxxxxxxxxx --service yyyyyyyyyyyy`;
const serviceStatusJson = JSON.parse(serviceStatus);
const runningCount = serviceStatusJson.services[0].runningCount;
if (runningCount > desiredCount / 2) {
console.log("Service is running");
break;
}
console.log(`runningCount:${runningCount}, so check again...`);
await sleep(5000);
}
}
$.verbose = true;
return true;
}
// 並列実行されるシナリオ数分スケーリング
await updateContainerSize(chunkSize);
/*
テスト実行する -> 終わったら、コンテナ数は0 or 1個とかに戻す
*/
結果
並列化し、実行直前にスケーリングするというすごく単純な解決策でしたが、これだけでスモークテスト全体の実行時間は 40min → 8min と 1/5 まで下げることが出来ました。
また、一時的にコンテナ数は増えるものの使用時間が減ったことで、総コストが 20~30%くらいは削減できた形となりました(ヤッター)
このようなテストは一度作って終わりではなく維持運用が必要です。
機能変更した際に誰がどのタイミングで変更するのか、新たにシナリオ追加する場合はどのような時かなどポリシーや運用ルールを定義し、開発チームで育てていっています。
実行数に関係なく数分で終わるようになった今、我こそはシナリオ職人だという QA エンジニア方の参画をお待ちしております。
関連情報
もっと詳しく聞いてみたい!と思った方はぜひ カジュアル面談 しましょう。Podcast では弊社の技術チャレンジである「HR のデジタル化」についても語っています。
副業転職の Offers 開発チームがお送りするテックブログです。【エンジニア積極採用中】カジュアル面談、副業からのトライアル etc 承っております💪 jobs.overflow.co.jp
Discussion