CI/CD のための自動テスト

自動テストは継続的インテグレーションとデプロイを支える礎の 1 つです。 継続的デリバリーを DevOps パイプラインに組み込むことで、ソフトウェアの品質を劇的に向上できます。

継続的インテグレーションとデプロイ (CI/CD) の要は、製品やサービスに対して定期的なフィードバックを得ることができるように頻繁かつ段階的に変更を提供することです。 しかしながら、多くの場合は提供の速度と頻度を向上させても製品の品質を下げるべきではありません。 結局のところ、次の斬新な機能を要求しているユーザーでも安定して機能するソフトウェアを期待しているためです。

最新のビルドに自信を持てる、信頼性の高い徹底した自動テストプロセスが CI/CD の実践に欠かせないのはそのためです。

CI/CD テストを自動化すべき理由

ソフトウェアの品質を確保するにはテストすることが基本であり、テストはソフトウェア開発手法の一環として組み込まれてきました。

ウォーターフォール型開発では、コードが開発されて統合された後に手動テストか QA のステージに進みます。

💡NB: ウォーターフォールモデルとも呼ばれているウォーターフォール戦略は、ソフトウェア開発とプロジェクト管理を線形的かつ逐次的に進める手法です。 ソフトウェア開発で使用される最初期モデルの 1 つで、構造化された体系的なフローが特徴です。

この戦略の欠点の 1 つは、コーディングしてからかなり後にならないとコードが意図通りに動作するかを確認できないことです。 確認する頃には元のコードにさらなる追加が行われている可能性があるため、問題の修正がより困難になります。 また、これは新機能とバグ修正の提供が遅くなる原因でもあります。

一方、アジャイル手法ではリリース頻度を上げ、エンドユーザーから得たフィードバックに基づいて次のビルド内容を決定する短期的な対話型の開発サイクルが好まれます。 CI/CD はコーディングからユーザーへのリリースまでのステップを自動化することでこの作業手法を支援し、プロセス全体の信頼性と効率を高めます。

この DevOps の手法を成功させるには、コードの変更を定期的に(理想的には 1 日に数回にわたって)コミット、ビルド、テストする必要があります。 しかしながら、小規模なチームであっても手動テスト一式を 1 日 1 回以上実行するのは非現実的です。 自動テストは CI/CD パイプラインで重要な役割を果たしているのはそれが理由です。

自動テストの作成には最初は時間がかかりますが、すぐに労力に見合った成果を得られるようになります。なぜなら、コミットとデプロイの頻度を大幅に上げられるようになるからです。 自動テストへの投資には次のようないくつかの重要なメリットがあります。

  • 個々のコード変更がチェックされることで、期待通りの動作を確保しつつ新たなバグの発生を阻止できます。
  • 同等のテストを手動実行するよりも迅速にフィードバックを得ることができます。
  • 変更によるバグを発生直後からより効率よく解決しやすくなります。変更内容がまだはっきりと記憶に残っており、他に追加の変更が行われていない状態で対処できるためです。
  • 手動テストよりも信頼できるテスト結果を得られます。人に同じタスクを繰り返し実行するよう依頼した場合では間違いや矛盾の発生が避けられないためです。
  • 自動テストは同時実行できるため、時間が重要な場合に品質保証を (インフラストラクチャの許容範囲で) 強化できます。

テスト自動化によって多くの面倒な反復作業がなくなるにしても、QA エンジニアが不要となるわけではありません。 QA チームは関連するケースを定義して優先順位をつけるだけでなく、自動テストの作成に関与し、多くの場合は開発者とのコラボレーションにも関与する必要があります。 後述する自動化できないテストの部分にも、QA エンジニアの存在が必要なのです。

CI/CDプロセスのどこにテストが組み込まれるのか

自動テストはパイプラインの複数のステージで実施されます。

CI/CD と QA の自動化では、チームができるだけ早期に問題を発見可能な短いフィードバックループが要です。

問題は発見直後に修正する方が楽です。それによって、問題のあるコードにさらなるコードが追加されるのを予防できるからです。 また、次の作業に移って前の作業内容を忘れる前に変更を行う方が効率的です。

多くの自動ビルドテストツールでは、CI/CD ツールとの統合がサポートされているため、ステージごとにテストデータをパイプラインに投入してテストを実施し、ステップごとに結果を確認することができます。 使用するCIツールによっては、前のステップのテスト結果に応じてビルドを次のステージに移行するかどうかを選択することもできます。

自動テストを通過するパイプラインを活用する場合、一般的には最速のテストが最初に実行されるようにビルドテストの順番を決めるのが理にかなっています。 フィードバックをより素早く得ることができるため、テスト環境をより有効に使用することができます。実行時間の長い、より複雑なテストを実行する前に、最初のテストの合格状況を確認することできるからです。

自動テストの作成と実行の両方をどのように優先させるかについて検討する際は、テストピラミッドを考察すると良いでしょう。

テストピラミッドの作成

テストピラミッドは、CI/CD パイプラインの自動テストを相対的なテストの実行数と実行順序の観点から優先順位をつけるためのツールです。

元々は Mike Cohn によって定義された手法であり、底部にユニットテスト、中間にサービステスト、そして上部に UI テストがあります。

テストピラミッドは次のステップで構成されています。

  • 素早く簡単に実行できる自動ユニットテストを強固な基盤として開始します。
  • その後、作成がより複雑で実行に時間がかかるテストに進みます。
  • 最後に少数の最も複雑なテストを実施します。

では、どのような CI/CD テストを考慮すべきなのでしょうか。 オプションを探ってみましょう。

ユニットテスト

テストピラミッドの基盤には、ユニットテストが最適です。 これらのテストは、できる限り最小規模の動作を解決して、コードが期待通りに機能することを保証できるように設計されています。 たとえば天気予報アプリを制作する場合、摂氏から華氏への値の変換はより大きな機能の一部となっているかもしれません。 ユニットテストを使用することで、温度変換関数が値の範囲に対して期待する結果を返すかどうかを確認できます。 このようなユニットテストを使用すると、関連コードを変更するたびにアプリをビルドして実行しなくても、この特定の要素が意図通りに機能することを確認できます。

ユニットテストの作成に時間を投資したチームでは、開発者が関連するコードの追加とユニットテストの作成を兼任するのが一般的です。 テスト駆動開発(TDD)は(後述のように)このプロセスを具体化しますが、TDD はユニットテストを作成するための要件ではありません。

また、ユニットテストを各開発タスクの完了を定義する要素となるように設定し、コードレビューを実行する際やコードカバレッジレポートを使用する際にユニットテストが機能していることを確認する方法もあります。

既存のシステムで作業している場合やユニットテストに投資した経験がない場合は、コードベース全体のユニットテストをゼロから作成するのは克服できない障害のように感じるかもしれません。 ユニットテストではカバレッジを広げることが推奨されますが、まずは小さな部分から着手し、後でカバレッジを追加していくとよいでしょう。

現在のコードに対するユニットテストのカバレッジが不十分な場合は、すべての変更対象コードにユニットテストを追加するようにチームを組んでカバレッジを構築することを検討してください。 この戦略によってすべての新しいコードが確実にカバーされ、最も頻繁に触るものに応じて既存のコードを優先できるようになります。

統合テスト

統合テストでは、アプリケーションとデータベース間、または API への呼び出しなど、ソフトウェアの複数の構成要素が期待通りに連携していることを確認できます。

統合テストは広い範囲のものと狭い範囲のものに分割するとよいでしょう。 狭い範囲のテストでは、実際のモジュールではなくテスト用のダミーを使用して別のモジュールとの連携をテストします。 広い範囲の統合テストでは、実際のコンポーネントかサービスを使用します。 天気予報アプリの例に戻って考えた場合、広い範囲の統合テストは API から予測データを取得する可能性がありますが、狭い範囲のテストは模擬データを使用することになるでしょう。

プロジェクトの複雑度と関連する内外のサービスの数によりますが、複数の狭い範囲のテストから着手することをお勧めします。 このようなテストはシステムの他の部分にアクセスできる必要がないため、広い範囲の統合テストよりも高速に実行(かつより迅速にフィードバックを提供)できるためです。

狭い範囲のテストが正常に完了したら、範囲の広い統合テスト一式を実行できます。 このようなテストは実行に時間がかかり、保守により労力を要するため、比較的優先順位の高い製品やサービスの部分に限定して実行することをお勧めします。

エンドツーエンドテスト

フルスタックテストとしても知られるエンドツーエンドテストでは、アプリケーション全体を確認します。 エンドツーエンドテストは、ユーザーがアカウントを作成できるか、またはトランザクションを完了できるかなど、ビジネスのユースケースの検証に通常使用されます。

自動エンドツーエンドテストは GUI を使って実行できますが、必ずしもそうである必要はありません。API 呼び出しでもシステムの複数の構成要素を実行可能です(ただし、API も統合テストでチェック可能)。

エンドツーエンドテストは実行時間が長いだけでなく、不安定になりがちであるため、テストピラミッドでは、エンドツーエンドテストの数を少な目にするように推奨しています。 ユーザーインターフェースが変更されるとテストが破綻し、結果的にビルドテストに無益で不要なものが入り込み、テストの更新に余計な時間をかけなければならなくなる可能性があります。 そのため、下層のテストでカバー済みの内容を把握してエンドツーエンドを入念に設計し、最大限の価値を生み出す必要があります。

ビヘイビア駆動開発

ビヘイビア駆動開発(BDD)は開発者、テスター、およびビジネスステークホルダー間のコミュニケーションギャップを埋めるソフトウェア開発のコラボレーション手法です。 ソフトウェアの実装よりも動作を重視することで、テスト駆動開発(TDD)を拡張します。

BDD は統合テストとエンドツーエンドテストの両方を開発するのに役立つ戦略を提供できます。 主に次のような特徴があります。

  • 動作を重視: BDD は平易な言葉で例を書いてシステムの動作を明記することを重視します。 このような例は一般的に Given / When / Then 形式で初期状態、アクション、期待される結果が書かれます。
  • コラボレーション: BDD は開発者、テスター、ビジネスステークホルダー間のコラボレーションを奨励し、全員が要件を理解できるようにします。 このコラボレーションプロセスは期待を明確にし、誤解を減らすのに役立ちます。
  • 実行可能な仕様: BDD のシナリオはテストとして自動的に実行できる方法で書かれます。 このようなシナリオを人間が読み取り可能で自動テストに関連付けできる形式で書くには、一般的には Cucumber、SpecFlow、JBehave などのツールが使用されます。
  • ドキュメント作成: シナリオはドキュメント作成にもテストにも役立ち、システムの動作に関する明確な現行の仕様を提供します。 これにより、システムとその要件を理解しやすくなります。
  • 反復的な開発: BDD は継続的なフィードバックと改善を促し、反復的で段階的な開発をサポートします。 イテレーションごとに新しいシナリオの作成と実装を含めることで、ソフトウェアがビジネスのニーズに合わせて進化できるようになります。

パフォーマンステスト

テストピラミッドではパフォーマンステストについて触れられていませんが、安定性と速度が主な要件となっている製品では特に検討する価値があります。

パフォーマンステストでは、一般的には次のような多様なテスト戦略を用いて本番環境でのソフトウェアの動作をチェックします。

  • 負荷テストは需要が増加した際のシステムの動作をチェックします。
  • ストレステストは想定される使用状況を意図的に超えさせて動作をチェックします。
  • ソーク(または耐性)テストは高い負荷が連続的に発生している状況でのパフォーマンスを測定します。

このような多様なテストを使うのは、ソフトウェアが定義したパラメーターに耐えられることを確認するためだけでなく、パラメーターを超過したときの動作をテストするためです(クラッシュするのではなく、安全に失敗するのが望ましい)。

テスト環境

パフォーマンスとエンドツーエンドテストには、本番に非常に近い環境が必要です。ビルドテストデータも必要な場合があります。 信頼できる自動テストの仕組みを作るには、テストを毎回同じ方法で実行するようにすることが重要です。 つまり、どのテスト実行でも常に同じテスト環境を使用し、変更がデプロイされた際にはテスト環境が本番環境と同じになるように更新するようにしなければなりません。

このような環境を手動で管理するには時間がかかる場合があります。 新たにビルドするたびに本番前環境を作成・破棄するプロセスを自動化することで、時間を節約しつつ、安定した信頼できるテストの仕組みを確保できます。

テスト駆動開発は自動テストにどのように役立つのか

テスト駆動開発(TDD)はエクストリームプログラミング(XP)に起源を持つ開発手法です。 TDD の最初のステップでは、追加する機能に対応するテストケースのリストを作成します。 その後は 1 つずつテストケースを選び、そのテストケースに対応する(失敗する)テストを作成し、テストに合格するようにコーディングします。 最後に、必要に応じてコードをリファクタリングしてから次のテストケースに進みます。 このプロセスは「Red, green, refactor(レッド、グリーン、リファクタリング)」または「Make it work; make it right(動かしてから適切に書き換える)」のように要約できます。

TDD の主なメリットの 1 つは、新しく作成したコードすべてに対して自動テストの追加を強制することです。 つまり、テストカバレッジが常に拡大することで、コードを変更するたびに迅速で定期的なフィードバックを得られるようになります。 テスト駆動開発にはその他にも次のようなメリットがあります。

  • 反復的手法が促される。 テストのリストを特定した後に 1 つずつテストケースに取り組み、作業を進めながらテストの残りのリストを改良していくことができます。
  • インターフェースと実装の分離が促される。 「レッド、グリーン、リファクタリング」ワークフローに従うことは TDD の要です。 
  • 作業を進めながらすぐにフィードバックを受け取れる。 最新の変更がテストに合格したことを確認できるだけでなく、他のテストが失敗したこともチェックできます。
  • テストにより意図が明確になり、きちんと解説されたコードを維持できる。 その結果、コードの保守が容易になり、新しいチームメンバーのオンボーディングが高速化されます。

TDD は自動テストのカバレッジを広げて CI/CD プロセスを効果的に支援します。 とはいえ、TDD は効率的な DevOps 戦略の要件ではありません。TDD なしでも自動テストのカバレッジを高いレベルに維持できます。

フィードバックの利用

CI/CD を実践する一環で自動テストを実行する目的は、行ったばかなりの変更に対する迅速なフィードバックを得ることです。 そのフィードバックに耳を傾けて対応することは、CI/CD のプロセスでは不可欠です。 以下のベストプラクティスは自動テストの仕組みを最大限に生かすのに役立ちます。

  • テスト結果に簡単にアクセスできるようにする。 CI サーバーはすべての結果をダッシュボードまたはラジエーター表示で確認できるよう、通常は自動テストツールと統合されています。
  • テストレポート機能を Slack などのメッセージングプラットフォームと統合する。それにより、テスト結果について自動通知を取得できるようになります。
  • テストが失敗した場合は可能な限り早急に原因を突き止める。それにより、失敗したコードにさらにコードが追加される前に修正に取りかかることができます。 このプロセスはテストが関連するコードベースの領域を特定し、スタックトレース、出力値、スクリーンショットなど、テストによって生成されたデータへのアクセスを提供する CI ツールによって高速化できます。
  • 各テストを 1 つの項目をチェックするように設計し、テストに正確にラベルを付けて何が失敗したかを容易に把握できるようにする。

当然ながら、ツールと手法は CI/CD を実現する構成要素の一部にすぎません。 本当に優れた CI/CD 自動化を実践するには、自動 CI/CD テストの価値のみならず、テストの失敗に迅速に対応してソフトウェアをデプロイ可能な状態に維持することの重要性を認識しているチーム文化が必要です。

継続的テストと自動テストの比較

多くのチームは手動で呼び出せる、または単純な継続的インテグレーションパイプラインの一部として呼び出せるユニットテスト一式からテストの自動化に着手します。 DevOps 文化が成熟するつれて、統合テスト、エンドツーエンドテスト、セキュリティテスト、パフォーマンステストなどを追加することで、テストピラミッドを登り始めることができます。

継続的テストはあらゆる種類の自動テストを CI/CD パイプラインの一部として実行する手法を指します。 継続的テストではバグをできる限り早急に発見できるよう、コード変更の各セットに対して自動的に一連の自動テストが実行されます。

継続的テストプロセスの初期段階では、変更がコミットされる前に IDE でテストを実行できます。 継続的テストの後の段階のテストでは、通常はパイプラインの中で自動的にリフレッシュされるテスト環境が必要になります。

継続的テストプロセスを完全に自動化することで、コード変更の信頼性を最大限に高めると同時にリリースを高速化できます。 厳格なテストの仕組みでソフトウェアをテストすることで、バグのリスクを大幅に削減できます。 このテストプロセスを自動的かつ継続的に実行すると作業効率が上がるだけでなく、緊急の修正を迅速かつ確実にデプロイできるようになります。

継続的テストは実現に時間がかかりますが、 CI/CD パイプラインの他の構成要素を自動化してテストのカバレッジを広げながら段階的に取り組める目標です。

CI/CD と自動テストでは手動テストが不要になるのか

CI/CD に不慣れな人たちは、自動テストによって手動テストやプロの QA エンジニアが不要になると誤解しがちです。

CI/CD 自動化によって QA チームメンバーの時間に余裕ができることは確かですが、メンバーが不要になるということではありません。 QA エンジニアは反復作業に時間を費やす代わりに、テストケースの定義や自動テストの作成、さらには探索的テストの創造や工夫に集中できるようになります。

コンピューターが実行できるように入念にスクリプト化された自動ビルドテストとは異なり、探索的テストには凝り固まった制限はありません。 探索的テストは、計画的に構造化されたテスト手法では見逃される可能性のあるエラーを探せることに価値があります。 本質的にはまだ考察されておらず、テストケースも作成されていない問題を探すためのテストなのです。

調査すべき部分を決める際には、新しい機能だけでなく、本番で問題が発生した際に最も悪影響を受けるシステムの部分も考慮してください。 テスターの時間を有効活用するため、手動テストはすべての自動テストが合格した後に実行するようにしましょう。

探索的テストは手動かつ反復的なテストになってはならず、 毎回決まった一連のチェックを実行することを目的としていません。 探索的テストで問題が検出された場合はその問題を修正するだけでなく、1 つ以上の自動テストを作成する必要があります。 そうすることで、問題が再発した場合にプロセスの早い段階で検出できるようになります。

テスト自動化の継続的な改善

テストスイートは一度作ったらそれで終わりというものではありません。 自動テストは保守によって妥当性と有用性を維持しなければなりません。 テストもコードと同じように継続的に改善する必要があります。

新しい機能に対応する自動テストを継続的に追加し、探索的テストの結果を採り入れることで、テストスイートの効果と有効性が維持されます。 また、テストの実行状況を確認したり、テストプロセスの一部を並べ替えたり細分化したりしてフィードバックの提供を迅速化できるどうかを確認したりする時間も作るとよいでしょう。

CI ツールにはパイプラインの最適化に役立つさまざまなメトリクスが用意されていますが、テストのメトリクスが不安定だとテストの信頼性が低くなり、不適切な自信や懸念を生む可能性があります。

メトリクスを使って自動テストプロセスを改善することはできますが、テストのカバレッジ自体が目標だと勘違いしないようにしましょう。 本来の目標は、機能するソフトウェアを定期的にユーザーに提供することです。 自動化は迅速で信頼性の高いフィードバックを提供し、ソフトウェアを本番にデプロイする前に確信を持てるようにすることで、この目標を果たしています。

CI/CD の自動テストについてのまとめ

テスト自動化は CI/CD パイプラインの中心的役割を果たします。 テストを自動的に実行することで、コード変更について迅速で信頼できるフィードバックを得られます。 その結果、バグをより早く発見して修正しやすくなるため、開発効率が上がります。

自動テストの順序は実行所要時間に基づいて決定するのが望ましいです。 最も迅速にフィードバックを提供するユニットテストを最初に実行し、その後に統合テストを実行し、最後にエンドツーエンドテストを実行することをお勧めします。 自動テストがない場合はユニットテストから始めるのが最善です。 テスト駆動開発(TDD)はユニットテストのカバレッジを改善して維持するのに役立つ実証された開発手法です。

DevOps 文化が成熟するにつれて、継続的テストに移行することをお勧めします。 この移行の対象にはテスト環境の作成と保守の自動化が含まれています。 より高いレベルの自動テストを作成する場合は、最もリスクの高い部分を優先させることを検討してください。 これには、負荷テスト、ストレステスト、またはソークテストなどの自動されたパフォーマンステストが必要になる場合があります。 手動の探索的テストは、テストカバレッジのギャップを特定して CI/CD プロセスを継続的に改善するのに適しています。

TeamCity のメリット

TeamCity は複数のテストフレームワークと豊富なテスト自動化機能を幅広くサポートすることで、CI/CD プロセスを最大限に活用できるようにします。

効率的なテスト自動化には速度と信頼性が不可欠であり、TeamCity はその両面において最適化されています。 TeamCity は問題の原因を探るのに役立つ詳細なテストレポートを提供するだけでなく、不安定なテストを自動的にハイライトするため、有効な失敗のみに確実に注目できます。 インテリジェントなテストの並べ替えと同時実行によってさらに迅速に結果を得られるようになり、リモート実行機能によりコミットする前にフィードバックを得られます。

TeamCity は課題トラッカー、IDE、Slack、その他のプラットフォームとの統合できるため、どこで作業していても失敗したテストに関する通知を受け取れます。 さらに、仮想マシンと Docker コンテナーを完全にサポートしているため、テスト環境の管理を自動化し、継続的テストを CI/CD プロセスの一部として実装できます。