「ソフトウェア品質保証ガイド」を執筆しました

 今月の8月28日に、執筆した「ソフトウェア品質保証ガイド チームで顧客満足を実現するためのアプローチ・手法・技術」が発売されます。

https://www.kadokawa.co.jp/product/302607002896/

 この本は、顧客満足を実現する活動を品質保証とし、ソフトウェアプロダクトの開発から運用までライフサイクル全体に求められる品質保証活動について解説しています。
 読者はQAエンジニアに限定せず、開発チームに関わるエンジニアやマネージャ全般に広く読んでいただくことを想定して執筆しました。
 トピックとして次のような内容を扱っています。

  • シフトレフトや品質の積み上げの実践
  • 開発と運用のフィードバックループの推進による顧客満足の向上
  • 要求分析から設計、実装、テストまで、ソフトウェア開発ライフサイクル上で有効な顧客満足の実現・向上活動
  • 生成AI活用や開発プラットフォーム整備といった技術活用
  • 顧客満足を支えるアーキテクティング
  • 顧客満足を維持・向上するための保守性・運用性作りこみ

 ぜひ買っていただけると嬉しいです。
 内容の参考として、目次を列挙します。

第1章 品質と品質保証
 1.1 本書における品質保証の定義 
 1.2 品質とは 
  1.2.1 品質の難しさ
  1.2.2 自分たちにとっての品質を追求しなければならない
  1.2.3 あるべき品質を追求する
  1.2.4 品質のさまざまな側面や種類
  1.2.5 品質モデル
  1.2.6 現代的なソフトウェア開発を支える品質
  1.2.7 品質を支える開発組織
 1.3 ソフトウェアの品質保証とは
  1.3.1 品質保証の定義
  1.3.2 顧客満足を実現するための活動
  1.3.3 開発チームの力を伸ばす品質保証の基本方針
  1.3.4 三現主義・五ゲン主義で品質保証活動を推進する
  1.3.5 ソフトウェア品質保証の当事者
  1.3.6 品質保証で避けるべき姿勢
  1.3.7 品質マネジメントシステムを整備する
  1.3.8 ハードウェア開発とソフトウェア開発での品質保証の違い
 1.4 QA エンジニアの価値 
  1.4.1 QA エンジニアとしての価値の確立 
  1.4.2 QA エンジニアのロールモデル 

第2章 品質保証のアプローチ
 2.1 品質のシフトレフト
  2.1.1 シフトレフトの恩恵
  2.1.2 シフトレフトの誤解
  2.1.3 シフトレフトを支える施策
  2.1.4 W モデル
 2.2 品質改善サイクルの推進
  2.2.1 PDCA サイクル 
  2.2.2 品質改善サイクルの種類
  2.2.3 源流管理と水平展開
  2.2.4 品質改善サイクルの組織定着
  2.2.5 改善サイクルをまわすためのバグ情報の蓄積
  2.2.6 ODC 分析
 2.3 プロダクトリスクの分離
  2.3.1 プロダクトリスクの分離を支える施策
  2.3.2 バルクヘッドパターン
  2.3.3 汚染防御設計
  2.3.4 ソフトウェア安全クラスの分離
 2.4 品質の積み上げ
  2.4.1 工程品質の積み上げ
  2.4.2 スパイラル型品質の積み上げ
  2.4.3 コンポーネント品質の積み上げ
 2.5 体系的品質保証
  2.5.1 品質保証の体系化のアプローチ
  2.5.2 テスト活動の体系化
  2.5.3 体系的品質保証で留意すべき効果や法則

第3章 開発アプローチに合わせた品質保証
 3.1 アジャイル開発での品質保証アプローチ
  3.1.1 アジャイルソフトウェア開発宣言
  3.1.2 アジャイルの原則
  3.1.3 アジャイルテストの原則
  3.1.4 アジャイルテストの四象限
  3.1.5 アジャイルを支える品質保証体制
  3.1.6 アジャイルでの品質保証のアプローチ
  3.1.7 スクラムを支える品質保証
  3.1.8 スクラムを支える品質保証体制
  3.1.9 プロジェクトのコンテキストにアジャイルを適応させる
 3.2 DevOps での品質保証アプローチ 
  3.2.1 DevOps とは 
  3.2.2 ホリスティックテスト
  3.2.3 DORA Four Keys と品質保証での対応 
  3.2.4 DevOps を支える品質保証体制
  3.2.5 DevOps を支える品質保証のアプローチ

第4章 品質の評価
 4.1 品質の評価
  4.1.1 仕組みの評価で品質を保証する
  4.1.2 品質メトリクスの選択
  4.1.3 GQM 
  4.1.4 評価基準の設定
  4.1.5 品質評価を支える計画と仕組みづくり
  4.1.6 品質保証としての品質評価の心がけ
  4.1.7 品質評価の手段
 4.2 レビュー
  4.2.1 レビューの強みと弱み
  4.2.2 レビューの適用領域
  4.2.3 レビューを行うポイント
  4.2.4 レビューの効果を引き出すための工夫
  4.2.5 レビューが適切に実施されているかの確認
  4.2.6 レビューの手法・技法
 4.3 静的解析
  4.3.1 静的解析の強みと弱み
  4.3.2 静的解析の適用領域
  4.3.3 静的解析を行うポイント
  4.3.4 静的解析の効果を引き出すための工夫
  4.3.5 静的解析の技術
  4.3.6 生成 AI による静的解析
 4.4 動的テスト
  4.4.1 動的テストの適用領域
  4.4.2 動的テストを行うポイント
  4.4.3 動的テストの効果を引き出すための工夫
  4.4.4 動的テストの分類や種類
  4.4.5 探索的テスト
 4.5 モニタリング
  4.5.1 プロダクトのモニタリング
  4.5.2 プロセス品質のモニタリング
  4.5.3 品質保証で活用できるプロセス品質のメトリクス
  4.5.4 モニタリングを支える品質の見える化
  4.5.5 モニタリングの効果を引き出す工夫

第5章 開発工程を支える
 5.1 開発工程を支える
  5.1.1 開発ライフサイクル上の品質の要所を押さえる
  5.1.2 少数の品質保証リソースで開発を支えるための工夫
  5.1.3 開発スピードを損なわず開発を支えるための工夫
 5.2 要求分析・要件定義を支える
  5.2.1 要求分析・要件定義の適正化を支援する
  5.2.2 品質要求を分析し要件化する
  5.2.3 ユーザや現場を観察して品質保証の知見を学ぶ
 5.3 設計・実装を支える
  5.3.1 顧客満足を支える品質の実現をサポートする
  5.3.2 品質と開発のスピード・レジリエンス・持続性の共立を支える
  5.3.3 適切な保守性の確保
  5.3.4 適切なテスト容易性の確保
  5.3.5 運用を支える品質の確保
  5.3.6 設計・実装の品質の維持
 5.4 アーキテクチャ設計を支える
  5.4.1 品質に関わるアーキテクチャ設計判断をサポートする
  5.4.2 関心の分離、SLAP で責務を調整する 
  5.4.3 プロダクトリスクの分離、コンポーネント品質の積み上げを支える
  5.4.4 アーキテクチャの疎結合設計の具体例:依存性逆転の原則
  5.4.5 副作用の局所化で疎結合設計を推進する
  5.4.6 契約による設計で凝集性を確保する
  5.4.7 アーキテクチャ品質の評価
  5.4.8 アーキテクチャ品質は顧客満足実現とビジネス成功の要
  5.4.9 アンチパターン:アジャイルではアーキテクチャ品質も差分開発で構築する
 5.5 開発者テストを支える
  5.5.1 開発者テストとは
  5.5.2 テスト駆動開発
  5.5.3 開発者テストを支える要点
  5.5.4 開発者テストを書く習慣をチームに定着させる
  5.5.5 開発者テストの網羅性の確保
  5.5.6 開発者テストの有効性の確保
  5.5.7 開発者テストの保守性の確保
  5.5.8 開発者テストをチームの資産として運用する
  5.5.9 開発者テストにおける生成 AI の活用
 5.6 テストを支える
  5.6.1 テストの作成のサポート
  5.6.2 テストの評価のサポート
  5.6.3 テスト実行のサポート
  5.6.4 テストの品質の確保
  5.6.5 テストの保守性の確保
  5.6.6 テストの信頼性の確保
  5.6.7 テストコードの品質確保
 5.7 リリースを支える
  5.7.1 コンテキストに合わせたリリース方針の選択

第6章 開発活動を支える
 6.1 変更を支える
  6.1.1 変更に対する事前の備え
  6.1.2 変更に対する品質保証の流れ
  6.1.3 変更レビューを支えるバージョン管理システムと CI/CD
  6.1.4 リスクベースドテストでピンポイントにリスクを確認する
 6.2 ユーザビリティ確保を支える
  6.2.1 ユーザビリティとは
  6.2.2 ユーザビリティの品質保証の流れ
  6.2.3 数字や言葉で扱えないユーザビリティを扱う
  6.2.4 A/B テスト
  6.2.5 ヒューリスティック評価 / エキスパートレビュー 
 6.3 性能効率の確保を支える
  6.3.1 性能効率性とは
  6.3.2 性能効率性の要求分析
  6.3.3 性能効率性確保のポイント
  6.3.4 性能確保の品質保証の基本方針
  6.3.5 性能テストの要点
 6.4 生成 AI の活用を支える
  6.4.1 ソフトウェア開発での生成 AI の活用
  6.4.2 生成 AI の課題に対応するためのポイント
  6.4.3 生成 AI により開発のボトルネックになる品質保証

第7章 運用・保守を支える
 7.1 運用・保守を支える
  7.1.1 現代的なソフトウェア開発における運用・保守
  7.1.2 プロダクトのデプロイプロセスの整備
  7.1.3 顧客満足の評価と改善
  7.1.4 運用と開発の連携による継続的改善
  7.1.5 ソフトウェアの適切な廃止
  7.1.6 品質悪化への対応 
  7.1.7 フィールドエンジニアリングで品質保証の力を磨く
 7.2 運用中のモニタリングと評価
  7.2.1 モニタリング指標の選択
  7.2.2 モニタリングの仕組みの構築
  7.2.3 プロアクティブアプローチの推奨
  7.2.4 シフトライトテスト
 7.3 市場の品質問題管理
  7.3.1 市場の品質問題の詳細
  7.3.2 市場の品質問題に対応する姿勢
  7.3.3 市場の品質問題への備え
  7.3.4 品質問題情報の蓄積
  7.3.5 市場の品質問題対応プロセスの整備

第8章 マネジメントを支える
 8.1 開発計画を支える
  8.1.1 品質計画の策定
  8.1.2 顧客満足に向けた開発計画の改善
  8.1.3 開発計画の評価と洗練
  8.1.4 見積もりとコミットメントを分ける
  8.1.5 計画の保守・運用
  8.1.6 品質戦略の策定
  8.1.7 アジャイル開発での計画のサポート
 8.2 マネジメントのモニタリングとコントロール
  8.2.1 品質保証としてプロダクトマインドセットを維持する
  8.2.2 品質の積み上げで進捗と品質を連動させる
  8.2.3 事前のモニタリング設計に注力する
  8.2.4 品質保証としてのマネジメントモニタリング
  8.2.5 モニタリングに基づいたコントロール
 8.3 リスクマネジメント
  8.3.1 リスクとリスクマネジメント
  8.3.2 リスクマネジメントの活動
  8.3.3 品質保証としてのリスクマネジメント
  8.3.4 品質リスクのマネジメントの要点

第9章 品質保証を支える仕組みの構築
 9.1 顧客満足を継続的に支える仕組みの構築
  9.1.1 有効な仕組み作りの工夫
  9.1.2 ISO 9000 の品質マネジメントの原則 
  9.1.3 仕組みとしての開発インフラの整備
  9.1.4 CI/CD 
  9.1.5 バージョン管理システム
  9.1.6 ビルドシステム
 9.2 開発プロセスの構築
  9.2.1 開発プロセスの構成
  9.2.2 開発プロセスモデルと開発方法論
  9.2.3 プロセスアプローチ
  9.2.4 品質保証を支える開発プロセスの構築方針
  9.2.5 開発プロセスを構成する各工程の構築方針
  9.2.6 品質保証を支援するプロセスを整備する
  9.2.7 生きたプロセスを維持する
  9.2.8 開発プロセス構築の流れ
 9.3 開発プロセスの改善
  9.3.1 開発プロセス改善の要点
  9.3.2 開発プロセス改善の流れ
 9.4 品質保証体制の構築
  9.4.1 組織構成と役割・責任の設計
  9.4.2 組織としての品質保証能力の維持
  9.4.3 品質保証を支える教育 
 9.5 品質保証を支える品質文化の形成
  9.5.1 あるべき姿を立脚点にする
  9.5.2 品質文化の形成
  9.5.3 品質文化の可視化
  9.5.4 品質についての適切なコミュニケーションの活性化
  9.5.5 チーム全体アプローチの実現
 9.6 品質保証の人材確保
  9.6.1 品質保証人材に求められる能力
  9.6.2 品質保証にかかわる人材の採用
  9.6.3 人材採用の流れ

第10章 品質保証に関わる手法・技術・アプローチ
 10.1 レビューの方法・技法 
  10.1.1 レビュー運営種別 
  10.1.2 レビューのリーディング技法 
  10.1.3 チームレビューの進め方 
  10.1.4 プルリクエスト/マージリクエストレビューの進め方
  10.1.5 パースペクティブベースドレビューの進め方 
  10.1.6 モビング(チーミング)の進め方
 10.2 システムテストのテスト設計アプローチ 
  10.2.1 システムテストでのテスト設計プロセスの工夫 
  10.2.2 テスト分析 
  10.2.3 テスト設計 
  10.2.4 テスト実装 
 10.3 モデリング 
  10.3.1 モデリングは要点を表現する 
  10.3.2 モデルの記法 
  10.3.3 モデリングは探索的 
  10.3.4 品質保証を支えるモデリング 
  10.3.5 状態遷移モデルによる状態分析 
  10.3.6 Why ツリーによる原因分析

G-Evalを使用してLLM-as-a-judgeを行う

 生成AIを組み込んだソフトウェアの評価では、出力の不確実性や柔軟性が大きいことから、対象をどう評価するかが定番の課題となります。その有望な対応手段にLLM-as-a-judgeがあります。
 LLM-as-a-judgeは、生成AIの出力結果の評価・採点を、精度の高いLLMを使って実施する手法です。LLMの柔軟性を活用して、多数かつ幅広い評価の自動化が行えるようになります。

 今回はそのLLM-as-a-judgeの実装例を解説します。評価の実装および実行環境は「生成AIアプリケーション評価入門」の解説を参考にしています。

LLM-as-a-judgeの評価者に使用するLLM

 評価にはLLM-as-a-judgeのフレームワークであるG-Evalを使用します。
 またG-Evalが利用するLLMとして、OpenAIのGPTを使用します(そのため以降のコード実行においては、本ブログ執筆時点ではOpen AIに課金し、API Keyを有効にする必要があります。有効化したKeyは実行環境の環境変数OPENAI_API_KEYに登録しておきます)。

評価対象のLLM

 評価対象は、ローカルで実行できるLLM、ollamaを使用します。テスト自動生成でのテスト観点の生成を想定して、「プロダクト品質モデルの主特性の名前を列挙してください」のような課題を指定し、その正確性を評価します。

LLM-as-a-judgeの実装

 LLM-as-a-judgeの実装を以下に示します。

from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCase, SingleTurnParams
from openai import OpenAI
import ollama

# テスト対象
input="ISO/IEC 25010におけるプロダクト品質モデルの主特性の名前を列挙してください"
response = ollama.chat(model='llama2', messages=[{'role': 'user', 'content': input,}], options={'temperature': 0})

# 評価内容(生成AIアプリケーション評価入門より)
correctness_metric_ja = GEval(
	name="正確性",
	evaluation_steps=[
	"実際の出力に含まれる事実が、期待される出力に含まれる事実と矛盾していないかを確認してください。",
	"重要な情報が省略されている場合は大きく減点してください。",
	"曖昧な表現や意見の相違は許容されます。",
	"評価理由は日本語で記述してください。"
	],
	evaluation_params=[
		SingleTurnParams.INPUT,
		SingleTurnParams.ACTUAL_OUTPUT,
		SingleTurnParams.EXPECTED_OUTPUT,
	]
)

# 評価
test_case = LLMTestCase(
	input=input,
	actual_output=response['message']['content'],
	expected_output="Functional Suitability, Performance Efficiency, Compatibility, Interaction Capability, Reliability, Security, Maintainability, Flexibility, Safety"
)

correctness_metric_ja.measure(test_case)
print("score:", correctness_metric_ja.score)
print("reason:", correctness_metric_ja.reason)

 ollama.chatで、評価対象に対し、課題を提示し回答を取得しています。
 そして評価対象の正確性についてG-Evalで評価しています。このうちevaluation_stepsが正確性をスコア評価するための評価指針になります。このコードでは前述の「生成AIアプリケーション評価入門」のサンプルをそのまま参考にして使用しています。

評価結果

 前述の評価コードの実行結果は以下に様になります:

score: 0.13775406687981456
reason: 期待される主特性は Functional Suitability、Performance Efficiency、Compatibility、Interaction Capability、Reliability、Security、Maintainability、Flexibility、Safety の列挙だが、実際の出力は Reliability、Security、Maintainability 以外に Durability、Customizability、Scalability、Accessibility、Environmental impact、Social responsibility など期待にない項目を多数含み、Compatibility・Interaction Capability・Flexibility・Safety・Functional Suitability が欠落しています。Performance も Performance Efficiency と厳密には一致せず、重要情報の省略と不正確な追加が大きいため低評価です。

 正確性は、事前に提示した評価指針に基づいて、0~1でスコア付けして提示しています。また、reasonとしてその理由を提示し、正確性の評価が妥当か確認できるようにしています。
 表示されている結果の通り、Judge側のLLMが、テスト対象の情報の古さや誤りを見つけ出し、減点していることが読み取れます。

副作用の局所化

 副作用の局所化はソフトウェアの設計アプローチです。対象のコンポーネントの副作用の発生可能性の軽減と、副作用の影響範囲の局所化を行うアプローチを指します。副作用の局所化は疎結合設計を支え、バグが少なく保守しやすいコードを生むために普遍的に重要な設計アプローチと言えます。

副作用とは

 ソフトウェアの設計やプログラミングにおける副作用は、意図した直接の出力とは別におこる作用を指します。例えば特定の値を返すことを目的とした関数を対象とした場合、値を返す以外に、内部でグローバルな状態・静的な状態を更新していれば、その状態更新が副作用になります。

 副作用を起こすものに以下があります。

  • グローバルな変数の更新
  • 静的な変数の更新
  • 共有するリソースやデータの更新
  • I/Oの実行
  • バッファオバーフローといった異常状態の発生
  • 上記を発生させる関数の呼び出し

 副作用は状態更新以外のものもあります。意図せず他の処理のタイミングや時間、順序を不適切に変えてしまう作用(例えばRTOSで動くソフトウェアにおいて、プロセッサリソースを占有して、他タスクの処理シーケンスを乱してしまうなど)も副作用です。ただし、副作用の局所化で語られる参照透過性や不変性などのコンテキストの中では、もっぱら副作用は状態更新に絞られます。

副作用の局所化

 副作用の局所化とは、副作用が発生する領域をなるべく小さな領域に閉じ込めること、副作用の発生可能性を軽減することを推進する設計アプローチです。
 状態更新の副作用に対しては、以下のような設計アプローチが、副作用の局所化に該当します。

  • 変数やリソースなどの状態のスコープを小さくする。グローバル変数を避ける
  • 静的な状態を少なくする
  • 横断的に共有される状態、静的な状態は、操作処理や、ガードといった防御的設計をまとめたオブジェクトとしてカプセル化する。状態更新についての防御的設計を複数に散在させない
  • 妥当な範囲で、参照透過性や不変性を確保する。例えば純粋関数(入力だけで出力を決め、外部に他の影響を発生させない関数)や、不変オブジェクト(初期化以外で状態を更新しない)として処理を実装する
  • バッファオーバーフローといった異常状態については、開発言語機能、型システム、コーディングルール、静的解析といった多重の手段で解消を目指す

 この副作用の局所化は保守性向上を中心に、さまざまな恩恵を提供します。主要なものを以下に示します。

  • デバッグが容易になる。バグ原因の発生可能性の範囲を狭く絞り込める
  • テストが容易になる。テストの入力・出力が明確化され、小さく絞り込まれる。ユニットテストなどで対象を切り出すのも容易になる
  • 再利用性を高める。再利用の際の前提条件や注意事項が小さくなる

不変オブジェクト

 副作用の局所化の実装手段として不変オブジェクトがあります。不変オブジェクトは、オブジェクト生成時の初期化を除いて、オブジェクトの状態を変更できないオブジェクトを指します。不変オブジェクトとして実装すれば副作用をなくせるわけではありませんが、副作用の発生可能性を下げ、副作用の範囲を削減する手段として有効です。

 例えばC++で以下のクラスUserがあるとします。

class User {
public:
    std::string name;
    int age;

    User(const std::string& name_str, int age_val) : name(name_str), age(age_val) {}
    
    ...さまざまなメソッド...
};

 このUserクラスはnameとageのパラメータを持ちます。この実装ではインスタンス化した後もnameとageを更新できるため、これは可変オブジェクトになります。外部から自由に更新できるため、Userのインスタンスは副作用の影響を受けやすいと言えます。

 このUserクラスを不変オブジェクトとして実装する場合、次のようになります

class User {
private:
    const std::string name_;
    const int age_;

public:
    User(const std::string& name, int age)
        : name_(name), age_(age) {}

    std::string getName() const { return name_; }
    int getAge() const { return age_; }

    ...さまざまなメソッド...
};

 すべてのパラメータについてconstを付与し、不変性を担保しています。Getterを定義する場合は、const宣言で内部のパラメータを更新しないことを担保します。

ドメインプリミティブな設計

 副作用の局所化と組み合わせるのが効果的な設計アプローチに、ドメインプリミティブという設計指針があります。これはドメイン駆動設計での用語で、プリミティブ型(開発言語の標準の基本型。intやstringなど)を使用せず、ドメインのコンテキストに合わせた独自型を使用すべきというものです。ドメインプリミティブの設計では、型にバリデーションを組み込み、オブジェクトが常に正しい状態の実現も目指します。ドメインプリミティブを遵守することで、不正な状態更新を抑止し、有害な副作用の影響を抑えることができます。

 例えば前述のUserクラスをドメインプリミティブの設計に基づいて改善した例を以下に示します。

class User {
private:
    const std::string name_;
    const int age_;

    static std::string validateName(const std::string& name) {
        if (name.length() < 1 || name.length() > 20) {
            throw std::invalid_argument("name must be between 1 and 20 characters");
        }
        return name;
    }

    static int validateAge(int age) {
        if (age < 0 || age > 200) {
            throw std::invalid_argument("Age must be between 0 and 200");
        }
        return age;
    }

public:
    User(const std::string& name, int age)
        : name_(validateName(name)), age_(validateAge(age)) {}

    std::string getName() const { 
        return name_; }
    int getAge() const { return age_; }

    ...さまざまなメソッド...
};

 ドメインプリミティブの設計では、ユーザの名前や年齢をstring型やint型そのままで定義するのではなく、上記のUserクラスで定義するように統一します。
 このUserクラスでは、値の初期化時に入力バリデーションを行い、不正な状態が指定された場合は例外で弾くように実装しています。こうした事前条件の評価・防御的処理を組み込むことで、常に正しい状態のオブジェクトしか存在しないように実装できます。これにより、有害な影響を発生させる副作用を抑止できます。

Androidをとりまくテストスイート(xTS:CTS、VTS、STS、GTS)の概要

 Android開発に関しては、仕様に準拠しているか、互換性を満たしているか、セキュリティといった特定の品質を実現しているか確認するためのテストスイートが複数提示されています。これらはAndroid xTSとも呼称されます。今回はその主要なxTSをまとめます。

CTS(Compatibility Test Suite)

 CTSは、対象のデバイスのAndroidのAPIが標準仕様を実現しており、デバイスが、サードパーティのAndroidアプリーケーションを動作させられるか確認するテストスイートです。

 CTSでは、端末メーカーのカスタマイズで標準のAndroid APIが壊れていないか、妥当な性能を確保しているかといった確認を行います。CTS合格の保証によりアプリケーション開発者に対するWORA(Write once, run anywhere:一回コードを書けばどのAndroid端末でも動作する)の実現を支えます。
 CTSはホスト環境から端末をリモートで操作して実行します。

 テストの概要やソースコードは公開されています。
互換性テストスイート(CTS)の概要  |  Android Open Source Project

 CTSのテストベースは以下のCDD(Compatibility Definition Document)になります。
Android 互換性定義ドキュメント  |  Android Open Source Project

 注意点として、テストコードを確認すれば分かりますが、CDDに対してCTSは基本的な確認しか実施していません。CDDの実現保証には、手動テストやレビュー、網羅性を高めたテストも必要です。

VTS(Vendor Test Suite)

 VTSは、Androidのハードウェアベンダーインターフェースが要件を実現しているか確認するテストスイートです。

 VTSは複数のテストで構成されます。構成要素の一つに、Google TestでHALを切り出してテストするコンポーネントテストがあります。またLinux Kernelを接続した統合テストやAPIテスト、LTP(Linux Test Project)準拠のテストによる、OSとハードウェアの疎通確認も含まれます。そこではGSI(Generic System Image)を使って汎用的な確認を実施します。
 VTSでハードウェアベンダーインターフェースの実現性保証を行うことで、ハードウェアバリエーションが増加・爆発した状態でも、円滑にAndroidフレームワークをアップデートしたり、Android APIの標準性を担保したりできるようにする状態を実現します。

 VTSのテストベースは、VSR(Vendor Software Requirements)と呼ばれるベンダーに対する一連の要件とHALの設定になります。VSRは前述のCDD中のハードウェアベンダーに対する要件や、ベンダーAPI仕様で構成されます。

 VTSとCTSの関係を以下に示します。


GTS(Google Mobile Services Test Suite)

 Google提供のサービス(Play Services等)やアプリ(Playストア等)の動作確認を行うテストスイートです。Google自身のサービスの品質確認を行うほか、端末メーカーがGoogleサービスを組み込んで出荷する際に正しく組み込まれているかの確認で使われます。一般公開されていません。

STS(Security Test Suite)

 Androidの脆弱性対策、セキュリティ確保が適切か確認するテストスイートです。テストを実行する仕組みは公開されており、関係開発者が実行可能になっています。ただテスト内容の詳細は、脆弱性情報や攻撃手段を含むため一般公開されていません。

GitHub Copilotでユニットテストを楽に構築・運用する

 十分かつ有効なユニットテストが配備されるとさまざまな恩恵が提供されます。リグレッションテストとして使用可能です。またリファクタリングや追加変更もより安全に進められるようになります。
 ただユニットテストは数が多くプロダクトコードと結合しているため、その構築・保守には手間がかかりがちです。特にプロダクトコードのテスト容易性が低いとその手間が大きくなり、ユニットテストの費用対効果が悪化します。
 こうしたユニットテストを取り巻く煩雑な手間についてですが、生成AIのサポートでかなり手間を削減できます。今回は生成AIサービスのGitHub Copilotを例に、ユニットテストの構築・運用を楽にする簡単なテクニックをまとめます。

ユニットテストを生成する

 GitHub Copilotを使うとテストコードのスケルトンやたたき台を容易に生成できます。例えば次のようなコードがあるとします。

target.cpp

#include <utility>
#include <vector>
#include "target.h"

int partition(std::vector<int>& arr, const int low, const int high) {
    const int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(std::vector<int>& arr, const int low, const int high) {
    if (low < high) {
        const int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

target.h

#pragma once

#include <vector>

void quickSort(std::vector<int>& arr, const int low, const int high); 

 こうしたファイルに対しては、GitHub Copilotのチャットビューで「test.cppに対するユニットテストを作成してください。別ファイルで作ってください」と指示を打ち込みます(今回は簡単な例なのでこれで済みますが、複雑なプロジェクトに対しては追加のプロンプトが求められます)。
 するとGitHub CopilotはGoogle TestやCMakeなど必要なツールのインストールを許可を取りながら進めます。そして以下のユニットテストファイルを生成します。

test_target.h

#include <gtest/gtest.h>
#include <vector>
#include "target.h"

TEST(QuickSortTest, SortsAlreadySortedVector) {
    std::vector<int> arr = {1, 2, 3, 4, 5};
    quickSort(arr, 0, static_cast<int>(arr.size()) - 1);
    EXPECT_EQ(arr, std::vector<int>({1, 2, 3, 4, 5}));
}

TEST(QuickSortTest, SortsReverseSortedVector) {
    std::vector<int> arr = {5, 4, 3, 2, 1};
    quickSort(arr, 0, static_cast<int>(arr.size()) - 1);
    EXPECT_EQ(arr, std::vector<int>({1, 2, 3, 4, 5}));
}

TEST(QuickSortTest, SortsRandomOrderVector) {
    std::vector<int> arr = {3, 1, 4, 1, 5};
    quickSort(arr, 0, static_cast<int>(arr.size()) - 1);
    EXPECT_EQ(arr, std::vector<int>({1, 1, 3, 4, 5}));
}

TEST(QuickSortTest, SortsSingleElementVector) {
    std::vector<int> arr = {42};
    quickSort(arr, 0, static_cast<int>(arr.size()) - 1);
    EXPECT_EQ(arr, std::vector<int>({42}));
}

TEST(QuickSortTest, EmptyVectorDoesNothing) {
    std::vector<int> arr;
    EXPECT_NO_THROW(quickSort(arr, 0, static_cast<int>(arr.size()) - 1));
    EXPECT_TRUE(arr.empty());
}

 またGitHub Copilotは同時にCMakeLists.txtといったビルド・実行のための設定ファイルも自動生成・更新し、生成したユニットテストをビルド・実行可能にします。
 実行結果です:

[       OK ] QuickSortTest.SortsRandomOrderVector (0 ms)
[ RUN      ] QuickSortTest.SortsSingleElementVector
[       OK ] QuickSortTest.SortsSingleElementVector (0 ms)
[ RUN      ] QuickSortTest.EmptyVectorDoesNothing
[       OK ] QuickSortTest.EmptyVectorDoesNothing (0 ms)
[----------] 5 tests from QuickSortTest (0 ms total)

[----------] Global test environment tear-down
[==========] 5 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 5 tests.

 注意点として、生成されたユニットテストのテストケースは通常そのまま信用できるのではなく、修正や補強が必要です。ただたたき台を自動生成させれば、ゼロからテストコードを手打ちするよりはるかに楽にユニットテストを確保できます。

テストコードの実装を補完させる

 GitHub Copilotでは、テストコードのプログラミングの中で細部のスニペット生成や実装候補の提案も高度に実施してくれます。
 例えば前項で生成したユニットテストのファイルで「//巨大な要素数で動作することを確認 」とコメントを入力してコード提案を実行させると、コメントに沿ったテストメソッドが提案されます。

 また高度なコード補完・コードスニペット生成手段としても有効です。テストメソッド中に「E」を入力すると、EXPECT_EQのアサーションメソッドの記述が提案されます。


プロダクトコードの変更に追従させる

 GithubCopilotではプロダクトコードの変更対応も高度に自動化できます。
 例えばプロダクトコードに次の関数を追加したとします。

target.cpp

void BubbleSort(std::vector<int>& arr) {
    const int n = static_cast<int>(arr.size());
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                std::swap(arr[j], arr[j + 1]);
            }
        }
    }
}

 こうしたプロダクトコード変更に対しては、チャットビューで「target.cppを網羅するユニットテストを作成してください。既存のユニットテストtest_target.cppを修正して対応してください」といったプロンプトを入力すると、プロダクトコードの変更に追従したテストコード修正が実施されます。今回の例では、追加関数に対する追加テストコードが作成されます。

 また例えば追加した「BubbleSort」を「bubbleSort」に置換した場合に対するセルフヒーリングも可能です。例えば「target.cppの変更に対応して、ユニットテストを修正してください」とプロンプトを実行すると、置換に対応したヘッダファイルとテストコード修正が実施され、実行可能確認が行われます。

テスト駆動開発のサポートを行う

 テスト駆動開発は、生成AIとかなり親和性が高いプログラミングスタイルだと前々からよく言われています。それは事実なのですが、注意点として生成AIを前提とした場合、テスト駆動開発の進め方やサイクルサイズが変化します。
 通常テスト駆動開発は次のステップで進めます。

1. 仕様をテストリストとして明文化する
2. テストリストの1項目について、テストを書いて失敗させる
3. テストの失敗を成功に変えるようにプロダクトコードを変更する

※このうち2、3をテストリスト全てに対応するまで繰り返す
※また一定のプロダクトコードがたまったら、適宜リファクタリングを実施してプロダクトコードを綺麗にする

 これを生成AI活用を前提とすると、次のような流れになります。

1. 仕様をテストリストとして明文化する
2. テストリストについて、プロダクトコードとテストコードを一緒に生成する
3. テストコードのテスト設計・テスト実装を適切なものに修正する
4. テスト成功状態を維持しながら、プロダクトコードを適切なものに修正する

 通常のテスト駆動開発では、テスト失敗→テスト成功のテストファーストのサイクルは数十秒から数分の短期サイクルです。しかし生成AIを使った場合、一気にすべてを生成できるため、サイクルが大きくなります。さらにテストを失敗するステップの恩恵が小さくなり、多くの場合スキップすることになります。

テスト工程・レビュー工程の全体の建付けを考える際に留意が必要な効果・法則

 テスト工程やレビュー工程の建付け(どのようなテスト工程をプロセス上に設けるか等)を考える際に、注意すべき効果や法則があります。今回はそのいくつかを紹介します。

多重チェックの落とし穴

 問題を見逃さないように、チェックを何重にも多重化すると、問題検出率がむしろ悪化するという傾向を多重チェックの落とし穴と呼びます。
 例えばレビューでのミス検出率について、レビューを1重化(一人が一回だけレビュー)すると検出率は65%になり、2重化(二人が別のタイミングでチェック)すると80%に改善する一方で、3重化すると65%、4重化すると55%と、多重化を増やすほど検出率が下がる調査結果が存在します。

 これは、レビューの多重化を増やすほど、個々の問題検出率も、全体の問題検出率も下がっていく傾向を示します。この傾向は多重化により自分が注力しなくても他が見つけてくれるという期待が発生することで生まれると考えられています。
 より難しい問題を見つけて高品質を実現したいという目的に対しては、逆行する傾向と言えます。

 この傾向は品質保証の体系や仕組みを構築する際にも注意が必要です。高品質の実現のため同じようなテスト工程・レビュー工程を何重にも設けても、工数やコストを悪化させる一方で品質を悪化させる可能性があるためです。
 なおこれは最初から「他者が見るから手を抜こう」という悪意を持った担当を避けても、発生しえる落とし穴です。というのも現場の開発ではスケジュールやリソースの不足に追われる場面は少なくありません。そこで他に同じようなテスト工程があれば、やむを得ず自分達はテストより他の作業を優先する、という判断はしばしば発生します。

 この多重チェックの落とし穴を回避して高品質を実現するためには、次のような工夫が求められます。

  • レビューやテストを多重化させる場合は、それぞれの視座や観点に違いを設け、各レビュー・テストのバリエーションを増やす
  • 各レビューやテストの責任を明確化する。例えば特定のバグはどこで防ぐべきか具体化する
  • レビューやテスト、あるいは品質保証についてのリーダーとその責任を明確にし、リーダーシップを発揮させる 

リンゲルマン効果・社会的手抜き

 共同作業をする際に、分担する人数が増えるほど、個々の個人の貢献やリソースパワーが減っていく効果を、リンゲルマン効果や社会的手抜きと呼びます(元の文献は、綱引きで人数を増やすほど個々人の力の入れ具合が弱まる傾向を示す)。これは、人数が増えるほど、個人の貢献や責任が希薄になるほか、連携のために効率が低下することで発生すると考えられています。

 このリンゲルマン効果・社会的手抜きも、多重チェックの落とし穴と同じように、テスト工程・レビュー工程の体系や建付けの構築において注意が必要です。例えばレビューを強化するにしても単純にレビューアを増やすだけだと、個々のレビューアの力が活かされにくくなる可能性があるためです。

 高品質の実現のためには、個人の能力の発揮を引き出すことが求められます。そこでこのリンゲルマン効果・社会的手抜きを回避して高品質を実現するためには、前述の多重チェックの落とし穴と同様のアプローチが求められます。すなわち各人に異なった視座や観点を割り当てる、各人の責任を明確化する、責任あるリーダーにリーダーシップを発揮してもらう、といった工夫が求められます。

今年の登壇・執筆活動まとめ

 登壇は基本的に受け身のみで、お声がけいただいたら対応しています。ただそれでも、今年は6月にソフトウェアテスト徹底指南書を出版してからありがたいことに多数の登壇依頼をいただき、かなりの講演・登壇をすることになりました。自分は講演上手でないと自覚しているので、ありがたい一方で、大変恐縮しています。振り返りとして、今年の登壇活動・執筆活動のうち、オープンにできるものをまとめたいと思います。

登壇系

■講師「テスト分析入門」

CADDi様社内勉強会

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

■講演「チームのテスト力を総合的に鍛えて品質、スピード、レジリエンスを共立させる」

「チームのテスト力を総合的に鍛えてソフトウェア開発の高品質と高スピードを両立させる実践技法」勉強会

https://speakerdeck.com/goyoki/testing-approach-that-improves-quality-speed-and-resilience speakerdeck.com

■講演・パネルディスカッション「書籍著者が語る!高品質と高スピードを両立させる!ソフトウェアテスト徹底指南書のご紹介」

コニカミノルタ様社内勉強会

■講師「プロダクト開発を成功させるためのソフトウェア品質保証のアプローチと技術」

DXPO東京25夏

https://speakerdeck.com/goyoki/software-qa-approach-for-puduct-success speakerdeck.com

■LT・パネルディスカッション「著者・訳者が徹底指南!2冊のフルスタックな書籍に学ぶ『テスト』の極意」

https://speakerdeck.com/goyoki/introduction-for-software-test-tettei-shinansho speakerdeck.com

■講演「チームのテスト力を鍛える」

「『ソフトウェアテスト徹底指南書』に学ぶ、品質とスピードを高める実践アプローチ」勉強会

https://speakerdeck.com/goyoki/develop-teams-testing-capabilities speakerdeck.com

■招待講演「チームのテスト力を総合的に鍛えてシフトレフトを推進する」

Veriserve Mobility Initiative 2025

https://speakerdeck.com/goyoki/shifting-left-with-software-testing-improvements speakerdeck.com

■壇上コンサルティング「テストデータの準備・保守の終わらない戦い…適切な方法は?」「AIが品質保証をする未来はどうすれば創れる?」

JaSST Online Gerbera
https://jasst.jp/online/gerbera-about/

■招待講演「自動テストを活かすためのテスト分析・テスト設計の進め方」

JaSST’25 Shikoku

https://speakerdeck.com/goyoki/jasst25-shikoku speakerdeck.com

■招待講演「 開発に寄りそう自動テストの実現」

ソフトウェアテスト自動化カンファレンス2025

https://speakerdeck.com/goyoki/automated-testing-integrated-with-development
speakerdeck.com

執筆系

■書籍「ソフトウェアテスト徹底指南書 〜開発の高品質と高スピードを両立させる実践アプローチ」

技術評論社
ソフトウェアテスト徹底指南書 | 技術評論社

■研究論文「Componentwise Automata Learning for System Integration」(共著)

ATVA 2025
https://arxiv.org/pdf/2508.04458