こんにちは、Software Engineer in Qualityチーム(通称SEQチーム)の @teyamagu です。 私たちのチームは普段自動/手動テストの基盤開発や開発フィードバックサイクルの高速化に向けた開発をおこなっています。
その一環で、先日、社内でfreeeの自動テストシステム全体像を共有したのですが、この辺りのことを社外の友人達と話したところ、自動テストの具体的な構成や普段の運用など事例が少なく、どんなことをやっているのかイメージしにくいとの話を伺ったので、社内向け原稿をちょっと手直しして、おすそ分けと言うことで、ここで紹介します。
特に変わったことをおこなっているわけではありませんが、自動テストの関係性の理解に参考になれば幸いです。
基本的な考え方
- 自動テストが既存のデプロイ・リリースのブロッカーではなく、開発のフィードバックを加速させるために、自動テストそのものが高速に実行でき、スケールできるようにしています。
- 自動テストシステムにおけるテストスクリプトの作成・運用は、プロダクトマネジャー、プログラマー、テスト・QA担当者で構成される開発チームでおこなうものとします。もちろん、相談はいくらでもお待ちしております。
- ここでの「テストスクリプトの作成・運用」とは、どのような確認したいことを自動テストとするかの決定やテストスクリプト自体の作成、実行結果の確認やテスト対象の変化に合わせたテストスクリプトの修正を指します。
- このことは、テスト対象を最も知っている人がテストを作成・運用することが最も効果的であり、テストしにくさを感じた際にテスト対象へすぐフィードバックできるため、このように設定しています。それだけでなく、開発チームが、自動テストという道具を使って、テスト対象の隅々に意識を持ち続けていけるように、このように設定しています。
- 自動テストのためだけの学習コストや維持コストが極力小さくなるようにしています。そのため、プログラミング言語やCIシステム、コミュニケーションツールなどはなるべく社内で利用されているものを利用していますし、テストシステムをサービスごとに個別最適をあまりおこなっていません(しかし、開発チームがテストフレームワークを自分達で導入することを妨げることはしていません)。テストシステムの改善は継続的におこない、とあるサービスでの不満をもとにテストシステム全体を改善しています。
- 社内で多くのサービスが開発されていますが、大別すると以下の区別でテストシステムの用意を進めています。
- APIか、ブラウザか、モバイルアプリか
- サービス自体がマーケットに受け入れられている状態か、まだプロトタイプを続けている状態か
- 社内で多くのサービスが開発されていますが、大別すると以下の区別でテストシステムの用意を進めています。
自動テストの大別とその量
現時点(2022/08/01)のfreee社内では大まかには下記のような3つの自動テストがあります。
- メソッドやクラス、コンポーネントを直接操作して確認するテスト。多義性があるので人によってイメージが異なると思いますが、一般的にユニットテストと呼ばれるもので、本稿ではこのテストを以降ユニットテストと称します。
- APIやシステムの中間層を操作して確認するテスト。本稿ではこのテストを以降APIテストと称します。
- ブラウザやモバイルアプリを操作して確認するテスト。本稿ではこのテストを以降E2Eテストと称します。
そして、これらの自動テストの量としては、下記になっており、今後APIテストの拡充に向けた整備をおこなっていこうと思っています。
- ユニットテスト: 大量
- APIテスト: 少量
- E2Eテスト: ほどほど
それぞれの自動テストでのテスト対象と確認内容
ユニットテストのテスト対象と確認内容
フロントサイドやサーバサイドに限定することなく、メソッドやクラス、モジュール、コンポーネントが意図通りに実装できていることや機能適合性、欠陥修正の確認や意図しない出力結果の変化がないこと、ある程度の単位での構造の確認をしています。 そのため、ユニットテストがおこなっていることをテストレベルやテストタイプで表現すると、ほとんどのものが以下となっています。
- テストレベル:コンポーネントテストや統合テスト
- テストタイプ:機能テスト・ホワイトボックステスト・変更部分のテスト
APIテストの確認内容
APIが開発者の意図通りに実装できていることや機能適合性、欠陥修正の確認や意図しない出力結果の変化がないことを確認しています。合わせて、APIテストでは、パフォーマンスやセキュリティに関する確認もおこなっています。 サービスの作りとしての加えて、お客様に提供している基本的なAPIに関しては、そのAPIがテスト環境だけでなく本番環境でも異常なく提供できているかを継続的に確認しています。 この自動テストは継続的におこなうため、レスポンス速度が遅くなっていることがわかったりすることもありますが、そのような点は目的ではなく副次的効果としています。 そのため、APIテストがおこなっていることをテストレベルやテストタイプで表現すると、ほとんどのものが以下となっています。
- テストレベル:コンポーネントテストや統合テスト
- テストタイプ:機能テスト・ホワイトボックステスト・変更部分のテスト
E2Eテストの確認内容
各サービスがブラウザおよびモバイルアプリとして動作することを確認しています。確認する動作としては、お客様の基本的な操作シナリオやサービスの基本的な機能が意図通りに実現できることや欠陥修正の確認、意図しない変化がないことなどとしています。動作しなかったら高レベルの障害となるような利用ユースケースを確認するものと考えると考えやすいかも知れません。 API同様、この自動テストは継続的におこなうため、レスポンス速度が遅くなっていることがわかったりすることもありますが、そのような点は目的ではなく副次的効果としています。 そのため、E2Eテストがおこなっていることをテストレベルやテストタイプで表現すると、ほとんどのものが以下となっています。
- テストレベル:システムテスト・受入テスト
- テストタイプ:機能テスト・変更部分のテスト
上記以外での自動テストによる確認内容
(一例)会計各種レポートの計算結果
会計の各種帳簿の計算結果は重要でありながら、様々な入力や設定などにより計算は膨大なバリエーションがあります。そこで、計算が正しく行われるか確認するための自動テストをおこなっています。 このテストは、会計士や税理士などドメインに詳しい社内のメンバーがバリエーションを効果的に作成、確認できることが望ましいため、今まで紹介した3種のテストとは別に、専用の自動テストシステムを構成しています。具体的には、入力や設定である取引データや事業所データ、また、正解の計算結果などをSpreadsheetに記載し、それを元に自動テストスクリプトの自動生成や成否確認などをおこなっています。そして、この自動テストはデプロイパイプライン中で自動実行をしています。
個々のテストの構成
ユニットテストの構成
- テストフレームワーク
- プログラミング言語のユニットテストフレームワークやパッケージ(RSpec, golang testing, Jestなど)
- テスト対象と同様のプログラミング言語を選択しています
- 大量にある場合はKnapsackを利用して、効率的に並列実行されるようにしています
- プログラミング言語のユニットテストフレームワークやパッケージ(RSpec, golang testing, Jestなど)
- テスト対象の実行環境
- 開発環境およびCI環境に構築した各サービス
- テスト実行環境
- 開発環境とCircleCI, GitHub Actions
- テスト実行方法とタイミング
- 開発中に、開発者による開発環境でのコマンド実行
- 各Pull Requestのコード更新時の自動実行
- テスト環境デプロイ前の自動実行
- リリースワークフローでの自動実行
- テストの実行単位
- 開発環境のコンソールでのみ個別テスト実行可能。その他はサービス単位での実行
- テスト結果の確認方法
- パイプラインでの実行結果はSlack, Pull Request上, CircleCIやGitHub Actionsで確認可能
- SlackはAll Pass or Failの即時把握用。CircleCIやGitHub Actionsは詳細及び傾向把握用
- パイプラインでの実行結果はSlack, Pull Request上, CircleCIやGitHub Actionsで確認可能
- テストスクリプトの管理方法
- 各サービスのリポジトリ内で、サービスのコードと同様に管理しています
- テストスクリプトの作成・運用者
- プログラマー
APIテストの構成
- テストフレームワーク
- 確認したいことや人によって利用しているテストフレームワークが複数あり、Postman(そのCLI実行Newman, gRPCurlを使ったgRPC含む)およびプログラミング言語が持つテストフレームワークを利用しています。
- テスト対象の実行環境
- 開発環境およびCI環境に構築した各サービス、常設のテスト環境で動いている各サービス
- テスト実行環境
- プログラミング言語のテストフレームワークで実装したAPIテストは、ユニットテストの仕掛け(開発環境とCircleCI, GitHub Actions)に相乗りしています。
- Postman, Newmanで実装したAPIテスト(gRPC向けも含む)は、一部は開発環境で実行し、一部はE2Eテストの仕掛け(常設のテスト実行用Jenkins環境)に相乗りしています。
- テスト実行方法とタイミング
- プログラミング言語のテストフレームワークで実装したAPIテスト
- ユニットテスト実行方法・タイミングと同様です
- Postman, Newmanで実装したAPIテスト
- 開発環境で実行されるテストは、開発中に、プログラマーやテスト・QA担当者によるPostmanおよびコマンド実行
- E2Eテストの仕掛けで実行されるテストは、E2Eテスト実行方法・タイミングと同様です
- プログラミング言語のテストフレームワークで実装したAPIテスト
- テストの実行単位
- 開発環境のコンソールでのみ個別テスト実行可能。その他はサービス単位での実行
- テスト結果の確認方法
- パイプラインでの実行結果はSlack, Pull Request上, CircleCIやGitHub Actionsで確認可能
- SlackはAll Pass or Failの即時把握用。CircleCIやGitHub Actionsは詳細及び傾向把握用
- パイプラインでの実行結果はSlack, Pull Request上, CircleCIやGitHub Actionsで確認可能
- テストスクリプトの管理方法
- 実行環境や実行指示などは、Postman, Newmanで実装したAPIテストはE2Eテストの仕掛けに相乗りし、テストフレームワークで実装したテストはAPIテストはユニットテストの仕掛けに相乗りしています。
- テストスクリプトの作成・運用者
- プログラマーやテスト・QA担当者および私たちのチーム
E2Eテストの構成
E2Eテストはテストシステムを使い分けているため、それぞれで説明します。
WEB(メイン)
リポジトリで独自に導入しているテストフレームワークと社内でメンテナンスしているテストフレームワークが主にあります。
リポジトリで独自に導入しているテストフレームワークは、現状把握している限りはCypressとPlaywrightの2つであり、それらの実行環境や実行タイミングなどは開発チームでコントロールしています。
社内でメンテナンスしているテストフレームワークは以下の通りの実行環境や実行タイミングとなっています。
- テストフレームワーク:
- RSpec, Capybara, SitePrismをベースに独自のメタプログラミングやサポートメソッドを多少追加したもの
- サポートメソッドは2段階構成。サービス固有のサポートメソッドと全サービス共通のサポートメソッドとなっています。
- 基本的には、2サービスで利用されるようになったサポートメソッドを全サービスのサポートメソッドに移動させる
- サポートメソッドの内容としては、非同期待ちやダウンロードなどブラウザの一般的操作もしくはドメインごとの抽象表現したもの「サインアップ」「ログイン」「プラン購入」など
- サポートメソッドは2段階構成。サービス固有のサポートメソッドと全サービス共通のサポートメソッドとなっています。
- テストスクリプトでの expect 等による比較だけでなく、Applitoolsを利用した画面比較やdiff-pdfを利用したPDFファイルの比較もおこなっています。
- 専用のlinterやCode Style Guideなども作成し、提供しています
- RSpec, Capybara, SitePrismをベースに独自のメタプログラミングやサポートメソッドを多少追加したもの
- テスト対象の実行環境
- 開発環境およびCI環境に構築した各サービス、常設のテスト環境で動いている各サービス
- テスト実行環境
- 複数のEC2で構成したJenkinsのMain, replica構成
- replicaによって多少カスタム設定や役割を持たせています
- 複数のEC2で構成したJenkinsのMain, replica構成
- テスト実行方法とタイミング
- 開発中に、プログラマーやテスト・QA担当者によるコンソールやSlackでのコマンド実行
- テスト環境デプロイ時の自動実行
- リリースワークフローでの自動実行
- 毎朝の定時自動実行
- テストの実行単位
- サービス単位と個別テストどちらも可能。
- 自動実行時はサービス単位で実行。サービス横断する自動テストも実行される。
- テスト結果の確認方法
- コンソール以外での実行結果はSlack、Allure、TestRail、Jenkinsで確認可能。
- Slackは即時把握用、Allureは自動テスト単体把握用、TestRailは手動テストと合わせた形での状況把握用、Jenkinsは実行時のコンソール出力・Fail時スクリーンショット確認用(JenkinsへはSlackからワンクリックで対象の実行結果へ飛べる)
- 中長期変化計測用に、結果をDBに保存しredashで観察しています。
- コンソール以外での実行結果はSlack、Allure、TestRail、Jenkinsで確認可能。
- テストスクリプトの管理方法
- rubyで記述され、E2Eのテストシステムと同一のリポジトリに全サービス分を管理しています。
- 全サービスのE2Eテストコードを同一のリポジトリ上で管理することで、おこないたい処理のカタログ化を実現しています。おこないたい処理を検索することで、他のサービスで実現したものが見つかり、それを参考にテストスクリプトを作成できます。
- rubyで記述され、E2Eのテストシステムと同一のリポジトリに全サービス分を管理しています。
- テストスクリプトの作成・運用者
- プログラマーやテスト・QA担当者
WEB(サービスがプロトタイプを続けている状態)
サービスがプロトタイプを続けている状態ではリリースも不定期で少なく、また変更も多いため、テストスクリプトを使い捨てするものと決めて、レコードアンドリプレイ型のSaaSとして mabl を利用しています。
個人的には、どこかでレコードアンドリプレイ型のSaaSの効果が独自メンテナンスの環境を越えていくと思っていますが、現時点ではまだ管理運用系の機能や実行単位でのコストなど足りない点が多いため、一部の利用としています。
- テストフレームワーク:
- レコードアンドリプレイ型のSaaS: mabl
- テスト対象の実行環境
- 常設のテスト環境で動いている特定サービス
- テスト実行環境
- SaaS環境
- テスト実行方法とタイミング
- 開発中に、テスト・QA担当者によるSlackでのコマンド実行
- リリースワークフローでの自動実行
- テストの実行単位
- サービス単位
- テスト結果の確認方法
- SaaS上およびSlack、Jenkinsで確認可能。
- テストスクリプトの管理方法
- SaaS上
- テストスクリプトの作成・運用者
- テスト・QA担当者
モバイルアプリ
- テストフレームワーク:
- WEBメインとほぼ同じフォーマットで作成できるようにしています。
- RSpec, Capybara, appium_capybara, SitePrismをベースに独自のメタプログラミングやサポートメソッドを多少追加したもの
- サポートメソッドは2段階構成。サービス固有のサポートメソッドと全サービス共通のサポートメソッドとなっています。
- 基本的には、2サービスで利用されるようになったサポートメソッドを全サービスのサポートメソッドに移動させる
- サポートメソッドの内容としては、非同期待ちやダウンロードなどブラウザの一般的操作もしくはドメインごとの抽象表現したもの「サインアップ」「ログイン」「プラン購入」など
- WEBメインとほぼ同じフォーマットで作成できるようにしています。
- テスト対象の実行環境
- BrowserStack上の端末にインストールした各モバイルアプリ
- 端末の種類やOSバージョンはテスト実行時に設定可能
- BrowserStack上の端末にインストールした各モバイルアプリ
- テスト実行環境
- 複数のEC2で構成したJenkinsのMain, replica構成
- 実行replicaを限定し、その並列数をコントロールすることでBrowserStackのライセンス数に合わせています。
- 複数のEC2で構成したJenkinsのMain, replica構成
- テスト実行方法とタイミング
- 開発中に、プログラマーやテスト・QA担当者によるコンソールやSlackでのコマンド実行
- WEBサービスデプロイ時の自動実行
- 毎朝の定時自動実行
- テストの実行単位
- サービス単位と個別テストどちらも可能。
- 毎朝の定時自動実行時はサービス単位の全テスト。WEBサービスデプロイ時は重要な一部のテストのみを実行。
- テスト結果の確認方法
- コンソール以外での実行結果はSlack、Allure、Jenkins、BrowserStackで確認可能。
- Web(メイン)とほぼ同様。差分はBrowserStack程度。BrowserStackは実行時の画面遷移動画、必要時にはネットワークやappiumログなどの確認用(BrowserStackはJenkinsコンソール出力内に当該実行結果リンクがある)
- 中長期変化計測用に、結果をDBに保存しredashで観察しています。
- コンソール以外での実行結果はSlack、Allure、Jenkins、BrowserStackで確認可能。
- テストスクリプトの管理方法
- rubyで記述され、モバイルアプリのE2Eのテストシステムと同一のリポジトリに全サービス分を管理しています。
- 全サービスのE2Eテストコードを同一のリポジトリ上で管理することで、おこないたい処理のカタログ化を実現しています。おこないたい処理を検索することで、他のサービスで実現したものが見つかり、それを参考にテストスクリプトを作成できます。
- rubyで記述され、モバイルアプリのE2Eのテストシステムと同一のリポジトリに全サービス分を管理しています。
- テストスクリプトの作成・運用者
- プログラマーやテスト・QA担当者
普段の運用としておこなっていること
以下では、自動テストの運用をイメージして貰うために、普段の自動テストの運用としておこなっている作業のうち、いくつかをピックアップして紹介します。作業をおこなうのは開発チームや私たちのチームなど様々です。 前提として、freeeは複数のサービスを有していて、それぞれのサービスが様々な頻度(日に複数回〜週に1,2回)でリリースしており、それに合わせた自動テストとなっています。1つの数字として、1ヶ月間で、E2Eテストは延べ25000シナリオ〜35000シナリオが実行されています。以下のグラフはE2Eテストの月次実行回数です。1〜5シナリオ程度を含んだ1つのテストファイルがいつ何回実行されたかを示しています。
自動テストの失敗時の原因確認
自動テストを導入することによって新たに生まれる作業が自動テストの失敗時の原因確認です。ユーザーに提供する機能的には変化がない場合でもサービスの非同期処理の実装方法の変化などにより、自動テストは失敗してしまうことがあります。非同期処理の場合は、実行時のサービスのリソース状況により変動し、自動テストが成功するケースと失敗するケースが発生します。 リソース変動に耐えられるようテストシステム内の工夫や安定して動作する書き方のレクチャーなどでこの作業は減ってきています。
成功率が下がってきたら安定化施策を実施する
失敗時の原因確認で紹介したように、サービスの変化などにより、自動テストは失敗してしまうことがあります。このようになると、自動テストが失敗した際の原因調査の回数が増えると共に要因が複雑になります。そこで、成功率が下がってきたら、安定して自動テストが実行できるように、テストスクリプトやフレームワーク、サービス実行環境などを修正しています。これをおこなうために、上記のテストの構成で示したように、継続的に実行した結果を収集し、状況を見るようにしています。
E2Eテストでは、現在は経験的に全体のテスト成功率が93−94%を下回ってきたら、また、個々のテスト成功率が5−10ポイント程度下がってきたら確認するようにしています。以下の図は、あるときの直近1週間内でのE2Eテスト失敗率ベスト20の上位を抜き出したものです。テストファイル名はマスキングさせていただいてますが、このようなリストを見ながら失敗しやすくなっているものを対象に状況確認および安定化施策を実施しています。
サービスごとのテストの実行時間が延びてきたら、短縮させる
サービスは日々進化していて、それに合わせて各種自動テストも増えていきます。そのような状態で、単純に自動テストを直列実行すると前記のような日々のリリースの障壁となってします。そこで、自動テスト群は基本的にテストスクリプトのファイル単位で並列実行できるようにしています。ユニットテストはCircleCIのテスト用コンテナの並列実行、E2Eテストの並列実行は実行replica指定やグルーピングなど細かな条件指定ができる、Jenkinsが持つキューと空いているworkerへの割り当ての機構を使って実現しています。 しかし、それでもサービスごとのテストの実行時間は延びてきます。その際には、1ファイル内の複数存在するテストシナリオを複数のファイルへの分割やテスト実行並列数の増加とテスト対象環境のリソース上限の引き上げ、時間のかかっているテストでの時間がかかる部分の短縮の検討などをおこなっています。これも成功率同様、日々の結果を継続的に収集し、状況を見ています。以下の図はテストの平均・最大時間をグラフにしたものです。ここ2ヶ月で平均値が少し右肩あがりになってきて原因調査をしたり、対策を考えたりしています。
今後の予定
フィードバック高速化およびE2Eテストの実行が気軽にできるようにするために、Pull Request上でE2Eテストが実行できたり、変更されたサービスに簡易的に触れるような環境の開発を進めています。現状は、特定サービス群のPull Request上でE2Eテストを実行できる仕掛けが開発できたので、アンケートを取りながら改善および対象サービスを増やしていく予定です。