組み合わせテストの組み合わせを減らすアプローチ

 組み合わせテストは、「組み合わせ爆発」という言葉がある通り、テストケースの規模が大きくなりがちです。それに付随して、開発のスピードやコスト、必要リソースに悪影響を及ぼすこともあります。
 そのため、組み合わせテストの組み合わせ削減は、テストケースの削減、そしてそれに伴うリソースや期間の削減に貢献しやすい領域です。

 今回は、その組み合わせテストの組み合わせを削減するアプローチについて解説します。

組み合わせテストの組み合わせを削減するアプローチ

パラメータと値を削減する。組み合わせを実現不能にする

 パラメータ(因子)を減らしたり、パラメータが取りえる値(水準)を減らしたりすると、テストすべき組み合わせの数を直接減らせます。

 このパラメータや値の削減手段として、最もインパクトあるのが、機能や設定、構成仕様の削除です。価値がない、使われていない、ビジネスが成り立たないような機能・設定・対応環境を削除することで、パラメータや値を根本から削除できます。
 なお、この手段はユーザやビジネスへの影響が大きく、単なるテストの効率化目的では推進困難です。現実的には、ビジネス観点での削除判断に、組み合わせテスト効率化がのっかるアプローチが取られます。ただし、組み合わせテストの変動は、ビジネス上の判断する上でどれぐらい品質保証が楽になるかの判断材料になります。

 次に、パラメータや値の組み合わせを実現不能にする実装で、組み合わせ数を削減できます。
 まず外部からの入力に対しては、入力の制限や、入力バリデーションといった防御プログラミングの推進で、取りえるパラメータや値、組み合わせを削減できます。例えば簡単な例ですが、文字入力ならば、テキストボックスの設定で入力できる文字数や文字種を縛ることで、値のパターンを削減できます。さらに画面を分けて同時入力を不能にする、特定の選択で入力項目をグレーアウトして操作不能にするといったもので、組み合わせを実現不能にできます。
 またある程度制御できる内部インターフェースからの入力については、契約による設計の推進が有効です。適切な事前条件を具体化して遵守することで、事前条件を満たさない組み合わせを組み合わせテストから排除できます。さらに、アーキテクチャ上の境界の結合性を低く設定することでも、パラメータや値の削減に貢献します。具体的には、簡単なものならグローバルスコープの変数削減や無駄な引数の削除、複雑なものなら並行処理の保護(並行処理は結合性の悪化を通して組み合わせを複雑化させがちです)や、入出力の抽象化・カプセル化といったものがあります。

 なお、留意事項として、組み合わせを実現不能にして組み合わせテストを減らすアプローチでは、組み合わせが実際に実現不能であることを確認・保証する活動が求められます(HAYST法でいう禁則のテスト)。前述のテキストボックスの例では、単機能テストで入力不能なことを確認する必要があります。契約による設計の推進では、Assertionによる事前条件確認やコードレビューなどが必要になります。

組み合わせに起因するプロダクトリスクを下げる

 パラメータと値の削減、実現可能な組み合わせの削減に次ぐ有効策が、組み合わせについてのプロダクトリスクの削減です。というのも、求められるテスト網羅基準の厳しさはプロダクトリスクと連動するためです。プロダクトリスクを削減できれば、組み合わせの網羅基準の緩和につながります。

 組み合わせのプロダクトリスクを下げるアプローチの一つは、パラメータ間の結合性を下げ、特に関係のないパラメータは明確に結合を分離する、結合性の設計・実装の工夫の推進です。このアプローチはプロダクトリスク事象の発生可能性の削減に貢献します。
 その中でも、プロダクトリスクの高い機能やコンポーネントを、他のものから結合度低く分離する設計アプローチが有効です。例えば認証、プライバシー、課金などトラブル時の損害に紐づくようなハイリスクなコンポーネントは、アーキテクチャ上でリスクの漏出を防止するバウンダリを設けて結合を分けると、それ以外のローリスクなコンポーネントとの組み合わせのプロダクトリスクを削減できます。

 次に、プロダクトリスクの源を置換・除去するアプローチも、一部の高信頼性開発で有効です。例えばプロダクト中の有毒成分(例えば製造装置の触媒など)を毒性の低いものに入れ替える、高出力装備(例えば放射線射出装置など)の出力を低いものに変える、といったものです。これは、組み合わせを含めた、プロダクトリスク事象の影響度の削減に貢献します。

 上記の対策をまとめると「組み合わせに関わるプロダクトリスクを局在化し、リスクレベルを下げる」のが組み合わせテストの削減に有効なアプローチになります。

組み合わせテストが得意な他活動にテストの責務を渡す

 QA活動・テスト活動の中には、特定の組み合わせテストを効率的にこなせるものがあります。例えば自動テストは、入力条件を変えながら同じ機能実行を何度も繰り返すテストが得意であり、それに当てはめられる組み合わせテストを効率よくこなせます。
 ですので、そうした組み合わせテストが得意なQA・テスト活動を導入して、テストの責務を渡してしまえば、自分たちの組み合わせテストのテストケースを削減できます。

 例えばカメラの顔認識AFのようなアルゴリズムは、膨大な組み合わせ条件を持ちます。そのような場合でよく取られるのが、次の2つのテストで膨大な組み合わせテストを効率的に対処する方針です:

 そしてアルゴリズムの膨大な組み合わせテストの責務を上記の2つの自動テストに移動させる形で、実機を使ったシステムテストの組み合わせテストを削減します。

詳細なテストが必要な個所はピンポイントのテストを充実させ、全体の一律網羅を緩和する

 プロダクトリスクは、機能やコンポーネントによってプロダクトリスクに濃淡があります。
 例えば一般的なサービスでも、ビジネスの成否を左右する重要機能や、バグが発生した時の損害の大きい機能はプロダクトリスクが高く、それ以外の補助的な機能はプロダクトリスクが低いです。
 また組み合わせの仕様についてもプロダクトリスクに濃淡があります。結合性が強いパラメータのセットは、組み合わせに起因するプロダクトリスクが高く、結合性が低いパラメータのセットは、組み合わせのプロダクトリスクが低い傾向があります。

 そのため、特に詳細なテストが必要な箇所については、ピンポイントでテストを充実させると、他の箇所の組み合わせテストの網羅基準を緩和できます。例えば次のようなアプローチです:

●ピンポイントと一律網羅のテスト設計技法の組み合わせ

 結合性が高いパラメータの組み合わせについては、デシジョンテーブルテスト等を使って詳細な網羅を行い、結合性が低い組み合わせについては、シングルワイズテストやペアワイズテストで組み合わせを減らしたり、組み合わせをテストしなかったりするアプローチです。これにより後者の組み合わせの網羅基準を緩和しやすくなります。

●複数の抽象度や網羅基準の組み合わせ

 例えば、リスクが高い機能については、詳細なパラメータ・値で組み合わせテストを設計し、リスクが低い機能については、抽象化したパラメータや値(例えば値をグルーピングしてまとめる。これにはクラシフィケーションツリー法が有効です)でテスト設計するアプローチです。これにより後者の組み合わせテストのテストケースを削減できます。

注意:効果的な組み合わせテストの削減には、組み合わせ起因のプロダクトリスクの削減が不可欠

 前述のアプローチの多くでは「ピンポイントのテストの拡充で組み合わせテストを削減」「他のテストにテストの責務をまわして自分たちのテストを削減」のような、テストの役割分担で組み合わせテストを改善するものがありました。

 注意として、このような役割分担の工夫による組み合わせテスト削減は、組み合わせに起因するプロダクトリスクをマネジメントし、改善してこそ推進できます。
 例えば、ソフトウェア開発においては、BOF等のメモリバグ、デッドロックといった並行処理バグ、リソース占有といった、結合性の設計の工夫を根本から破壊するバグがあります。こうしたバグがありふれていると、パラメータ間の結合性が弱い・強いの判断は困難になります。例えばフロントエンドの描画のコーディングミスで、構造的に分離されている重要コンポーネントの動作が異常になるといったことも起こりえます。そうした状況では「他でテストするので、フロントエンドのテストは削減してよい」みたいな判断が難しくなります。
 こうしたものに対策し、「組み合わせに関わるプロダクトリスクを局在化し、リスクレベルを下げる」アプローチを推進することが、テスト設計の改善につながります。

現代的なユニットテストでのコードカバレッジ(テストカバレッジ)の扱い方

 ユニットテストのコードカバレッジ(テストカバレッジステートメントカバレッジやC0、C1など)は、不適切な運用が根強く見られます。多いのが、コードカバレッジの確保だけをテストの十分性目標にして、まずいテストを書いてしまうパターンです。
 今回はこのコードカバレッジについて、現代的な開発を支えるための適切な運用について解説します。

コードカバレッジのみを直接のテストの十分性の目標にしてはいけない

 結論から言うと、まずユニットテストは以下を目標として作成します。

  • ふるまいの仕様が実現されているか確認する
  • プログラマが感じる品質リスク(いわゆる不吉な臭い等)が許容できる水準であることを確認する
  • 法規制対応など、外部からのテスト要求に対応する

 コードカバレッジは、上記目標を達成することで副次的に確保されることを目指します。

 注意点として、コードカバレッジの確保のみを直接の目標にすると弊害が大きくなります。あくまで上記目標の達成が妥当かの参考指標、副次的目標として活用します。参考指標としての活用例を以下に示します:

  • ふるまいの仕様に対して網羅的なテストを作ったのに、コードカバレッジが低い場合は、以下の問題がないか調べる
    • 仕様からはずれた冗長なコードがある。極端な例では、実行できないデッドコードがある
    • 明示化されていない暗黙の仕様がある。それがテストの不足を引き起こしている
    • コードのテスト容易性に問題がある
  • 継続的に実行しているテストのコードカバレッジが下がった場合は、以下の問題がないか調べる
    • 適切なテストを書かないまま、プロダクトコードを追加・変更している
    • テストが意図通り実行されていない可能性がある。例えばテストのブロックが発生している
    • Flaky Testといったテストの信頼性の問題が発生している

 注意事項として、コードカバレッジの確保が無価値というわけではありません。まず上記のような参考指標として活用できます。またPythonといったインタプリタ言語では、高いコードカバレッジを確保したテストは、最低限実行できるコードであることを保証する、コンパイルチェックの代替となります。リグレッションテストとして信頼してよいかの補助指標にもなります。
 あくまで、コードカバレッジだけを目標としてはいけない、というのが要点です。

コードカバレッジのみを直接の目標にしてはいけない理由

 アジャイル、CI/CD、DevOpsといった、継続的に・反復的にソフトウェアを作りこむ現代的な開発では、ソフトウェア開発の持続可能性・保守性の確保が重要です。テストコードの実装でもそれは同様であり、次のようなテストの品質の確保が重要になります。

  • 冗長さがなく、コンパクトなコードで的確にテストの目的を満たしている。
  • テストの意図が明確で、変更時にテストをどのように保守すればよいか理解できる。
  • テストが安定していて、継続的にソフトウェアを変更する中でも壊れにくい。

 コードカバレッジのみを直接の目標にしてはいけない理由は、上記の品質確保を阻害するためです。具体的には次のような問題を発生させます:

  • コードの仕様とは無関係のテストを誘発する。テストコードを読んでもなんのためのテストか分かりにくい状態を引き起こす。またソフトウェアの価値に貢献しない無駄なテストを許してしまう。極端な例として(過去に見たものですが)Assertionのない実行するだけのテストや、testcase_1、testcase_2のような連番で構成されるテストメソッド名のようなものを生んでしまう。
  • テストが構造に強結合し、コードの変更に弱くなる。例えばふるまいを変更させないリファクタリングでもテストが壊れることがある。

 このような問題の存在から、コードカバレッジは、他の目標達成の過程で達成を目指す、副次的な目標として扱うべきです。

コードカバレッジが不足している場合にどうするか

 テストのないレガシーコードを扱ったり、テストが壊れた状態をリカバリしようとしたりする場合などで、コードカバレッジが低い状態に対応しないといけない場面が度々あります。

 そこではCover&Modifyのためにテストの網羅性改善が必要になりますが、そうした場合でもコードカバレッジのみ見てテストをつぎ足すアプローチでは前述の問題を誘発しがちです。
 手間が増えるため十分に推進できない場面がありますが、理想的には、次のアプローチを取るのが無難です。

  1. テストの目的に立ち戻り、ふるまい仕様や品質リスク等に対してどのようなテストケースが必要か、テスト設計する
  2. 上記に基づいてテストコードの整理や追加を行う
  3. テストコードの整理や追加の副次的効果として、コードカバレッジが改善していることを確認する

 あくまでテストの目的に立ち戻り、それに基づいてテスト設計を整理するのが基本アプローチとなります。

抽象レベルの高いテスト設計(テストアーキテクチャ設計など)の構成要素にテストケースセットを選んではいけない

大規模な開発では、抽象レベルの高いテスト設計(いわゆるテストアーキテクチャ設計が代表例)を通して、大規模なテストを中小規模のテスト要素に分割するアプローチがとられます。
例えば次ようなものです:

  • システムオブシステムズのレベルで、各企業組織など独立性のあるプロジェクトごとにどうテストを分担するか分担設計する
  • プロジェクト全体のレベルで、システムテスト、各結合テストユニットテスト等をどうするかテストレベル設計を行う
  • 各テストレベルごとに、どのようなテストタイプやテストコンテナ(例えばセキュリティテスト、性能計測、周辺機器接続テストなど)を構成するかテスト設計する

この抽象レベルの高いテスト設計では、現場や教材によって「分割するテスト要素を何にするか」にバリエーションがあります。例えばテストタイプだったり、テストコンテナだったり、テストスイートだったりといった感じですす。

これについて一点注意があるのですが、その「テスト要素」をテストケースセット(テストケースのグループ)にすると危うい場合があります。

理由:手動のテストケースは「全て実施されOKになること」が保証されない

理由ですが、設計した手動テストのテストケースは、全て実施されOKになるとは限らないためです。
テストの現場経験を長く積んでいる方なら自明だと思いますが、例えば大規模な手動テストでは、以下のような対応が行われることが珍しくありません:

  • リリース間際で仕様変更を行った際に、変更部分とその影響範囲に特化してテストケースの一部を抜き出しテストする。テストケースのフルセットの再実施はしない
  • 開発遅延中のデバッグで、デバッグ部分に対するテストを探索的に実施する。テストケースの厳格な再実行はしない

その他、リリースに追われている状況下で制約でテストがブロックされている状態に対し、テストケースが求めるテスト条件を一部緩和してテストを進めるといった対応が行われることもあります。

上記のような対応下では、リリース版のソフトウェアテストに対して、設計したテストケース全てをフル実行してOKとなることが保証されません
代わりに、直面した制約(例えばリリースが迫っていて工数確保できない)に対応しながら、テストの責務に基づいてテストの取捨選択・補完を行います

そうした各テスト要素ごとに柔軟な状況や制約対応を行うためには、各々のテストの責務を自明にする必要があります。ここでいう「テストの責務」は、具体的には以下で構成されるものです:

  • テストの目的
  • テストのスコープ
  • テストの十分性についての目標や基準
  • 対応すべきテストの課題

ここから導き出される留意点ですが、テスト設計でテストケースやテストケースセットまで成果物を落とし込むと、上記のような責務情報がアウトプットから抜けてしまします。
この責務情報の欠落は、十分に細分化された詳細なテスト設計や自動テストでは問題にはなりにくいです。ただ抽象度の高いテスト設計で欠落させると、工数不足などでテストケースを取捨選択する際に、上位のテスト設計で欠落した目的や責務をサルベースする作業が発生することになります。

一応、各テストケースの優先順位を設定したり、横断的な選択基準を設けたりすることで、テストケースセットの状態でもある程度制約対応できるようになります。ただこのアプローチは細かな調整に限界があり、テスト規模に対してスケールしなくなります。

ですので、抽象度の高いテスト設計では、個々のテスト設計要素ごとに「テストの責務」を明確にすることが重要になります。

アーキテクチャ設計をメタファーにテストアーキテクチャ設計を解説する際の注意点

テストアーキテクチャ設計は、通常のアーキテクチャ設計をメタファーに解説されているのをしばしば見ます。

これについて注意点があるのですが、アーキテクチャ設計の最終的な成果物であるコードは、作れば作った通りの動作をします。しかしテストアーキテクチャ設計の最終的な成果物であるテスト実装成果物は、手動テストならば作った通り実行されるわけではありません。その先の状況・制約対応も加味する必要があります。いうなれば、アーキテクチャ設計は構造の設計をしていますが、テストアーキテクチャ設計はテスト設計活動の設計をしているのです。
メタファーとして活用する際には、この差異に注意が必要だと思います。

CI/CD方針、テスト・QA方針と連動する三分類ブランチ管理方針で、開発での高品質と高スピードの両立を支える

最近の開発では、CI/CD、自動テスト、継続的テストが当たり前となっていますが、その影響で、それらのCI/CD方針、テスト方針と、Git等のバージョン管理のブランチ方針をどう連携させるかが、定番の課題になっていると感じています。
今回は、このブランチ方針、CI/CD方針、テスト方針を連携させて、開発の品質とスピードを向上させるアプローチについて解説します。

結論から言うと、要点は以下の二つとなります。

  • バージョン管理のブランチ方針は、CI/CD方針、テスト・QA方針と不可分であり、連携を考えながら方針立てする必要がある
  • ブランチ方針の工夫で、CI/CD、テスト・QAの開発インフラリソース消費を削減でき、本当に重要なポイントに開発インフラリソースを投入できる。これにより、限られたリソースでの高品質・高スピードの両立を支えられる

背景:開発インフラの進化が全てを解決すると楽観視していた発展期

ここ十数年の間、ソフトウェアエンジニアリングの分野で、明確に進化したといえる分野の一つが開発インフラです。特にCI/CD、デプロイメントパイプラインについての進化は目覚ましく、次のような変化がありました。

  • AWSやAzureのようなIaaSが普及し、開発インフラリソースの柔軟なスケールアウトや大規模運用が容易になった
  • Docker等のコンテナ仮想化が開発インフラで当たり前になり、開発インフラのランナーやエージェントのセットアップ・運用が容易になった
  • k8sのようなコンテナオーケストレーションのツール・サービスの充実で、動的なランナーやエージェントのライフサイクル管理が容易になった
  • デプロイメントパイプラインの記述が高度化した。かつてはJenkins1デフォルトのGUIベースのコンフィグ設定のような定義形式が多かったが、Github ActionsやJenkins2 Pipelineのような高度なスクリプトベース・テキストベースのパイプライン定義が可能になった。また事前に使用インフラやブランチを静的定義するのではなく、パイプライン実行時に動的にブランチやインフラを選択するパイプライン記述が容易になった
  • End to Endテストやヘッドレスの自動化ツールが充実し、CI/CDに様々なテストやQAの活動を統合可能になった

他にも様々な進化がありましたが、こうした進化を目のあたりにして、この先に「全ブランチで、継続的かつフルセットのテスト・QAタスクを実行して、常に高品質状態を維持」「時間がかかるパイプライン処理は、求める時間になるまで分割・並行処理して時間短縮し、常に実行」ができる理想のCI/CD時代が来る、と思っていた時期が昔ありました。

開発インフラリソースに制約のある現状

ただ、現時点では、これら理想の実現にはいろいろ制約が残っている状態です。例えば次のような制約が残っています。

  • IaaSやコンテナオーケストレーションは開発インフラサーバのスケーラビリティや保守性を劇的に改善したが、思ったほど安くなかった。特に中規模以上のコストのスケールメリットが小さかった。筆者の経験として、例えばCI/CD・自動テスト向けの50台ぐらいの物理PCインフラをIaaSに移行させた際、保守性が劇的にあがり、Flaky Testが大幅削減されて移行効果はあったものの、運用コストの大きさから必要なサーバリソースの最大量を常時保有する状態にできなかった
  • インフラリソースの増大に連動して、リソース消費量も増大した。例えばEnd to Endテスト、APIテストなどリッチな自動テストが増えた。またコンテナ仮想化は、VMに比べれば緩和しているものの、メモリや時間のリソース消費が大きく、並行化によるリソース削減効果に制約が出た

しかし、ソフトウェア開発では高品質と高スピードの両立の要求が高まっていて、それを支える開発インフラリソースの需要が増えています。
というのも、より有効で詳細な自動テストを実現すればそれに相応する品質確認ができますし、たくさんの開発インフラリソースを投入すれば、デプロイメントパイプラインを高速化できるためです(リソースのスケールアップで処理速度を高めたり、スケールアウトで並行化を促進したりするなど)。そのため使えるリソースに合わせて使用量が飽和している状態が続いています。

この、開発インフラリソースが不十分であることと、需要が増大していることの板挟みの結果、現代的な開発の多くでは、少ない開発インフラリソースを、うまくやり取りするリソース最適化が求められるようになりました。具体的には、詳細なテスト・QAが必要なポイント、時間制約が厳しいポイントに、リソースを厚く割り当て、それ以外ではリソースを浅く割り当てる、という方針が必要になりました。

CI/CD方針、テスト方針と連動させたブランチ管理方針

この「開発インフラリソースの割り当ての最適化」の有効手段の一つに、コードのブランチ管理と、CI/CD方針、テスト・QA方針を連携させて、ブランチごとに開発インフラリソースの割り当てを優先付けするアプローチがあります。

具体的には、開発インフラリソース需要とCI/CDのタスク密度を基準に、以下のようにブランチを三分類します。

  • 変更を受容しながら、継続的に高品質を維持するブランチカテゴリ
    • 開発インフラリソース投入:大、テスト・QAタスク密度(テストや静的解析の厳しさ・網羅性等):密
    • フルスタックのテスト・QAタスク(自動テストなど)を、高頻度に継続的に実行
  • 変更を限定しながら、継続的に高品質を維持するブランチカテゴリ
    • 開発インフラリソース投入:小、テスト・QAタスク密度:密
    • フルスタックのテスト・QAタスクを、低頻度にピンポイントで実行
  • 変更を受容しながら、開発の都合に柔軟に対応するブランチカテゴリ
    • 開発インフラリソース:小、テスト・QAタスク密度:薄
    • 軽量なテスト・QAタスクを必要に応じて実行

そして様々な工夫で、メインブランチカテゴリ、保守ブランチカテゴリのブランチ数を絞ることで、限られた開発インフラリソースで、高品質・高スピードの両立を実現するアプローチとなります。

なお、このアプローチは珍しいものではありません。今流行のトランクベース開発や、筆者が好きなGitlab-flowでも、このようなカテゴリ分けに基づく、開発インフラリソースの割り当て最適化がよく行われています。

メインブランチ、リリースブランチ、開発ブランチの三分類で開発インフラリソース割り当てを最適化する

前述のブランチ分類法を、より実例の方針に具体化したものを示します。ここでは、メインブランチ、リリースブランチ、開発ブランチの三分類でブランチ方針を具体化します。

●メインブランチカテゴリ

(前述の「変更を受容しながら、継続的に高品質を維持するブランチ」カテゴリ)

【概要】 チームの開発の中心となるブランチ。フルスペックのテスト・QAタスクを投入し、CiCDのインフラリソースも最大限投入して、高品質な品質を常に維持する。

【ブランチの役割】 開発中のコードの最新の正式版を管理する。プロダクトすべてのコードをチーム全体に共有するために用いる。トランクベース開発でのトランクに該当する。

【ブランチの品質要求】 詳細なテスト・QAタスクをフルスペックで継続的(高頻度に)に実行させ、それらがグリーンであることを保つ。パイプラインに障害が発生しても、即時にCI/CDで検出し、高速に修正して、継続的にコードが高品質な状態を維持する。

【変更の受容】 積極的な変更の受け入れを許容する。高頻度の変更の受容と高品質の両立は、CI/CDのテスト・QAタスクで支える。

【開発インフラリソースの割り当ての考え方】 リソースを最優先で投入する。

●開発ブランチカテゴリ

(前述の「変更を受容し、開発の都合に柔軟に対応するブランチ」カテゴリ)

【概要】 一般的なフィーチャブランチである。各開発者の作業をさせるために、開発者の都合に合わせて自由に運用する。

【ブランチ方針】 メインブランチや開発ブランチからブランチを取る。

【ブランチの役割】 開発の各種作業用に用いる。開発者の都合に応じて自由に作成する。

【ブランチの品質要求】 各チームに合わせる。ユニットテストといった常識的なテストタスクをグリーンに保つことは行うが、詳細なEnd to Endテスト等、リソース負荷の高いタスクは、リソースに余裕がないならばスキップしたり、実行頻度を落としたりする。

【変更の受容】 各チームに合わせる。開発者の都合に合わせて自由に変更する。

【開発インフラリソースの割り当ての考え方】 優先度を低くする。後述するCI/CDや設計・インテグレーションの工夫で、少ないリソースでも品質を確保できるようにする。

●保守ブランチカテゴリ

(前述の「変更を限定しながら、継続的に高品質を維持するブランチ」カテゴリ)

【概要】 リリース等のために、品質が確保されたコードを分離管理する場合に利用する。Gitlab-flowのリリースブランチが該当する。

【ブランチ方針】 通常はメインブランチからブランチを切る。

【ブランチの役割】 メインブランチ・開発ブランチの頻繁な変更から分離して、コードを高品質な状態で保守するために用いる。主用途はリリースコードの保守である。

【ブランチの品質要求】 メインブランチと同等の品質を維持する。ただし、ブランチに関わる品質リスク(マージ、チェリーピック等に起因するリスク)を抑える対策で、テスト・QAタスクを簡略化する方針をとる。

【変更の受容】 変更はなるべく許容しない。

【開発インフラリソースの割り当ての考え方】 優先度を低くする。テスト・QAタスクの実行頻度や実行時間の制約を緩和して、リソース消費量を減らす。

高品質と高スピードの開発を支えるブランチ管理のプラクティス

この三分類を使ったブランチ管理方針により開発インフラリソース割り当ての最適化を進める中で、次のような設計やインテグレーション、テスト・QAのプラクティスを推進すると、高品質・高スピードの両立を促進できます。

設計とインテグレーションの工夫でメインブランチカテゴリを最少化する

メインブランチカテゴリは開発インフラリソースを大量に使用するため、そのブランチ数は最小化すべきです(モノリシックリポジトリなら理想は一つ)。

反面教師として、多くはありませんが、コードのバリエーションごとにブランチを分ける運用方針をたまに見ます。例えば「本番用コードとデバッグ・テスト用コード(本番環境依存部をTest Doubleに置換したものなど)でブランチを分ける」「機能のあるなし・機能の違いでブランチを分ける」「仕向け(日本語向けと英語向け等)でブランチを分ける」といったものです。

これは悪いブランチの運用です。あるべき姿としては、一つのブランチですべてのバリエーションを全部入りで管理し、設計、インテグレーション、動的な切り替え手段で、取捨選択すべきです。切り替え手段には、例えば以下のような仕組みがあります。

  • プリプロセス、ビルド、インテグレーション、デプロイのスクリプト(例えばBazelやMakefileでバリエーションを選択)で切り替え
  • フィーチャトグルやDependency Lookupといった、機能の有効無効を制御する手段で切り替え
  • デバッグモード機能といった、ソフトウェア実行中の設定操作での切り替え

CI/CDに結合した継続的テストを充実させて、保守ブランチカテゴリを最少化する

保守ブランチカテゴリは、リグレッションテストが充実しているほど、必要性が下がります。理想状態ですが、例えばリリース判断できるほど十分なテストが自動化されCI/CDに組み込まれていれば、リリース管理は、メインブランチカテゴリへのタグ付けで実現でき、リリースブランチは不要になります。
GoogleのDORAの調査でも、最上位の技術的組織では、その理想状態の実現により、リリースブランチは事実上存在しないと報告しています。

ですのでCI/CDに組み込んだ自動テストを一定レベル以上に充実させると、保守ブランチカテゴリのブランチが減り、メインブランチにより多くの開発インフラリソースを投入できるようになります。

テスト用にブランチを切るのを避け、メインブランチに対して継続的にテストを行い、メインブランチの品質を育てる

システムテストのような手動の大規模なテストをする際、テストと並行する開発作業によるリグレッションやバグ混入から守るため、テスト用にブランチを切って、そこに対してテストする運用は珍しくありません。
ただこの運用は次の問題を持ちます。

  • テスト用ブランチのために開発インフラリソースの使用量を増やします。
  • デバッグ時にブランチ間のチェリーピックや複雑なマージが必要となり、それら起因の品質リスクを高めます。
  • テストで用いるコードと、開発している最新コードの差異が広がります。それによりテスト結果が陳腐化しますし、差異からのバグ見逃しの可能性も高まるため、開発コードを再度テストしなければならなくなります。

上記の問題から、広い視点で見ると、テスト用にブランチを分けないパターンよりも品質リスクが悪化する恐れがあります。

そのため、品質とスピードの両立では、テスト用にブランチを切るのではなく、継続的テストのアプローチをとった方が都合が良い場合が結構あります。例えば大規模なシステムテストを行うならば、ユースケース単位などでテストを細切れにして、メインブランチに対して細かく・継続的にテストを加えていくアプローチです。

クオリティーゲートを重厚化するのではなく、開発・修正のリードタイムを高速化して品質維持し、ブランチのライフタイムを短縮する

開発ブランチカテゴリ、保守ブランチカテゴリは、ブランチのライフタイムが長いほど、メインブランチとの差分が広がり、ブランチ関連作業の品質リスクを高めます。これはブランチの開発インフラリソース消費の増大につながります。

そのため、開発ブランチや保守ブランチは、なるべくライフタイムが短くなるように運用するのが望ましいです。
対策としては、チームの開発力を高めて、メインブランチの修正のリードタイムを高速化することで、ブランチのマージを許容するアプローチがあります。壊れないようにクオリティゲートを重厚化するのではなく、壊れたらすぐ治すようにチームを鍛えるということです。また、同じくチームの開発力を高めて、開発ブランチの目的達成を高速化することでも、ブランチのライフタイムの削減につながります。

設計の工夫で、ブランチ作業に起因する品質リスクを抑える

ブランチ、特にそれぞれの開発ブランチの成果物をメインブランチに集めるインテグレーションにかかわる品質リスクを抑えると、開発ブランチカテゴリへの開発インフラリソースを削減できます。

例えばマイクロサービスアーキテクチャの推進など、アーキテクチャレベルで結合性を下げる設計を行うと、各コンポーネントのインテグレーションに起因する品質リスクを下げられます。これにより、開発ブランチでは、各コンポーネントユニットテスト結合テストのみ行い、大規模なシステムテストはメインブランチに対してのみ適用するといったアプローチが可能となり、開発インフラリソースの割り当て最適化を促進できます。

結論

文書が長くなりましたが、重要な要点は以下の二点になります。

  • ブランチ方針は、CI/CD方針、テスト・QA方針と不可分で、連携を考えながら方針立てする必要がある
  • ブランチ方針の工夫で、CI/CD、テスト・QAの開発インフラリソース消費を削減でき、本当に重要なポイントに開発インフラリソースを投入できる。これにより、限られたリソースでの高品質・高スピードの両立を支えられる

品質保証(QA)とは。定義の三大流派と定義揺れの弊害

近年のソフトウェア業界では、テスト関連活動を担うエンジニアを「QAエンジニア」と呼ぶようになっています。ただQA(品質保証)という言葉は、旧来から二つの定義が共存しているほか、業界内の通例で更に別の意味付けが行われた結果、定義が曖昧になり誤解を生みがちな状態となっています。
そこで今回は、日本語圏で、QA(品質保証)の言葉がどのように定義されているか、整理して解説します(結論からいうと三流派あります)

国際標準規格での定義:品質マネジメントシステムの実証

IEEEやISOといった国際的な標準規格、およびそれに準拠した知識体系や標準では、古くから体系立てて品質マネジメント、品質保証、品質管理の定義を行っています。

有力な文献として、品質マネジメントの標準規格である、ISO 9000:2015の定義を紹介します。
まずISO 9000では、品質保証の前提として品質マネジメントという用語を使っているので、前提知識としてその用語定義を関連用語とまとめて引用します。

【品質マネジメント(quality management) 】
品質に関するマネジメント。
品質マネジメントには、品質方針及び品質目標の設定、並びに品質計画、品質保証、品質管理及び品質改善を通じてこれらの品質目標を達成するためのプロセスが含まれ得る。

【品質】
対象に本来備わっている特性の集まりが要求事項を満たす程度

【マネジメント】
組織を指揮し管理するための調整された活動

要点をかいつまんで説明すると、望ましい品質を実現するための組織的な活動を「品質マネジメント」と呼び、品質保証は、品質マネジメントの一要素である、と定義しています。

次に、品質保証や類似活動の定義を次のように行っています。

【品質保証(quality assurance)】
品質要求事項が満たされるという確信を与えることに焦点を合わせた品質マネジメントの一部。

【品質管理(quality control) 】
品質要求事項を満たすことに焦点を合わせた品質マネジメントの一部。

【品質改善(quality improvement) 】
品質要求事項を満たす能力を高めることに焦点を合わせた品質マネジメントの一部。

別の文献ですが、ISO/IEC 29119-13:2022 Part13のTR版では、次のような記述があります

Software testing is a form of quality control, which, together with quality assurance comprise quality management
ソフトウェアテストは、品質管理の一部である。品質管理と品質保証を組み合わせて品質マネジメントを構成する)

※()内は本ブログでの意訳

内部解説を含めて、具体的に要点をまとめると、次のようになります:

  • 品質管理は、開発成果物が品質目標を満たしているか確認し、品質をコントロールするための活動。具体的には、開発成果物がユーザの要求を満たしているかテストする、コードに品質リスクがないか静的解析する、といった活動が該当する。
  • 品質保証は、必要な品質を実現するための方針立て、プロセス、組織体制、その運用活動が、問題なく妥当であると確信を得るための活動。品質管理が適切に行われているか確認するのも品質保証の一部。具体的には、開発者がプロセス定義どおりに活動しているか確認する、プロセス定義が法規制に準拠しているか監査する、といった活動が該当する。

日本的品質管理での定義:顧客満足の実現

一方、日本語圏では、前述のISO等の国際規格とは異なる定義で、品質保証という言葉を使う動きがありました。これは戦後日本で発展した品質管理の方向性の中で成立した定義で、誤用や誤解に基づく定義の揺れではありません。

まず日本人が中心となって定義したSQuBOK(ソフトウェア品質の知識体系)から説明を引用します。

日本では品質保証という用語を『お客様が安心して使っていただけるような製品を提供するためのすべての活動』(飯塚悦功、超ISO企業実践シリーズ総論ISOを超える)『品質保証は品質管理の真髄である』(石川 馨、日本的品質管理~TQCとは何か)のように、顧客を満足させる活動を総称する意味で使うことが多い。

(日本では)顧客が「安心」「満足」「長く使用できる」ことを目的として品質保証が実施されるようになった。
これに対して、欧米では、契約社会という文化的背景から、品質保証していることの「実証」を重要視して発展してきたと考えられる。

※()部分は本ブログでの補足追記

また「現代的品質管理総論」(飯塚悦功、永田靖)でも次のような解説を行っています。

顧客が満足する製品・サービスを提供すること、あるいはそのための活動を品質保証という。

海外由来でない日本の標準規格でも、この定義に従って標準化しているものがあります。例えばJISハンドブックでは品質保証に「消費者の要求する品質が十分に満たされていることを保証するために、生産者が行う体系的活動」という意味づけを行っています。

戦後日本で語られてきたこの品質保証の定義では、ユーザの要望を掘り起こすマーケティング活動や、クレーム処理、ユーザテストなども、品質保証活動に含まれます。完全な包含関係ではないですが、テスト活動の一部も品質保証活動に含まれます。

海外の標準規格と比べて、目指すものは似ていますが、その手段はズレがあり、「品質保証にテストが含まれるか」の解釈も変わります。

日本のソフトウェア業界での通例:テストエンジニアの上位職種

最後に、日本のソフトウェア業界の新興企業では、前述とは違う定義で「品質保証(QA)」の言葉を使用しています。そこでは、大まかに「ソフトウェアテストそのもの」あるいは「ソフトウェアテストの上位の活動」という意味で品質保証(QA)の言葉を使用しています。

例えば既存のQAエンジニアの募集要項を引用します。
https://cybozu.co.jp/recruit/entry/career/qa-engineer.html

プロダクトのQA担当者としていずれかの製品を担当していただきます。

  • テスト計画、テスト設計、テスト実施
  • リファインメントや振り返りへの参加
  • テスト効率化および自動化
  • 開発・テストプロセスの改善提案と実行推進

書かれている通り、ソフトウェアテストの仕事に対して、QAという呼び名を使用しています。
上記の引用先の企業に限らず、他の日本の新興のソフトウェア開発企業でも、この「品質保証(QA)≒テスト」の意味づけが広く見られます。

こういった業界的なQAの意味づけは、大まかな方向性ですが、「リリースできる品質を実現していると判断するのが品質保証」→「その手段が主にテスト」→「そのため品質保証≒テスト」という思考ロジックで使われることが多い印象を持ちます。

また従来のテストエンジニアよりも難しい・よりスコープが広い・職位が高い仕事であることを表現する方向性で使われる場面も増えています。
この「難しい・よりスコープが広い・職位が高い」の具体例は複数あり、人や組織によってバラバラです。主要なものを以下に挙げます。

  • テストだけでなく、レビューや品質メトリクス等を使って、上流工程から品質を作りこむ(より職務のスコープが広い)
  • テストに限らず、品質の確保と確認にかかわる作業全般を担当する(テスト以外の業務も担当する)
  • 与えられた作業指示通りテストするのでなく、自らテスト要求を分析して必要なテストを実現する(受け身の作業者でなく、テスト全般の責務を主体的に果たしていく)
  • 現場のテスト設計を横断的に分析し、改善して、より高度なテストを導く(上級テストアナリストである。組織横断的業務である)
  • 開発チームのテストより独立性をもってテストを行い、開発チームが見逃した不具合や品質リスクを取りきる(既存のテストエンジニアより独立性が高い)
  • テストの計画、マネジメントを担う(上級テストマネージである)
  • ユーザが求めるニーズに精通し、本当に必要な品質要求を特定してそれを実現する支援を行う(テストエンジニアに加えてドメインスペシャリストでもある)

【余談】日本のソフトウェア業界での通例が生まれた背景

余談ですが、ソフトウェア業界でQA≒テストの意味づけが慣用的に広まった背景として、二つの事情があると考えます。

一つ目の事情は、リリースのためのクオリティゲートの役割の高度化です。
近年のソフトウェア開発では、次の要因から、リリース前のテストの高度化が進んでいます。

  • 外部のSaaSを組み合わせるなど製品が複雑化する一方で、リリースの高頻度化・開発の高速化が進展。複雑な製品に対し、限られた時間でテストし、リリースできる品質を確保できているか判断しなければならなくなった。
  • 品質を確認する責務がチーム全体に分散(例:自動テストを組み込んだCIで開発者もテストの責務を担う)。テストチームが最終段階で徹底的に品質確認するアプローチでなく、他の工程・他の職種と連携しながらテストする全体最適のアプローチが一般化した。

その結果「テストを主軸にリリースのための高度な品質確認を行う役割」の需要が高まってきました。
その中で、標準規格を気にしないまま「品質を保証する(QA)」の字面のニュアンスが単純に合っていると判断して、この役割をQAエンジニアと呼び始めた人が少なくないと感じます。そしてこの役割の需要増大に伴って、このQAエンジニアの呼称が業界に増えてきたのではと考えています。

二つ目の事情は、日本の一部で昔から次の考え方が根強くあることが影響していると思います。

  • 本当はソフトウェアテストは難しい仕事である。現在テストの担当者は単価が低く技術的難易度の低い職種とよく見なされているが、本当は高度な技術力が求められる職種である
  • 上記を業界的に知らしめるために、「高度な技術力が求められるテストの職種」に新しい職種名を付与して、既存の職種名(=単価が低く技術的難易度の低い職種の象徴)と区別しよう

例えばテストを担う役職名として英語圏では「Tester」がよく使われています。日本国内では、それに合わせて「テスター」という名前を採用していました。ただ、日本国内では、低単価の第三者検証の急速な普及で、このテスターを「単価が低く技術的難易度の低い職種」とみなすネガティブイメージが業界で形成されました。
それに対し、2010年ごろから、ネガティブイメージを打破して「テストの担う役職は、テスト分析やテスト設計で高度な技術力が求められる職種」であると啓蒙するため、その職種に「テストエンジニア」「テスト担当者」という別の職種名を使う動きがテストのコミュニティや関連団体で生まれました。
一例として、筆者が技術委員として所属するJSTQBでも、この業界的流れに基づいて、英語の「Tester」は「テスター」と訳さず、すべて「テスト担当者」と訳すシラバス翻訳方針が作られました。

2020年頃から目立ってきたテストエンジニア→QAエンジニアの職種名の入れ替えも、このテスター→テストエンジニアの言葉の入れ替えの動きの延長線上にあるのではないかと思います。従来の低単価・低スキルな仕事(=テストエンジニア※あくまで業界的な印象)と比べ、より高度でスコープが広い仕事(=QAエンジニア)であることを示すために、QAの言葉を使い始めたということです。

三流派の定義の共存の弊害

以上、日本語圏の品質保証(QA)の定義の三流派をまとめて紹介したのですが、割と弊害がある状態だと思います。

というのも、明確に内容が違うものに対して、同じ名前を使っている結果、詳細情報を読まないと、QAが示す具体的内容を理解できない状態になっているためです。例えばQAエンジニアの募集についても、詳細な募集要項を見ないと具体的にどのような仕事なのか読み取れなくなっています

また、キャリアプラン的にも弊害があります。業界慣例のQA(実質テスト)と、国際規格のQAのキャリアパスは明確に違うためです。前者はテストエンジニアとして成熟すれば担える仕事です。しかし後者は、設計やプログラミングが妥当か、要求定義が妥当かといった問題を分析するために、上流工程の経験・能力も求められます。テストエンジニアの仕事だけしていれば担える仕事ではありません。

ただ、定義の揺れ・乱れの弊害性を認知する人も増えて、最近その定義を整理する動きも生まれています。例えばQMファンネルのような動きです。テストやQAにかかわるイベント、シンポジウムで活発に議論されていますので、「品質保証(QA)」の言葉を扱う人ならば、キャッチアップする価値はあると思います。

テスト設計の一通りの流れについてのチュートリアルに登壇

先日、テスト設計コンテストU-30クラスに関連するイベントとして、テスト設計のチュートリアル講師の機会を頂きました。

https://speakerdeck.com/goyoki/test-design-tutorial

今回のチュートリアルでは、初学者向けに、勉強会やセミナーで解説されるテスト分析と、実際の現場のテスト分析のギャップを埋めることを意図して、講演を組み立てました。

その背景ですが、テスト観点分析、テスト条件分析の解説では、テストレベル、テストタイプ、仕様を小さく切り取って、最初から小さいスコープで分析を進めるものが多い印象を受けます。その知識のみを、複雑で規模の大きな現場のテスト設計に適用しようとすると、分析アプローチのインプットをどう確保しスコープを決めるかという課題と、テスト観点・テスト条件の発散や大規模化をどうさばくかという課題に直面しがちと感じていました。
そこで、今回はJSTQBベースのテスト設計の流れの解説に、以下を要点とした補強を加え、解説させていただきました。

  • テストの責務(対象、スコープ、十分性、対応すべき制約)の導き方
  • テスコンではテストアーキテクチャ設計と呼ぶ、テスト分析での関心の分離・モジュライズのアプローチを、テスト分析初期に実施するやり方

資料や記録動画が何かしらテスト設計の改善の一助になれば幸いです。
またテスト設計コンテストは今年度もエントリーを積極募集中です。気軽に・積極的にエントリーいただくとこちらも幸いです。
テスト設計コンテスト

モダンなテストレベル設計(ユニットテスト~システムテスト等をどう設計するか)の原則

 プロジェクト全体のテストを組み立てる際に重要な課題になるのが、テストレベル設計です。テストレベル設計は、ユニットテスト結合テストシステムテストといったテストレベルを、どのような責務・段取りで行うか分析・設計する活動です。

 このテストレベル設計ですが、ここ10年程度の間に望ましいアプローチが変わってきたと感じています。今回はこの変化と、変化後のモダンなテストレベル設計の原則について、考えていることを書き出したいと思います。

旧来のテストレベル設計のアプローチ

 旧来、このテストレベル設計では、Vモデルをベースしたアプローチや、自工程完結・品質積み上げをベースとしたアプローチがよく見られました。

 このうち一つ目のVモデルをベースとしたアプローチは、要求定義から設計までの上流工程への対応を観点に、テストレベルを設計するものです。
 (Vモデルが必須と明言しているわけではなく、極端な例ですが)例えば「要求定義工程があるので、その成果物の実現性を検証するためにシステムテスト工程を確保」「アーキテクチャ設計工程があるので、その成果物の実現性を検証するために結合テスト工程を確保」のように、開発工程と対となるテスト工程を用意するアプローチが、Vモデルを意識したプロジェクトの多くで取られます。
 またテストレベル設計は、IEC62304やA-SPICEなどのプロセス標準・プロセス規格に従って行う組織も多いと思います。そういったプロセスの標準・規格は大抵の場合Vモデルをベースにしているので、実質的にこのアプローチとなっていることが多いです。

 二つ目の自工程完結・品質積み上げをベースとしたアプローチは、「自分たちチームの成果物の品質は自分たちが責任をもって確保・保証する」「構成要素ごとに品質を確保・保証する」という考え方でテストレベルを設計するものです。
 例えばチームがシステムを構成する特定のコンポーネントを担当する場合、コンポーネントに対するユニットテスト結合テストを自チーム内に確保し、担当コンポーネントの品質確保・品質保証を自分たちでしっかり行うようにします。
 チームが実装工程を担当するならば、ユニットテスト工程を確保し、実装仕様の実現性保証や、実装に起因するバグの取りきりをチーム内で行います。
 このアプローチにより、分担した成果物の品質確保・品質保証をそれぞれの担当組織・構成要素で行い、品質を積み上げることで、全体の品質を向上させていくアプローチとなります。
 SoS開発や複数会社での開発では、組織構造に合わせてテストレベルを設計するアプローチが一般的に取られますが、それもこのアプローチの一種と言えます。

 こうした旧来のテストレベル設計では、テストレベルごとにある程度独立性をもって、それぞれのテストの責務を果たしていくアプローチが好まれます。

開発技術やプロセスの発展とテストレベル設計

 上記のアプローチは現在でも陳腐化したわけではありません。ただ開発技術やプロセスの発展で、より柔軟なアプローチを加えることが可能になっていると感じています。

 その発展の一つが、自動テスト手段の充実です。ここ十年、ユニットテスト結合テスト、End to Endテストのツールが多数生まれ・普及しています。Flutterのような便利な自動テスト機能を自身に持つ開発フレームワークも増えています。特に、最近は費用対効果の高い結合テスト・End to Endテストの自動化手段が、複数の分野で充実・普及してきました。
 これにより、テストレベルごとの有効性や費用対効果の差が明確に大きくなりました。特に、旧来の手動のシステムテストに対し、現代的な自動化を行ったシステムテスト結合テストの有効性、アジリティ、費用対効果がかなり大きくなりました。
 結果、Vモデルや自工程完結のアプローチだけでテストレベルを設計するだけでなく、費用対効果の高いテストレベルの責務を増やし、費用対効果の低いテストレベルの責務を減らす設計アプローチが、開発の生産性を高めるのに有効になりました。

 発展の二つ目は、継続的デリバリやDevOps、またはDORA Four Keysを追及するような、開発のリードタイムを重視する開発プロセスの普及です。
 この開発プロセスでは設計、実装、静的テスト、動的テスト、インテグレーション、デリバリまでの一連の開発のリードタイムを高速化し、ビジネスと開発のフィードバックサイクルを軽快に回せる体制・プロセス・環境を構築することで、プロダクトの価値を高めていきます。例えばユーザのニーズ・シーズの取り込みの速さ・バグ修正の速さを高め、プロダクトの競争力や高品質を保ちます。
 この開発プロセスにとっては、旧来の大規模な手動テストは、遅すぎ・コストがかかりすぎで、リードタイムを大きく悪化させ、プロダクトの価値を棄損します。
 そこで、リードタイムの短いテストレベルでなるべくテストを済ませ、遅いテストレベルはできるだけ排除するアプローチが採用されやすくなりました。

 最後の発展は、CI/CDによるデプロイメントパイプラインの充実です。
 デプロイメントパイプラインの作業を手動で行っていた時代では、工程移行時にバグが混入するリスクが少なくありませんでした。例えばユニットテスト後に、マージ・インテグレーションする作業でバグが混入し、後段のシステムテストで見つかる、といったものです。またテストレベルごとにブランチやバージョンが異なり、その差異から前段のテストでバグを見つけられない、といった状況もありました。こうしたリスクから、後段のテストの責務を前段のテストに移動させるアプローチには、バグ流出のリスクが付随しました。
 ただCI/CDのデプロイメントパイプラインが普及したことにより、同一のバージョン・ブランチに一括して複数のテストレベルを実行したり、工程移行時のバグ混入をなくしたりすることが可能になり、テスト責務の移動のリスクを軽減できるようになりました。
 これにより、テストレベルの本来の能力に基づいて、テストの責務を移動させるアプローチを柔軟に取れるようになりました。

モダンなテストレベル設計の原則

 この自動テストの発展、リードタイム重視の開発プロセスの普及、CI/CDデプロイメントパイプラインの充実により、テストレベル間でのテストの責務調整を柔軟に実施できるようにする後押し・環境が生まれました。
 これにより遂行可能になったのが、次の原則に基づいてテストレベル設計を行い、開発のアジリティや生産性を向上させるアプローチです。

  • もっとも効果的なテストレベルの責務を最大化する
  • それぞれのテストレベルの強みを最大化し、弱みを最小化・補完する
  • テストレベル間のテストの重複は、全体が良くなる方向で削除する

 一言で言うならば、「全体が良くなるように、個々のテストレベルの責務分担を最適化する」という原則です。

 例えば、CI/CDに組み込まれた自動テストとそれ以外では、変更対応を中心に、テストのコスト・アジリティ・費用対効果が大きく変わります。そこで、複雑な組み合わせやロジックのテストは自動テストで済ませ、手動テストは、ユーザビリティテストなどどうしても手動でなければならないテストを除いて、探索的テストで済ませるようなアプローチをとる、といった方針がこの原則に乗っ取った設計方針になります。

 当たり前に見える原則ではありますが、旧来と違うのは、テストの責務移動と、テストの責務重複の削除を大胆に推奨するということです。結合テストが一番効率的なら、組み合わせは結合テストでテストして、システムテストからは組み合わせテストを削除するといったものです。

 余談ですが原則中の「もっとも効果的なテストレベル」として、ユニットテストを選び、テストピラミッドのテストレベル設計を行う事例をよく見ます。ただ現代的な開発では、ユニットテスト以外も有力な候補になっています。マイクロサービスアーキテクチャのようなサービスの群体ならAPIテストが有力です。FlutterといったモダンなFEフレームワークを作っているなら、フレームワークが提供する結合テストが有力です。
 またユニットテストは、メリットを多く持つものの、次のようなデメリットも持ちます。

  • プロダクトの価値から離れる。魅力的なユーザビリティや、競争力のある高度な機能といった、プロダクト価値は、ユニットレベルのような細分化された世界では意識しにくくなります。プロダクト価値を意識せず、コードカバレッジを満たせばよい、のようなやり方を取ってしまう場合もあり得ます。
  • プロダクトコードに強結合する。コードの細部にテストが依存するため、変更・保守を行う手間が発生します。例えば機能の影響のないリファクタリングでもテストが壊れるといった状況対応です。

 このようなデメリットを加味して、注力するテストレベルを選択すべきといえます。テストピラミッドでなく、APIテスト等結合テストを主体としたテストトロフィーを志向したほうが良い開発も少なくありません。

テストレベル間でのテストの責務の移動を支える基礎

 上記のモダンなテストレベル設計の原則を実現するには、テストレベル間でテストの責務を安全に移動させるための基礎作りが不可欠になります。基礎作りの中で重要なものを次にいくつかピックアップします。

契約による設計(またはそれと同等の責務設計)を推進する

 システムテストの責務を結合テストに移動させる場合に重要になるのが、システムの機能や処理を、コンポーネントのどこが責務として担っているのか明確にすることです。

 例えば以下の事例を考えます。

  • 複雑な入力バリデーション(入力の形式、数、フォーマット、組み合わせは正しいか等チェックする機能)をテストする。
  • 対象システムはフロントエンドレイヤとバックエンドレイヤで分かれている
  • システムテストと、フロントエンド結合テスト、バックエンド結合テストのテストレベルを実施している。入力バリデーションはシステムテストでテストしている。

 この入力バリデーションのテストの責務を結合テストに移動させる場合、入力バリデーションの責務がフロントエンドで行うか、バックエンドで行うか、設計で保証されている必要があります。
 仮に入力バリデーションの責務がフロントエンドにあると保証されているならば(契約による設計の言葉を使えば、入力バリデーション済みの状態が、フロントエンドの事後条件とバックエンドの事前条件に指定されているならば)、システムテストから、フロントエンド結合テストへ、入力バリデーションのテストを移動できます。
 しかし入力バリデーションの責務が曖昧だったり、レイヤ横断的に実装されている場合は、システムテストから結合テストへの移動は、リスクができず実施できなくなります。

 ですので、 契約による設計、またはそれと同等のアーキテクチャレベルの責務設計は、テスト責務を柔軟に移動させるアプローチにとって不可欠になります。

デプロイメントパイプラインの信頼性を挙げる

 CI/CDのデプロイメントパイプラインの信頼性を挙げて、安全にテストレベル間でテストの責務を移動できるようにするのも重要です。
 例えば各テストレベルで、各テストレベルのテスト条件(例えば対象のブランチ、バージョン、インテグレーション状態)を一致させるようにパイプラインを組むのは有効です。またテストレベルを移行する間のバグ混入を最小化するのも有効です。
 こうした信頼性向上策は、後段の長大なテストレベルの責務を前段のテストレベルに分散するアプローチを採用可能にします。

アーキテクチャレベルで凝集性を上げ、結合性を下げる

 システムテストのような包括的なテストレベルの責務を、結合テストユニットテストに移動させるアプローチでは、システムを結合することで発生するリスクを減らす対策が必要になります。
 これには、一般的に求められる凝集性を上げ(前述の契約による設計もこのためのアプローチになります)、結合性を下げるアーキテクチャ設計が有効です。例えば「コンポーネント間の結合部の並行処理のリスクを下げることで、システムを結合しないと再現できないバグを減らす」「コンポーネントの凝集度を上げ責務を明確化することで、多数のコンポーネントを横断させないとテストできない機能を減らす」といった設計の工夫の積み重ねが、テスト責務の移動を安全にします。