ソフトウェアの品質を学びまくる

ソフトウェアの品質、ソフトウェアテストなどについて学んだことを記録するブログです。

アジャイル開発におけるメトリクスには、どういうものがあるのか

 アジャイル開発において、プロダクトや組織の現状を把握するのに役立つメトリクスを知りたいと思い、「agile kpi」などでググって上位のサイトを読むという丁寧なサーベイを敢行。20個くらい読んでいくとだいぶネタも尽きてきたので、整理してみました*1。参考にしたサイトについては、記事の最後に載せておきます。
 なお以下では、アジャイル開発とスクラム開発をほぼ同じ意味で使っていますが、怒らないでいただきたいですね。

メトリクスとKPI

 メトリクス(metric / metrics*2)とKPIはごちゃごちゃに使われがちです。こちらの資料では、以下のように説明しています。

www.slideshare.net

  • メトリック: プロセス・プロダクト・チームの定量的な評価・制御・改善のための物差し、またはその組み合わせ
  • KPI: 戦略的な目標に強く紐づけられた、最低1つの期限付き目標をもったメトリック

 KPIの設定方法として、有名な「SMART」や「INVEST」といった基準に言及しています。

 では、各サイトで言及されているメトリクスについて紹介していきます。
 ざっくりと、「開発能力」「進捗」「プロセス」「品質」に区分していますが、複数の区分にまたがるものもあります。

開発能力

 チームの開発能力を測ることのできるメトリクスは、以下のものです。

  1. ベロシティ
  2. サイクルタイム
  3. 平均欠陥修復時間 (MTTR)

ベロシティ

 スプリント内で完了できた仕事の量。
 仕事の量は、見積もり規模であったり、ストーリーポイントであったり、チームによってさまざまでしょう。
 ベロシティを測定する目的の一つは、自分たちが一定期間に開発できる量を知り、見積もりの精度を上げることです。スプリントを重ねていくと、ベロシティが安定していくことが期待できます。低下傾向にあるようなら、開発プロセスに非効率な部分があるとか、コードの保守性が悪化しているといった問題の兆候かもしれません。

サイクルタイム

 タスクの消化にかかる平均時間。要件の特定からリリースまでの「リードタイム」(TTM)の対となる概念という扱いです。
 具体的には、タスクが仕掛中(doing)になっている時間を測っています。タスクが適切に分割されていれば、サイクルタイムは一定になるはずで、開発能力や開発プロセスの安定を見ることができるという理屈です。
 このメトリクスは今回の調べる中で初めて知ったのですが、扱っているサイトは多く、一般的なもののようです。

 下の図は、Main Agile Software Development Metrics and KPIsから引用したものです。平均、移動平均、標準偏差と、サイクルタイムを1つの図に表現しています。

13-768x456_CycletTimeChart

 サイクルタイムが短いということは、開発の能力が高いというだけでなく、品質問題による手戻りが少ない、作業の待ち時間が短い、コンテキストスイッチが少ないなど、品質や開発プロセスの良さによるものかもしれません。逆もしかりで、サイクルタイムの悪化は、品質やプロセスに何か問題のある兆候と考えられます。
 サイクルタイムは「安定して」「徐々に短くなっている」ことを目指すことになります。実装と欠陥改修のタスクを分けて計測しているケースもあるようです。

平均欠陥修復時間 (MTTR)

 欠陥修復までの平均時間。欠陥数 / 改修のためのコーディング時間 で算出します。
 MTTR(Mean Time To Repair、平均修理時間)は、コンピュータシステムの保守性を表す指標として有名です。これを開発チームの効率を測るのに使ったものです。

www.dospara.co.jp

 ここでは「開発能力」にカテゴライズしていますが、問題発見から原因特定・改修・テストまでのプロセスが円滑であるとか、原因の特定や改修が容易な、保守性の高いプロダクトであるといったことも考えられます。
 この指標も、たとえば「バージョンを重ねるごとにMTTRが低下しているのは、技術的負債が増加しているためでは」「品質が悪すぎるので、新規開発を止めて改修に専念すべきなのでは」というように、問題の兆候と捉えたり対策を打ったりするのに役立ちそうです。 

進捗

 開発の進捗を測ることのできるメトリクスは、以下のものです。

  1. バーンダウンチャート
  2. テスト進捗率

バーンダウンチャート

 進捗をみるうえで代表的なのが、タスクの消化具合を可視化した、このバーンダウンチャートでしょうか(バーンダウンチャート自体はメトリクスではありませんが・・・)。残日数と仕事量から算出した理想線と実際の線を比較することで、進捗状況の把握や、完了見込みの推定を行います。スプリント単位でも、バージョン単位でも用途があります。

 ポイントとして、「開発の途中での総作業量の増加」を見える化することも大事です。ある1日に10SPの仕事を消化した一方で、何らかの理由で10SPの仕事が追加されてしまった場合に、単に「グラフが下がらない」と見えるだけだと困ります。「消化した分、下がる線」(傾きがベロシティに相当)と、「追加された分、上がる線」(各時点での全作業量に相当)を見られるようにしておくべきでしょう。
 他の表現方法として、「追加された分を棒グラフの負の側に出す」というものもありました。こちらの方が直観的にわかりやすいかもしれません。

 下の図は、サイクルタイムと同様、Main Agile Software Development Metrics and KPIsから引用しています。

7-768x430_ReleaseBurndown

 なおアジャイル開発において、1つの開発バージョンの中でスコープの変更が起こることは必然です。一方スプリントの中で頻繁にスコープクリープが発生するのはよくないことでしょう。要件の受け入れやタスクの工数見積もりに問題がないかの確認が必要です。

テスト進捗率

 テスト全数に対し、完了しているテストの数で、時間推移を見ます。
 チケット管理と一元化されているか、テスト管理ツールで別管理するかはチームによります。

プロセス

 プロセスの良しあしを測ることのできるメトリクスは、以下のものです。

  1. タスクのステータス分布
  2. リリースオーバーヘッド
  3. フロー効率性
  4. 予見性

タスクのステータス分布

 タスクのステータス(たとえば todo、doing、doneの3種類)を色分けして積み上げたもの(累積フローチャート、累積フロー図)です。doingの層の横幅が、上述のサイクルタイムに相当します。また、Main Agile Software Development Metrics and KPIsから引用しています。

14-1-768x464_CumulativeFlowDiagram

 健全な状況では時間ともにdoneの割合が増えていくことが期待されれます。
 doingの割合が増加傾向にあれば、doneに進められないようなプロセス上の問題や、ブロックしているバックログがあるのかもしれません。あるいは、WIP(Work in Progress)タスクが多すぎてコンテキストスイッチが多発し、開発効率が落ちているかもしれません。チームメンバーのWIP数と流出欠陥には相関があるという調査(PDF)もあります。

 これもやはり、問題を早期に発見するためのツールといえるでしょう。
 タスクの種類が、実装なのかバグの改修なのか、はたまた環境構築なのかによってグラフを分けてみれば、新しい知見があるかもしれません。インシデントレポートはどんどん増えているのに、一向にdoingにならない、とか・・・。 

リリースオーバーヘッド

 リリースのためにかかる時間やコストを表します。
 短い周期で定期的にリリースするのであれば、そのための時間とコストを小さくする必要があります。RC(Release Candidate)のテストやデプロイ前の確認フェーズでの時間やコストを計測し、ボトルネックを見つけることで、プロセスの効率化を図ります。

フロー効率性

 実働時間と待ち時間の割合を示します。
 他のタスクの完了を前提とするようなタスクが増えてくると、前提タスクの完了を待つ時間も長くなってしまします。そのような非効率を可視化し、プロセスの劣化やボトルネットを検出することができます。

 このフロー効率性と、対をなす概念であるリソース効率性については、以下のページがとてもわかりやすいです。

i2key.hateblo.jp

予見性

 各メトリクスにおける計画と実績の乖離をみるという、メタ的なメトリクスです。
 メトリクスについていろいろな記事を読んでいると、「開発能力が高い」ということと同じくらい、「安定している」ことが求められていることに気づきます。プロセスが安定していて、見積もりの精度が高い。これをpredictability(予見可能性)と呼んでいます。
 予定との差の大きさや、実績値の標準偏差を測定し、ばらつきが少なくなるように改善をすることになります。ばらつきが少なければ、見込むバッファも小さくてすむようになります。 

品質

 もともとこれが知りたくて調査を始めたのでした。
 品質の良しあしを測ることのできるメトリクスは、以下のものです。

  1. コードカバレッジ
  2. コードの複雑度
  3. コードチャーン (code churn)
  4. 欠陥密度
  5. 欠陥の出方
  6. DDP (Defect Detection Percentage、欠陥検出率)
  7. 平均欠陥検出時間 (MTTD)
  8. テストの成功率
  9. ビルドの成功率

コードカバレッジ

 JSTQB用語集(PDF)での定義は、以下の通り。

テストスイートが、ソフトウェアのどの部分を実行(カバー)し、どの部分が未実行かを判定する分析手法。たとえば、ステートメントカバレッジ、デシジョンカバレッジ、条件カバレッジ。

 品質の十分条件というより、必要条件的に使われることが多いと思います。
 ステートメントカバレッジ・デシジョンカバレッジ100%を目指す組織もあれば、『知識ゼロから学ぶソフトウェアテスト』では「一般の商用ソフトウェアなら60~90%で十分」としています。(60~90は幅広いですけど・・・)

 カバレッジの種類はいくつかあるので、意味を理解しておく必要があります。このブログでは以下で紹介しています。

www.kzsuzuki.com

コードの複雑度

 ソースコードがどれだけ複雑かを定量化したものです。
 コードが複雑であれば、欠陥を埋め込みやすく、テストも複雑になるため、プロダクトの品質の悪化につながるでしょう。 また、同じ理由で改修も難しくなってしまうので、保守性も悪くなり、長期的な品質に影響します。複雑度は、欠陥を埋め込みやすいリスクの高いコードを特定し、コードレビューやリファクタリングの優先順位付けに利用することができます。

 複雑度の例としては、ネストの深さやクラスの結合度、McCabeのCyclomatic複雑度、Halsteadの複雑度などがあります。ただ、人間と機械では複雑さに対する感覚が異なるので注意が必要です。先日の機械学習のイベントでも、富士通アプリケーションズさんのお話で、「コード解析では複雑と判定されなくても、人間にとっては読みづらいコードを学習させる」というお話がありました。

www.kzsuzuki.com  またこれ以外のコードメトリクスとして、コードクローンやセキュリティ脆弱性など、主にツールを使って検出するものもありますね。

コードチャーン (code churn)

 どのくらいのコードが追加・削除・変更されたかという情報から、開発ステージごとのコードの安定性を見る指標です。
 Qiitaの @hirokidaichi さんの記事では、以下のように説明されています。

複数人で複数回にわたって編集されたファイルは複数の目的でコードが編集されている訳ですから、「単一責務原則」を違反している可能性が高く、潜在的にバグを内在していそうだということです。

qiita.com

 増加傾向(コードの修正が頻繁になっている)であれば、テストが不足している可能性があります。また、リリース直前に複数の人間による多くのコミットがあれば、そのコードが安定しているかを調査する必要があるでしょう。

欠陥密度

 開発規模(SLOC、FP、ストーリーポイントなど)あたりの欠陥の数です。WFでも何かと議論になりがちなメトリクスですね。
 たとえばSLOC(source lines of code)を分母とするケースでのわかりやすい欠点としては、実装の難しさやコードの複雑さが考慮されないというものがあります。「大きな組織で1つの基準を設け、全プロジェクトでその基準を参照する」よりは、1つのプロダクトの中でトレンドを見る方がよさそうです。SPを分母にする場合は、他チームとの比較がそもそもできないでしょう。
 ベロシティなどと同様、安定していれば見積もりにも使えるようになりますし、何らかの異常を検知することもできます。

欠陥の出方

 アジャイル開発に限った話ではありませんが、欠陥をいろいろな観点で分析することは重要です。たとえば、機能・ストーリーでの分類、重要度での分類、品質特性(機能性、性能、ユーザビリティ、保守性、・・・)での分類、オープン/クローズの時間推移など。
 ただ、分類と数字だけで判断するべきではなく、傾向を見つけたらそのエリアのバグ票の中身にまで突っ込んでいくことが必要になるでしょう。
 アジャイル開発においてもシフトレフトアプローチは有効で、早期に見つけるほど改修コストは下がります。

DDP (Defect Detection Percentage、欠陥検出率)

 プロダクトの全欠陥のうち、開発の中で摘出できたものの割合です。DDPが高いほど、開発の中でしっかり欠陥を刈り取れているということになります。

 ウォーターフォールの文脈ですが、『システムテスト自動化 標準ガイド』では、「開発中」と「リリース後」の二分だけでなく、開発中の各テストフェーズでのDDPを算出する話が出てきます。たとえば、結合テスト(欠陥80件)→システムテスト(欠陥15件)→ユーザ受け入れテスト(欠陥3件)→リリース(欠陥2件) とすると、以下のように計算できます。

  • テストのDDP = (80+15+3) / (80+15+3+2) = 98.0 %
  • システムテスト完了時点での結合テストのDDP = 80 / (80+15) = 84.2 %

 たとえばテスト工程をスプリントで読みかえて・・・ということも考えてみますが、適切な読み替えではなさそうでもあり。アジャイル開発の文脈で、DDPをうまく使っている例があれば知りたいです。

 リリース後の欠陥という観点では、fabric.ioのようなサービスを利用しての、モバイルアプリクラッシュステータスの監視に言及しているサイトもありました。

平均欠陥検出時間 (MTTD)

 MTTRがはシステムの保守性を表していたのに対し、MTBF(Mean Time Between Failure)はシステムの信頼性を表現するメトリクスです。
 MTTDはそのアナロジーで、「テスターが次の欠陥にぶつかるまでの平均時間」になります。これはプロダクトの品質・信頼性に依存しますが、テストの質やテストエンジニアの能力にも依ります。

テストの成功率

 実行したテストの成功の割合です。時系列でみると、向上していくのが理想です。終盤で伸び悩んでいる場合は、欠陥をクローズできていないなどの問題があり、危険の兆候です。
 もちろん、テスト側に問題があることもあります。テストの期待値が誤っている、テストが壊れている、など。

ビルドの成功率

 「最後のビルド失敗からの日数」「ビルド連続失敗回数」などで表現するもので、ビルドプロセスが安定しているかの指標になります。
 システムテストに入る前のコードに問題がないか、チームのホワイトボックステストのやり方に問題ないかといった観点での改善につながります。

メトリクスの扱い方

 さて、アジャイルに限ったことではありませんが、メトリクスには以下のような注意点があります。

  1. 報告される側は、個人の評価に使わない。
  2. 報告する側は、取り繕うためにデータを歪めない。
  3. 収集に手間がかかりすぎるものは避ける。できるだけ自動で取得・集計・表示できるように。

 またこちらのページでは、短期と長期に分けて、ダッシュボードで見える化することを勧めています。
 メトリクスの例は、以下の通りです。

  • 短期 ビルドの成功率、自動テストの成功率、、バーンダウンチャート、追加された致命的バグ、未解決な致命的バグ、・・・
  • 長期 テストの失敗率、テストの数、ベロシティ、種類別バグ、・・・

www.slideshare.net

おわりに

 一通り紹介してみましたが、いかがでしょうか。
 もともとの動機が「品質に関わるメトリクス」だったので、偏りがあると思います。進捗を表すメトリクスが2つしかないし、コストに関しては1つも書いていません。実は、EVM(Earned Value Management)をアジャイルに適用する試みについての記事も読んだのですが、割愛しています。

www.infoq.com

 みなさんのチームで使われているナイスなメトリクスも教えていただきたいです。特に品質!

追記

 Twitterなどで寄せていただいた有益なサイトへのリンクです。

https://www.slideshare.net/oota_ken/agile-mericswww.slideshare.net

 辰巳さんからご紹介いただいた、SHIFTの太田さんのスライド。よっぽどちゃんとまとまっていました・・・。

www.slideshare.net

 LINEの伊藤宏幸さんの資料(楽天在籍当時)。Twitterで言及していたところ、ご本人に紹介いただきました。ありがとうございます。
 CFD、スループット、サイクルタイム、リードタイムなどに言及があります。

www.slideshare.net

 こちらも伊藤さんの資料。実際に現場で適用した内容なので、説得力が違いますね。
 また、メトリクスの悪化やボトルネックを発見したときも、対処は1つだけではないということを教えてくれます。

参考サイト

 本文中で直接言及していない記事・資料について、以下に列挙します。

reqtest.com www.getzephyr.com

www.slideshare.net www.slideshare.net www.slideshare.net www.atlassian.com www.testdevlab.com https://www.testingexcellence.com/how-do-we-measure-software-quality-in-agile-projects/www.testingexcellence.com Agile Metrics - What You Need to, Want to, and Can Measure(PDF) www.capriconsulting.co.uk http://www.everydaykanban.com/2016/09/25/flow-efficiency/www.everydaykanban.com

 まだ読み切れていないものも参考までに・・・。

implementingagile.blogspot.jp www.scaledagileframework.com www.swtestacademy.com techbeacon.com nesma.org

*1:メトリクスの複数形は「metrics」なのに、勘違いしてURLを「metrix」にしてしまい泣きたい。

*2:最初「metrix」と書いていたのを修正しました、しかしカスタムURLには残っている、恥しかない・・・。

JSTQB-TTAシラバスのお勉強 ─ 第2章「構造ベースドテスト」(1)

 TTAシラバスの第2章「構造ベースドテスト」は非常に謎めいた構成で、2.2から2.6までがコードカバレッジの話なのに、2.7が突然「APIテスト」なのです。なんだかとてもバランスが悪い印象なのですが、とにかく進むしかありません。
 あと「based」の訳が当初「ベース」と「ベースド」で不統一だったことから現在は「ベースド」に統一されていますが、さすがに「漢字熟語+ベースド」にはギャグ感があります・・・。

2 構造ベースドテスト

 構造ベースドテストは、シラバスに

テスト設計のベースとして、コード、データ、アーキテクチャ、およびシステムフローを使用する。

とあるように、プログラムの中身からテストを作っていく方法です。
 第2章で説明されるコードカバレッジについては、以前の記事で自分なりの整理をしました。詳しくはそちらを参照!ということで、個々で気になる点だけ補足していきたいと思います。

www.kzsuzuki.com

2.2 条件テスト

 対応するコードカバレッジは、条件網羅です。「判定」と「条件」がなんとなく似ているので混乱しがちなのですが、「条件」(condition)は不可分な命題です。∧や∨を含みません。「判定」(decision)は、条件やその組み合わせを評価した結果のブーリアンです。

 条件網羅はあまり実用的な意味のない概念だと思います。
 たとえば条件100個からなる、こんな判定を考えてみましょう。

(条件1 ∧ 条件2 ∧ ... ∧ 条件99) ∨ 条件100

 考えられる組み合わせは2100個ありますが、この場合に条件網羅を100%にするには、

条件1~99 = True、条件100 = False 条件1~99 = False、条件100 = Ture

の2つでOKです。一方、この2つのテストケースでは判定がともにFalseになるので、Trueの場合を実行できず、判定網羅は50%になってしまいます。

2.3 判定条件テスト

 対応するコードカバレッジは、判定網羅です。
 制限/注意事項としては「テストケースの増加」に言及していますが、条件網羅が保証されない点も重要でしょう。
 シラバスの表を少し書き換えて以下のようにしても判定網羅は満たされますが、条件網羅は満たされていない(条件AがFalseの場合がカバーされていない)ですね。

#ABA and B
1TrueTrueTrue
2TrueFalseFalse

2.4 改良条件判定カバレッジテスト

 対応するコードカバレッジは、改良条件判定カバレッジ(MC/DC)です。そのままですね。

定義が難解

 それにしても、シラバスの以下の文章は理解が難しいです。

N個の一意な不可分条件を想定した場合、MC/DCは通常、N+1個のテストケースで実現できる。MC/DCは判定条件カバレッジを達成するが、次の要件も満たす必要がある。
  1. 不可分条件Xが真になると判定結果が変わる1つ以上のテスト
  2. 不可分条件Xが偽になると判定結果が変わる1つ以上のテスト
  3. 各不可分条件に、上記の要件1と要件2を満たすテストが存在する。

 かみ砕いていうと、「その条件の真偽値が変わることで判定全体の真偽値が変わる」場合、そのテストケースはやっておけということ。裏を返すと、「その条件の真偽値が変わっても判定全体の真偽値が変わらない」のであれば、そのテストケースはやらなくていい(判定網羅を満たす範囲であれば)。

 TechMatrix社のページには以下のようにあります。こちらの1.は判定網羅、2.は条件網羅なので、シラバスの3つの項目が、3.の1文にまとめられていることになります。

DO-178Bに従うと、完全な(100%の)MC/DCカバレッジを得るには、次の3つの条件を満たす必要があります。
  1. 各「判断文」が、少なくとも1回すべての可能な結果を得ている。
  2. 1つの「判断文」中の各条件が、少なくとも1回すべての可能な結果を得ている。
  3. 1つの「判断文」中の個々の条件が、単独で全体の「判断文」の結果を左右する。

www.techmatrix.co.jp

具体例

 シラバスに示された、MC/DC=100%の例で確認してみましょう。

#ABC(A ∨ B) ∧ C
1TrueFalseTrueTrue
2FalseTrueTrueTrue
3FalseFalseTrueFalse
4TrueFalseFalseFalse
  • 条件A・B・Cが、TrueとFalseの両方の値をとるテストケースがある(条件網羅)
  • 判定結果 (A or B) and C が、TrueとFalseの両方の値をとるテストケースがある(判定網羅)
  • テスト1はAがTrueで判定がTrue。テスト3ではAだけをFalseに変えて、判定がFalseになっている。
  • テスト2はBがTrueで判定がTrue。テスト3ではBだけをFalseに変えて、判定がFalseになっている。
  • テスト1はCがTrueで判定がTrue。テスト4ではCだけをFalseに変えて、判定がFalseになっている。

 確かに、満たされているようです。

どうやって導出するのだ?

 さて、ではどのようにMC/DCを満たすテストケースを導出するのか。シラバスには書かれていません。「MC/DCは通常、N+1個のテストケースで実現できる」の根拠もわかりません。
 ネットを調べてみても(論文は調べていません!)、「これはMC/DC100%のテストケース群ですよ」といきなり「答え」が出ているものばかり。TechMatrix社のツールではMC/DCのカバレッジ分析ができるとのことなので、論理的に導出できるのではないかと思うのですが・・。。

 唯一希望を持てるのは、キャッツ株式会社のWebサイトで読める「ZIPC WATCHERS Vol.13」の記事「組込みソフトウェア開発課題への挑戦~網羅度~」(PDF)です。ここに以下の記述があります。

MC/DCテストケースを最小化する方法、MC/DCランダムテストに確率値誤差逆伝播法(バックプロパゲーション)を用いる方法については、別稿で述べる。→リンク先:松本

 でも「リンク先:松本」が見つからない・・・
 と言いますか、ん? バックプロパゲーション? ディープラーニングでちらっと勉強したあれ? 気になりすぎるのですが、とりあえず発見できませんでした。

導出方法を勝手に考えてみる

 ここからはあまり根拠のない話なので、冷やかし程度で読んでください。

 先の記述の、「ランダム」「最小化する」という言葉を見るに、MC/DCの場合は論理的に導ける唯一解があるわけではなく、何らかのランダム性を入れて、そこから最小な群を見つけていくアプローチなのでは、と想像しています。

 では自分なりにその方法を考えてみましょう。

 まずOR文。「または」なので、2つ以上の条件がTrueの場合、どれが独立にFalseに変わっても、OR文全体の結果は変わりません。
 次にAND文。「かつ」なので同様に、2つ以上の条件がFalseの場合、どれが独立にTrueに変わっても、AND文全体の結果は変わりません。
 よって、テストケース削減の方針として、以下2つを挙げることができるでしょう。

  • 方針1: OR内で複数条件がTrueになっているテストケースは省略する
  • 方針2: AND内で複数条件がFalseになっているテストケースは省略する

 では、シラバスの例である (A or B) and C について考えてみましょう。真理値表は以下の通りです。

#ABC(A ∨ B) (A ∨ B) ∧ C
1TrueTrueTrueTrueTrue
2TrueTrueFalseTrueFalse
3TrueFalseTrueTrueTrue
4TrueFalseFalseTrueFalse
5FalseTrueTrueTrueTrue
6FalseTrueFalseTrueFalse
7FalseFalseTrueFalseFalse
8FalseFalseFalseFalseFalse

 方針1から、AとBが両方Trueになっているテスト1と2を落とします。次に方針2から、(A ∨ B)とCが両方Falseになるテスト8を落とします。
 残るのはテスト3・4・5・6・7です。

 テスト3とテスト7は、条件Aを反転することで結果が反転しています。
 テスト4とテスト3は、条件Cを反転することで結果が反転しています。
 テスト5とテスト7は、条件Bを反転することで結果が反転しています。
 テスト5とテスト6は、条件Cを反転することで結果が反転しています。  

 条件Aの反転と条件Bの反転のために、テスト3・5・7は必須。あとは条件Cのために3・4を選ぶか、5・6を選ぶか。
 シラバスでは前者の3・4・5・7のパターンになっていますが、3・5・6・7でもMC/DCを満たしているといえるのではないでしょうか。なお、判定条件網羅はともに満たしています。

■シラバスの解

#ABC(A ∨ B) ∧ C
3TrueFalseTrueTrue
4TrueFalseFalseFalse
5FalseTrueTrueTrue
7FalseFalseTrueFalse

■別の解

#ABC(A ∨ B) ∧ C
3TrueFalseTrueTrue
5FalseTrueTrueTrue
6FalseTrueFalseFalse
7FalseFalseTrueFalse

 自力の導出方法として、この程度が限界でした。何かスマートなやり方あるんだろなあ。
 長くなってきたので、第2章の残りは別途。

#JaSST '18 TokyoのMicco氏のチュートリアルを復習し、少しデータで遊んでみる

 GoogleのテストマネージャーであるJohn Micco氏の、Flakyなテストについてのチュートリアルの内容は、こちらに書き尽くされています。なので、あらためてその復習記事を書くことはないのだけれど、SQLの勉強も兼ねて、要点を絞って振り返っておきます。

nihonbuson.hatenadiary.jp togetter.com 

 Micco氏のお話の要点は、以下の2つだとわたしは考えています。

  1. 自動テストが膨大になると、効率よい順番で実行したり、適切に間引いたりすることが重要である。
  2. シンプルな記録であっても、積み重ねればさまざまな洞察を導くことができる。

 この記事では、上述の2つを踏まえてチュートリアルを振り返るとともに、せっかく権限をもらったGoogleさんのログで、少し遊んでみたいと思います。

膨大な自動テストの効率を上げるには

 Micco氏の資料*1によると、Googleで継続的に実行されている420万件あり、1日あたりテスト実行が150万回とのこと。
 一方、バグが見つかるのはわずか1.23%のテストだけ。膨大なリソースを使うことから、テストを効率的に実行することが求められます。

 テスト結果を分析する中で、Micco氏はテストの結果の遷移(transision)に注目。過去にpassしていたテストがある時点でfailに変わるネガティブな遷移は、テストがflaky(あてにならないもの)であることが原因の1つであることがわかりました。flakyなテストについて、スライドでは以下のように述べられています。

  • flakyなテストとは、同じコードであるにも関わらず、passになったりfailになったりするテストのこと。
  • 420万件のテストのうち、16%が何らかのflakinessをもっている。
  • flakyなテストの結果の確認に、開発者の時間が浪費される。そのため開発者は無視しがちだが、その判断が誤っていることもある。
  • flakyなテストの(再)実行のために、2~16%の計算リソースを使っている。
  • flakyなテストは、進捗を阻害したりリリースを遅らせたりする。

 この無駄を減らすことが、自動テスト実行の効率改善の大きな助けになるというのがモチベーションです。 

Flakyなテストを見つけるには

 チュートリアルで教えてもらったのは、どのようなデータをどう使って分析しているか、ということです。

どんなデータを使う?

 実はこの目的に必要なデータベースは、次のシンプルな2つのテーブルです。targetsはテストケース、resultsはテストケースの実行結果と、ざっくり捉えてよさそう。

■targets

idINTEGERREQUIRED テストのID
targetSTRINGREQUIRED テストケース(のディレクトリパス。これでテストが一意に決まっているようだ)
flagsSTRINGREQUIRED テスト環境などの情報をフラグ化したものの組み合わせ

■results

target_idINTEGERREQUIRED テストのID
changelistINTEGERREQUIRED プログラム変更リストのID
resultSTRINGREQUIRED 実行結果
timestampTIMESTAMPREQUIRED タイムスタンプ

 resultsテーブルの「result」に入るのはテストの結果で、以下のようなものがあります。一部内容を聞いていないものもあります。

  • AFFECTED_TARGET
  • PASSED: 成功
  • SKIPPED
  • FAILED_TO_BUILD: コンパイルできずチェックイン不可
  • FAILED: 失敗
  • INTERNAL_ERROR: テストを開始する前提が満たされなかった。テストインフラの問題
  • PENDING: 実行開始できなかった
  • FLAKY: 一度のテストの中で、初めは失敗したが、リトライで最終的には成功したもの
  • ABORTED_BY_TOOL: 実行時間の閾値15分オーバーによる実行停止
  • FORGE_FAILURE
  • BUILD_TIMEOUT
  • PROHIBITED
  • BLACKLISTED

 このように結果を細かく分けることで、テストの問題なのか、インフラの問題なのかを切り分けやすくし、開発者とテストインフラチームがお互い時間を浪費しないように工夫しています。

 わたしのチームでもテスト失敗の要因は区別していて、自動テストインフラのどこに改善ポイントがあるのかがわかるようには、一応しています。
 プロダクトのバグ起因、自動テストケース起因、テスト環境起因、人の凡ミス、前のテストの影響が残ってしまった、などなど。
 Googleとの大きな違いは、この分類を、ほぼ人がやっているということで・・・スケールしないですね。

結局flakyって?

 チュートリアルの中では、flakyを2つの意味で使っているように感じました。
 1つ目はテスト結果の種類としてのもので、「1回のテスト実行の中で、最初は失敗したが、リトライで成功している」ことを指しています。2つ目はテスト結果の変化の種類としてのもので、「ある時に成功したが、次の実行の際には失敗した」ことを指しています。
 でもこの2つって本質的には同じで、結果の変化が1回のテストの中で起こったか、2回のテストの中で起こったかの違いだけなんですね。

 よって現時点では単純に、「同じコードで同じ結果を期待しているのに、結果が変わってしまう」ものだと捉えています。

遷移するテストを見つける

 チュートリアル最初は、resultsのレコード件数は?みたいなシンプル過ぎる練習クエリを投げていたのですが、遷移するテストを特定するクエリから、以下のようにややこしくなってきます。(MiccoさんのGitHubから引用しています)。

 以下読み解いてみますが、解釈が間違っていたり言葉遣いがおかしければご指摘ください。
 まず、WITH句で、サブクエリ「result_values」を生成しています。resultsテーブルを並び替えたもので、こんな感じ。*2

Rowrowtarget_idchangelistresult
2602601100515424AFFECTED_TARGET
2612611100518994AFFECTED_TARGET
2622621100519320AFFECTED_TARGET
2632631100520152AFFECTED_TARGET
2642641100522728PASSED
26512100005012AFFECTED_TARGET
26622100020333AFFECTED_TARGET

 PARTITION BYによって、target_idごとにレコードを区分し、changelistでソートしたうえでrow列でナンバリングしています。
 「区分」されているため、target_idが変わるとナンバリングが1から再スタートするというところがポイントです。264行目まで増加していたrowが、265で1に戻るのはそういうわけです。

 WITHが終わった後のSELECTでは、上で作ったテーブル result_valuesを、自分自身と結合します。結合の条件は、①target_idが一致している ②rowが1個ずれている。
 target_idとrowを指定すればレコードは一意に決まりますね。上のテーブルだと、たとえばRow260をRow261と、Row261をRow262と、1行ずらしで順に結合していくことになります。イメージ的には、以下。

changelistresultchangelistresult
100515424AFFECTED_TARGET100518994AFFECTED_TARGET
100518994AFFECTED_TARGET100519320AFFECTED_TARGET
100519320AFFECTED_TARGET100520152AFFECTED_TARGET
100520152AFFECTED_TARGET100522728PASSED

 WHERE句で、resultが一致していないものを抽出すると、1番下の行だけが残ります。これって、「あるテストと次のテストで結果が違う」、つまり遷移を表していますね。さらにこれをtarget_idでグルーピングして、遷移が4回以上起きているものを残す。とこういうことをしていたんですね。
 まあややこしかったけれど、遷移がよく起きるテストケースを特定することができました。

Rowtarget_idtransition_count
184818481
229299458
370376429
471932425
592007416

遷移の歴史を見抜く

 さて次の問題は、「flakyに見えるけれど実はflakyじゃなかった」遷移をどのように見抜くか、ですね。

 1つの仮説として、それぞれのテストのflakyさ≒遷移のパターンは、独立しているはずです*3 。逆に、複数のテストが同じような遷移の履歴を示すのであれば、それはテスト以外のものに由来する問題である可能性が高い。Micco氏はそのよ1うな現象の原因として、サブシステムやライブラリの問題を挙げていました。
 テスト自体がflakyなわけではないことが明確になれば、テストインフラ側の問題として引き取ることで、開発者の調査の工数を減らせそうです。

 まず事前準備として、先ほどのクエリを少し変えて、以下のようなテーブル「target_transitions」を作っておきます。遷移が起こった際のchangelistとtarget_idの組み合わせ表です。

Rowchangelisttarget_id
110000000117341
210000000128661
3100000001183752
4100000001228506
5100000001259831

 このテーブルに対し、以下のクエリを投げる。

 ざっくりいうと、target_idでグルーピングしたうえで、そこに紐づくchangelistをarray_agg関数で配列にしている。結果がこれです。

Rowtarget_idchangelists
1424100437692
100437728
100446646
100452444
1434100031939
100036158

 上の表でいうと、(424, [100437692, 100437728, 100446646, 100452444]) みたいになっているってことですね。つまり、「テストケース、遷移歴」のテーブルになっているってことですよ。
 この遷移歴が一致するテストケースが多ければ、その遷移・flakyさは、テスト自体と違うところに起因する(可能性が高い)といえるわけですね。
 この結果から、遷移する22,760のうち、1,610が遷移歴を共有していることがわかりました。これらはFlakyではないと判断できそうです。

Google先生のデータで遊ぶ

 さて、シンプルなデータですが、いろいろできそうな気もしますよね?
 少しだけ遊んでみました。

遷移回数を正規化する

 上で出た「flaky_tests」テーブルですが、内容は「target_id」(テストケース)と、「transision_count」(遷移回数)です。でもこれだと、実行回数が多いテストと少ないテストを公平に比較できないのでは?と考えるのが自然ですよね。
 実行回数はすぐ得られるので、遷移回数/実行回数 で正規化してみましょう。
 クエリはこんな感じか・・・?

Rowtarget_idexecution_counttransition_countflake_ratio
14956292068.97%
27764441636.36%
38881892224.72%
4545421419.05%
56792891617.98%
6434132518614.04%
76456132517212.98%
86031125414811.80%
98769102411210.94%
102468154414809.59%
11407389808.99%
12593745408.89%
1369021691408.28%
147811307118005.86%
1549018484004.72%
161529262912204.64%
17709189404.49%
187367280610103.60%

 さっき1位だった434は6位に、2位だった7811は14位に、3位だった6456は7位に。ランキングはそれほど変わらないかな?

Googlerの人たちのホワイトさを調べる

 テストと結果のテーブル以外に、「TensorFlowCommits」というコミット履歴のデータがありました。どんな言語でよくfailするのかを、修正対象となったファイルの拡張子から判断したりしています。
 チュートリアルで、「誰がよくfailを引き起こしているのか」という分析もあったのでですが、「いや、むしろ何時ごろの修正がfailを引き起こすのか、の方が面白いのでは!?」と。ただ残念ながら、コミット履歴のテーブルの一部が匿名化されており、またテスト結果のテーブルと結合できるキーもないため・・・、一気にスケールダウンし、「googlerは何時にコミットしているのか」を見てみましょう。クエリはたぶんこんな感じ・・・。

 この結果をグラフにすると、こう。いきなりExcelですけれど。

workingHour

 7時ごろに急に立ち上がりはじめ、昼前にピークを迎え、昼休憩をとった後16時ごろにもう一度ピークを見せて、17時ごろから急減。
 ただ、意外に、夜中働いていらっしゃる・・・。人間以外のコミットがあったりする、のでしょうか?*4

まとめ

 基調講演やチュートリアルで学んだことは、膨大なテストの効率的な実行が必要だけれど、ヒントはシンプルなデータの中からも得られる、ということでした。
 何億件というデータはGoogleならではですが、使っている情報も、ロジックも、とてもシンプルと言えるのではないでしょうか。
 自動テストはログの蓄積と相性がいいので、自分なりのメトリクスを考えるのは楽しそうですし、効果も見込めそうなので、仕事の中で何か見つけてみたいと思っています。
 Miccoさん、ありがとうございました! 謝謝!

*1:The State of Continuous Integration Testing @Google (pdf)

*2:実際は「AFFECTED_TARGET」は除外されている。

*3:実際のところ、20回同じタイミングで遷移するような組み合わせは、わずか2つしかなかったそう。

*4:@nihonbuson さんに、「多拠点開発だからなのでは?」とのご指摘いただきました。なるほどー。

JSTQB-TTAシラバスのお勉強 ─ 第1章「リスクベースドテスト」

JSTQB-TTAシラバスのお勉強 ─ 第1章「リスクベースドテスト」 ISTQBのAdvancedレベル「テクニカルテストアナリスト」シラバス日本語訳が出ました。いつ試験が行われるかは微妙なところですが、シラバスの紹介をしてみます。

テクニカルテストアナリストとは何なのか

 実は、ISTQBで定義されているロールの体系を見ても、定義を見つけることはできませんでした。この絵でいう「CORE」で、Advanced Level (AL)はFoundation (FL)の上位に位置付けられており、以下の3つから成っています。

  • Test Manager (テストマネジャー、TM)
  • Test Analyst (テストアナリスト、TA)
  • Technical Test Analyst (テクニカルテストアナリスト、TTA)

 テストマネジャーは一番わかりやすいでしょう。テストの計画・進行・管理・分析を行うロールですね。
 テストアナリストにもマネジメントの知識は要求されますが、どちらかといえばテストマネジャーに対して情報をインプットするという側面が強い。いわゆるテスト技法を駆使したテスト設計であったり、テストツールの導入であったりが仕事の中心と見なされています。

 テクニカルテストアナリストは、(ぱっとシラバスを読んだ限りでは)よりホワイトボックス的な色合いの強いロールのようです。シラバスでは、コードカバレッジや、静的/動的解析が章立てされています。また品質特性については、機能性やユーザビリティなど「ユーザが直接意識する」部分をTAが見るのに対し、TTAはどちらかといえばユーザがあまり意識しない保守性やセキュリティなどに責任を持つことになっています。第4章の「品質特性」でも明らかになりますが、TTAは開発者側が意識すべき内容が多くなっています。

2018/3/21追記

 確かに、TTAについては、こちらの「概要」にきちんと説明がありました! 足元がおるすになってますよ。
 TTAの「ビジネス成果」から引用してみましょう。

Advanced Levelテクニカルテストアナリストは次のビジネス成果を達成できる。
TTA1: ソフトウェアシステムの性能、セキュリティ、信頼性、移植性、保守性に関連付けされる代表的なリスクを認識し、分類する。
TTA2: 性能、セキュリティ、信頼性、移植性、保守性のそれぞれのリスクを軽減するためのテストの計画、設計および実行を具体化したテスト計画を策定する。
TTA3: 適切な構造テスト設計技法を選択し適用する。コードカバレッジおよび設計カバレッジに基づいて、テストが適切なコンフィデンスレベル(確信度合い)を提供することを確保する。
TTA4: コードおよびアーキテクチャ内の代表的な間違いに関する知識を、開発者およびソフトウェアアーキテクトとのテクニカルレビューに参加することで効果的に適用する。
TTA5: コードおよびソフトウェアアーキテクチャ内のリスクを認識しテスト計画要素を作成して、動的解析を通してこれらのリスクを軽減する。
TTA6: 静的解析を適用することで、コードのセキュリティ、保守性および試験性への改善を提案する。
TTA7: 特定の種類のテスト自動化を導入することから想定されるコストおよびメリットを概説する。
TTA8: テクニカルなテストタスクを自動化するために適切なツールを選択する。
TTA9: テスト自動化の適用における技術的な概念や課題を理解する。

 9つ中4つに「コード」という言葉が出てくること、またTAに比べて自動化がより詳細になっていることがポイントかと思います。
 上に書いた通り、より実装に近いところにフォーカスされている印象です。

1. リスクベースドテストにおける、テクニカルテストアナリストのタスク

 それでは、今回は第1章を見ていきましょう。まず、リスクベースドテストとは何だったでしょうか。
 用語集での定義は以下の通りです。

リスクベースドテスト(risk-based testing)
プロジェクトの初期段階からプロダクトリスクのレベルを低減させ、ステークホルダにその状態を通知するテストの方法。プロダクトリスクの識別の他、テストプロセスをガイドする際のリスクレベルの活用もこれに含まれる。

TMでも出てくる言葉なので、軽くこちらのエントリも覗いていただければ。

www.kzsuzuki.com

 識別・評価(アセスメント)・軽減というタスクについてはTMと同じですが、TTAは「技術的なリスクについて」の知識の提供が期待されています。
 ALの3つのロールでいうと、ざっくり分けてTMがプロジェクトリスク、TAとTTAがプロダクトリスクの深堀りをすると考えるとわかりやすいでしょう。

 例として挙げられているリスク例は以下です。

  • 性能リスク(例:高負荷状態では応答時間を達成できない)
  • セキュリティリスク(例:セキュリティ攻撃による機密データの漏えい)
  • 信頼性リスク(例:アプリケーションがサービスレベルアグリーメントで規定された可用性を満たせない)

 リスクの識別や評価については、特に目新しいことは書かれていません。専門家へのインタビューや有識者レビューを通じてリスクを洗い出そうとか、発生確率と影響度を評価しようといった一般論です。

Rex Blackさんの説明

 さて、これはプロジェクト管理でなくテストの話なので、テストを通じてこれらのリスクを低減する必要があります。そのリスクに応じてテストの優先度を決めるのが、リスクベースドテストです。

 リスクベースドテストの提唱者であるRex Black氏の資料、Risk-Based Testing - What It Is and How You Can Benefit(pdf)を見てみましょう。
 この資料では、リスクベースドテストの長所が以下のように述べられています。

  • リスクの高い順番にテストを行うことで、深刻な欠陥から順に見つけていける可能性を最大にできる。
  • テストの工数をリスクに基づいて割り当てることで、リリースまでに残った品質リスクをもっとも効率的に最小化できる。
  • テストの結果をリスクに基づいて測ることで、テスト実行期間中にどれだけのレベルの品質リスクが残っているかを組織が把握し、適切なリリース判定を行うことができる。
  • スケジュール上必要であれば、リスクの低い順からテストを割愛することで、品質リスクの増加を最低限に抑えながら、テスト実行期間を短くすることができる。

 このスライドを見ると、品質リスク項目とその品質特性を並べて、それぞれについて発生確率と影響度を記入しています。リスクベースドテストを紹介している他のサイトでは、縦軸に機能モジュールを並べているものもあります。機能×品質特性で、それぞれどんな品質リスクがあるかのマップを作るとわかりやすいでしょう。

たとえば

 例として、「外部I/Fの変更と機能追加の影響で、性能が劣化するリスクがある」ケースを考えてみましょう。
 まず発生確率について、プロジェクト開始当時は「非常に高い」だったとしても、初期段階でのプロトタイプ評価により思ったほど性能影響がないことがわかれば、「中程度」に変更されます。
 影響について、プロジェクト内部で定めた性能目標値を達成していなかったとしても、ユーザはまったく気にしないかもしれません。あるいはシステム全体の前提を満たせなくなり、リリース自体が不可能になるかもしれません。
 「性能が劣化する」と一言で言っても、その度合いによって確率も影響も異なるので、ものによっては別のリスクとして扱うのがよいでしょう。

 リスクの高いものから順に、対応するテストケースを消化していくことになりますが、そのテストケースも有限なので、リスクがゼロになることはありません。許容される工数と時間内で、受け入れられる程度にまでリスクを低減できたかを判断することになります。

機械学習の分野でも注目される、メタモルフィックテスティングとは何か。

 先日のJEITAのイベントで、メタモルフィックテスティング(Metamorphic Testing、MT)というものを知り、「誰かMetamorphic Testingの勉強会やりませんか?」とブログに書いたところ、ソフトウェアテスト・ヒストリアンの辰巳さんが資料をたくさん紹介してくださいました。ちなみに、一緒に勉強してくれる人は誰もいませんでした。

www.kzsuzuki.com

 ICSE MET'2017のサイトにも、MTについてのスライドがたくさん掲載されていたので、学んだところを紹介してみたいと思います。
 日本語の論文も無償で見られるものがいくつかあるのですが、どちらかといえばMTについての知識は前提として、それを機械学習にも応用してみたという内容なので、基本を学ぶには敷居が高そうです(論文だから当たり前か・・・)。

メタモルフィックテスティングの概要

 まず、Chris Murphy氏の『Applications of Metamorphic Testing』(ppt)が一番わかりやすいです。スライド終盤のサマリには、以下のようにあります。

  • MTは、既存のテストケースから新しいテストケースを生成する手法である。
  • テストオラクルのないアプリケーションにおいて、バグ検出の役に立つことがある。
  • ソフトウェアのメタモルフィックプロパティ(metamorphic property)に強く依存する。
  •  一つずつ、見ていきましょう。

    なぜテストケースを増やす必要があるのか。

     「新しいテストケースを生成する」理由は何でしょう。
     境界値分析や組み合わせテストといったテスト技法は、テスト空間を論理的に絞り込み、効率的にテストケースを減らすための技術でした。なぜあえて、テストケースを増やすのか。
     このスライドでは「だって、テストケースは多ければ多いほどいいだろう?」とあります。手動テスト中心のテスターが聞くと吐血しそうな意見ですね。

     たとえばこちらのICSTの論文『Metamorphic Model-based Testing of Autonomous Systems』(pdf)では、ドローンの飛行や障害物回避のシミュレーションプログラムにMTを応用した例が載っています。ドローンの出発地点と到着地点、その経路にある障害物には無数のパターンがあるので、ある特徴的なテストケースをいくつか通すだけでなく、そこから大量のテストケースを派生させて、プログラムの妥当性を検証することが有効なのですね。

    テストオラクルってなんだっけ?

     テストオラクル(Test Oracle)などという用語を使うのは、I/JSTQBで学んだ界隈の人だけだと思っていましたが、MTの文脈では当然のように使われている言葉です。
     ISTQB用語集に追加された日本語検索機能をさっそく使ってみましょう! 使い方は、湯本さんのnoteから!

    note.mu

    テストオラクル(test oracle)
    テスト対象のソフトウェアの実行結果と比較する期待結果のソース。オラクルは、実在する(ベンチマーク用の)システム、他のソフトウェア、ユーザマニュアル、個人の専門知識の場合があるが、コードであってはならない。

     テストオラクルがないというのはつまり、「どういう出力なら正しいのか」がよくわからない状況ということですね。

    メタモルフィックプロパティとは?

     「メタモルフィックプロパティ」(Metamorphic Properties)とは一言でいうと、対象とするソフトウェアが備えている「性質」のことです*1。MTでは、この性質から多くのテストケースを導出することを目的としています。

     それってプログラムの仕様そのもの、あるいはそれを変換した高位レベルテストケースのことでは、とも思いませんか? 一般的なテスト技法でテストケースの絞り込みを行わなければ、テストケースはいくらでも追加できそうなものです。
     たとえば、「12歳以上は料金1,000円」という仕様からは、入力である年齢を12歳、13歳、14歳、・・・とし、出力が「1,000円」となることを確認するテストケースがどんどん作れます。

     ですが、メタモルフィックプロパティはもっと別のものです。
     わたしは、「プログラムの仕様として定義するまでもない、自明な性質」と解釈しています。

    メタモルフィックプロパティの具体例

    数値リストから最小値を求めるプログラム

     抽象的な話になってきたので、先の資料から具体例を引用します。
     整数のリストの中から最小値を得る関数 findMinを、以下のように書くとします(バグがあります)。

     「2、1、4、3」というリストを入力にすると、期待値(つまり最小値)は「1」になる、というテストケースは、以下のように表現することができます。

    { {2, 1, 4, 3}, 1}

     さて、このプログラムが持つべきメタモルフィックプロパティには、どういうものが考えられるでしょうか。
     それはたとえば、「リスト内の数字を並び替えても、出力値(最小値)は同じになる」という性質です。このような性質は、おそらく外部仕様として明示されない、「自明な」性質ということができるでしょう。

     この性質に従ってテストケースを生成すると、

    { {4, 2, 3, 1}, 1}

    というテストケースが通らないことがわかり、バグを検出することができます。

    形式的な表現をしてみると

     少し形式的に表現してみましょう。
     プログラムfに対する元のテストケースを {x, f(x)} と表現します。

     元のテストケースの入力値を関数tに通すと、新しい入力値はt(x)、プログラムfからの出力値はf(t(x))となります。
     一方、元のテストケースの出力値を関数gに通すと、新しい出力値はg(f(x)) となります。
     プログラムfのメタモルフィックプロパティとは、すべての入力値xに対して、f(t(x))=g(f(x))を満たすような関数ペア(t, g)のこと。文字列だと全然わからないですね・・・。

     先のfindMinの例では、関数tは「リスト要素の並び替え」に相当します。出力値の方は変わらないことを期待しているので、関数gは恒等関数になるでしょう。入力に「リスト要素の並び替え」という操作を行っても、出力値は変わらないというのが、findMinのメタモルフィックプロパティです。

    一般的にはどんなプロパティがある?

     一般的に、どんなメタモルフィックプロパティがあるのでしょうか。P.22からその例が書かれています。
     元のテストケースが「要素a~fの総計がsになる」というものだったとして、

  • Permute: 並び替えを行っても総計はsと変わらない
  • Add: 各要素に定数を足すと、総計は s+定数*6 になる
  • Multiply: 各要素に定数を乗じると、総計は s*定数 になる
  • といったものがメタモルフィックプロパティとなります。これはだいぶ単純というか・・・数学以外には適用できそうにない、退屈な性質ですよね・・・。
     もう少し違うタイプとして、以下のようなものが紹介されています。

  • Noise-based: 出力に提供を与えないであろう入力(の変化)
  • Semantically Equivalent: 意味的に「同じ」入力。
  • Heuristic: 元に「近い」入力。
  • Statistical: 統計的に同じ性質を示す入力。
  • 数値計算分野以外での応用

     メタモルフィックプロパティが上で紹介したようなものばかりだと、MTは数値計算関連のプログラムにしか応用できないのではと思いますが、Zhi Quan Zhouさんの講演『Metamorphic Testing: Beyond Testing Numerical Computations』(pdf)によると、数値計算関連分野での適用は全体の4%でしかないそうです*2

     この講演で紹介された適用例は、プログラム難読化ツール*3
     難読化ツールのメタモルフィックプロパティには、たとえば以下のようなものがあります。

  • 同じソースコードを入力するといつでも、動作的に等価なプログラムが出力される。
  • 一つのソースコードに対し繰り返し難読化を何度かけても、動作的に等価なプログラムが出力される。
  •  こういったプロパティを利用して、一般的なテストでは必ずしも検出できないバグが、MTであれば検出できるという例を示しています。

     また、オンライン検索での例も紹介されています。一口にオンライン検索といっても、GoogleのようなWebサイトの検索、ホテルや飛行機の空き状況、商品、用語、地図など無数にありますが、これらに共通して言えそうなメタモルフィックプロパティがあります。

     まず一般的な(general)メタモルフィックプロパティとして、以下が考えられます。

    「AがBに包含されている」という関係が成立しているなら、
  • Aの結果はBの結果の部分集合になる
  • Aの結果の数は、Bの結果の数以下になる
  •  この2つ目のプロパティから導出できる具体的な(concrete)プロパティは、たとえば店舗検索において、

    検索する地域を絞ることで、ヒットする結果は絞る前より少なくなる

    というものが考えられます。このメタモルフィックプロパティを利用してテストケースを生成し、妥当性を確認することができます。

    まとめ

     引用した資料に明確に書かれているわけではないのですが、生成されるテストケースが常に「期待値付き」であることが非常に重要だと思います。いくら大量にテストケースを生成したとしても、それが期待値なしであればあまり役に立ちませんし、自動化もできません。

     テスト技法といえば、テストケースを合理的に選択することと思いがちでしたが、「とにかくたくさんのテストパターンが欲しい」「でもテストオラクルがない・・・」という状況において、有効だが数が少ないテストケースから、期待値付きのテストケースを大量に派生させる技術として、メタモルフィックテスティングが有望な技術だということがわかりました。

    *1:他の資料では、「メタモルフィック関係」(Metamorphic Relations、MR)という用語も出てきますが、どうやら同じものを指しているようです。

    *2:Chris Murphyさんの資料では、実際の応用分野として、生物情報学、機械学習、ネットワークシミュレーション、コンピュータグラフィックスなどがあるとのこと

    *3:クラッカーなどに重要なシステムの内部情報を与えることを防ぐためのプログラム。obfuscator。