AAAにてテスト自動化の品質特性について講演

先日、AAAというテスト自動化のイベントで、25分の短時間枠ですが、テスト自動化に関わる品質モデルや品質特性についてのセッションの機会を頂きました。

http://www.slideshare.net/goyoki/ss-36405244

イベントも大変エモい感じで、楽しい一日でした。
運営の方々、参加者の方々、大変ありがとうございました。

探索的テスト入門

だいぶ前に探索的テストの入門解説を行う機会があったのですが、最近その資料を公開しました。

探索的テスト入門改訂版/Introduction to exploratory testing - Speaker Deck

なお今年読んだExplore It!の内容を一部加筆しています。
一般的な割に意外と探索的テストの日本語資料は少ないようですが、何かしらの一助になれば幸いです。

Classification Tree法(クラシフィケーションツリー法)について

※Classification Tree法のまとまった解説として以下資料を作成しました:

クラシフィケーションツリー法入門/Introduction to Classification Tree Method - Speaker Deck

最新は上記参照ください。以降はバックアップです。

●●●●

 ソフトウェアテストの分野では、日本語圏と英語圏で話題や志向が違うことが結構ある。
 その違いの代表例の一つに、テスト技法であるClassification Tree法がある。この技法は、海外ではISO/IEC 29119が代表的なテスト技法として挙げているなどそれなりの知名度を持っているそうだが、日本国内では知名度がかなり低い。
 今回はそのClassification Tree法について、簡単に紹介したいと思う。

Classification Tree法の概要

 Classification Tree法(Classification Tree Method。クラシフィケーションツリー法。分類木法。略称はCTM)は、テスト観点や同値クラスモデリング手法に属する。
 決定木と同じく、元々Classification Treeは用途をテストに限らない汎用的なモデリング手法として使われてきた。テスト設計ではそれにいくつかのサブセットルールと意味論的な定義を加えて用いることになる。
 用途だけれど、テスト設計でのClassification Tree法は、分類木を使って組み合わせや同値分割を階層構造でモデル化するのに用いられる。それによって、同値分割のズームイン・ズームアウトを容易にする、組み合わせの分布を抽象・具象両面で評価可能にする、テスト観点の分析をやりやすくする、といったメリットを確保する。またツールを使って、テスト網羅度のビジュアライズやテストの自動生成をサポートさせる場合もある。
 なおこの手法と類似している手法として、テスト観点のモデリングを扱うNGTや、テスト設計手法であるFOTで似た図を用いている。違いとしては、前者はテストアーキテクチャ設計を行うためのテスト観点のパッケージングなどが補強されている。後者は原因結果グラフと同じように、組み合わせの制約を図中に記述できるように拡張されている。

Classification Tree法の運用

 Classification Tree法ではモデリングやテスト設計支援でツールを用いるのが一般的になっている。
 ツールによっては、Classification Treeのモデルを作成すると、それに基づいて組み合わせテストを自動生成してくれるものがある。そのためClassification Tree法はモデルベースドテストの実現手段として見られることもあるようだ。
 Classification Tree法では、モデル上の各要素をテストケースにおいてどう組み合わせるかは自由度をもたせている。そこではそれぞれのテストの目的や制約に応じて、テスト設計者やツールが全網羅や2因子間網羅などを選択することになる。

Classification Treeの書き方

 Classification Treeは以下の構成要素でモデルを構成する

  • Aspect : テスト観点。Classficationとも呼ばれる。文献によってはClassの集合と表現されることもある。
  • Domain : テスト入力の集合や同値クラス。Classとも呼ばれる。

 また、最上位のAspectを「Aspect of interest」と呼称し扱いを区別することもある。


 モデルでは上記をツリー上でつなげて記述する。具体的には、以下のようなパターンでツリーを作成する。

  • 幹側の「Aspect」に、それを構成する「Domain」の枝をつなげる
  • 幹側の「Aspect」に、より具体的な「Aspect」の枝をつなげる
  • 幹側の「Domain」に、それを分解するための「Aspect」の枝をつなげる。

 なおモデルの表記法は文献によってばらつきがある。ツールによるアシストが一般的な手法のため、ツールの違いが表記のばらつきにつながっているようだ。表記法の例をいくつか以下に紹介する。

●「Guide to Advanced Software Testing」の場合

 書籍 「Guide to Advanced Software Testing」 では以下のような書式スタイルをとる。

  • Aspect : 四角の枠線で囲って描く
  • Domain : 丸の枠線で囲って描く

例を以下に示す。

●「Test Case Design Using Classification Trees」の場合

 割と著名な資料 「Test Case Design Using Classification Trees」 では以下のようなスタイルをとる。

  • Aspect : 四角の枠線で囲って描く
  • Domain : 枠線なしで描く

例を以下に示す。


ツールによるサポート

 Classification Tree法はツールによって拡張されることが多い。例えばCTEというツールでは、原因結果グラフと同じような制約条件を各構成要素に設定し、テストケースを自動生成できるようにする機能をサポートしている。

キーワード駆動テストの導入

 最近キーワード駆動テストがややバズワード化している傾向を感じている。というのも、キーワード駆動テストの導入で無用な手間を増やしている場面を見るようになっているためだ。
 キーワード駆動テストはフレームワークによっては手間を増やすことがあるので、その導入にあたっては、導入内容が目的に見合っているか多少の注意を向ける必要があると感じる。基本的な事柄であえて言及する必要もない内容かも知れないが、今回はそれについて簡単に触れたい。

キーワード駆動テストの目的

 言及するまでもないかもしれないけれども、何かしらの改善を行う際は、その手段が目的に見合っているか留意する必要がある。ではキーワード駆動テストの目的は何かというと、大雑把にまとめて以下の3つがある。なおこれは排他ではなく、一緒に目指しても良い。

目的(1)テストの保守性改善

 まず目的の一つに、テスト設計やテスト実装物の保守性改善のための構造化手段として、キーワード駆動テストを導入する場合がある。例えば以下のような目的だ。

  • 重複テストコードを共通ロジックに、非重複テストコードをキーワードに展開してテストスクリプトのコピペを削減し、保守のミスを防ぐ。
  • 可変性分析で抽出した流動的要素をキーワードに展開することで、テストケースやテストコードの変更性を高める。

目的(2)非開発者をテスト設計に巻き込む

 次に、非開発者でもテスト設計・実装が行えるようにするのもキーワード駆動テストの主な目的とされる。例えばテスト実装スキルのないユーザやテストエンジニアが、キーワードを組み合わせてテスト設計を行ったり、キーワードの組み合わせでテストをレビューしたりするのを実現する手段として、キーワード駆動テストのフレームワークを導入する。そこでは具体的にはExcelWikiによるテストケースの記述や、非開発者が読めるDSLでのテストケースの記述などが実現される。
 キーワード駆動テスト向けのフレームワークは、基本こちらの目的に対応したものが多い。

目的(3)テストの抽象化

 その他としては、テストの保守性と被る部分もあるが、テストケースの抽象化手段として用いられることがある。例えば、抽象的な手順をアクションワードで表現し具体的な手順はフレームワークが担保することで、以下を実現する。

  • テスト対象が変更されてもテストケースに影響がないようにする(フレームワークが変更部分を吸収する)。
  • 一つのアクションワードを複数の環境や条件間のテスト手順に横断的に展開できるようにする。

 この目的も、キーワード駆動テストの目的として一般的だ。

キーワード駆動テストの導入の注意点:目的を取り違えないこと

目的(2)のツールを導入する場合の注意点

 まず、見ていて注意が必要だと感じているのが、上記の目的(1)目的(3)の達成のために、目的(2)の用途のフレームワークや技術を導入してしまうパターンだ。
 目的(2)のために作られたフレームワークは、非技術者でもテスト設計を担当できるようにする。その代償として、保守性の作りこみに制限をかけたり、キーワードをテストスクリプトに落としこむ手間でテスト実装の効率性を落としたりすることがある。極端な例だと、「テストケース仕様をExcelで書くようにして構成管理コストを悪化させる」「テストケースのフォーマットをキーワード形式に無理に合わせるためにテストの可読性を犠牲にする」といったものがある。
 そのため、目的(2)のフレームワークを導入すると、目的(1)(3)の達成を難しくする場合がある。そして上記でも軽く触れたけれど、商用のキーワード駆動テストのフレームワークは目的(2)の用途を想定したものが多いので注意が必要だと感じる。目的(2)の達成も十分価値があることだけれど、その価値が活かせない環境ならば、手間が増えるだけになってしまうかもしれない。

目的(1)(3)の改善が必要な場合の注意点

 一方、目的(1)目的(3)が要求される状況では、背景としてそもそもテストスクリプトの品質の悪さが根本原因である場合が少なくない点に留意が必要だ。フレームワークを導入してキーワード形式でフォーマットを固定するよりも、愚直にテストスクリプトの設計改善を行ったほうが問題が解決することがある。
 コードやスクリプトの形式をフレームワークで固定して設計品質を向上させるのはDIやバインディングなどであるけれども、それは設計品質の向上を意図して作られているからできるというのを忘れてはいけない。

目的(2)の改善が必要な場合の注意点

 また目的(2)が要求される場合、そもそもスキル的な問題が根本原因にあることがある。そこではフレームワークを導入するより、テストスクリプトを書けるように教育・人員確保を行ったり、スクリプトの保守性を上げて部分的にでも非開発者がメンテナンスできるようにしたほうが、全体の生産性が高まる場合がある。
 あと目的(2)については、キーワード駆動テストにスクリプトを無理に合わせこむことで、ユーザ等にとってかえってテストがわかりにくくなり書きづらくなることがある。今では柔軟なDSLでテストを書けるフレームワークが複数出ているので、キーワード駆動テスト以外の選択肢の必要性も考慮したほうが良いと思う。

まとめ

 とにかく、キーワード駆動テストを導入する際は、新技術だから、流行っているからという印象でフレームワークに飛びすくのではなく、目的とフレームワークの方向性が一致しているか検討するのも大事だと思う。極端に言えば「担当者は皆テストコードが書けるのに、新技術という事であえてテストをコードでなくExcelで書くように変更した」と同じようなことをしてしまっている可能性もあると感じる。

gauche.testを使う

Gaucheを扱う機会があったのだけれど、とりあえずGaucheでもTDDをやりたいということで、その標準の単体テストライブラリであるgauche.testに触れる。

gauche.test

テストコードは以下の様なもの。ソートの関数my-quick-sortをテストする。

(use gauche.test)  
(load "./my-quick-sort")

(test-start "my-quick-sort")  

(test-section "sort values")
(test* "sort positive value" '(1 2 3 6 8 10) (my-quick-sort '(8 6 10 3 2 1)))
(test* "sort negative value" '(-10 -1 4) (my-quick-sort '(-1 4 -10)))
(test* "sort value with duplicate" '(4 4 7 8 8) (my-quick-sort '(8 7 4 4 8)))

(test-section "sort string")
(test* "sort string" '("abc" "e" "zzzz") (my-quick-sort '("zzzz" "e" "abc")))
(test-end) 

「(test* 〜」がテストケース。「test-section〜」がテストのグループ単位になる。
テストが失敗した際は以下のような通知がされる。

Testing my-quick-sort ...                                        
<sort values>------------------------------------------------------------------
test sort positive value, expects (1 2 3 6 8 10) ==> ok
test sort negative value, expects (-10 -1 4) ==> ok
test sort value with duplicate, expects (4 4 7 8 8 9) ==> ERROR: GOT (4 4 7 8 8)
<sort string>------------------------------------------------------------------
test sort string, expects ("abc" "e" "zzzz") ==> ok
failed.
discrepancies found.  Errors are:
test sort value with duplicate: expects (4 4 7 8 8 9) => got (4 4 7 8 8)

Matcherを使ったテスト

katzchangさんがMatcher付きのassertを公開されている(gauche unitを書いた)
これを使うと以下の様な形式でテストが書けるようになる。

(assert (my-quick-sort '(2 3 1)) (is '(1 2 3)))

問題形成チャートについて

 少し前に佐藤允一さんの問題構造の書籍を読んでいたが、そこで説明される問題形成チャートが書籍で解説される問題構造をうまくまとめていて良かったので、今回紹介したい。なおこのチャートは結構昔に提唱されたものだけれど、REBOKに似た図が使われている等、今でもある程度普及しているようだ。

問題形成チャート

 問題形成チャートは以下のような形を取る。主に問題の構造を明示化するのに使われる。

f:id:goyoki:20140420130949p:plain

 なお注意として、これは問題の因果関係を図化するのではなく、問題を産んだ一連の活動を図化する。例えば「テスト漏れによるバグ流出」という問題について図を描くならば、Whyツリーのようなバグ流出の要因の連なりを描くわけではない。その時のテストのやり方を図に展開して、結果として目標と現状の間にバグ流出という問題が発生している、という図を記述する。

 各部の説明だけれど、この図の定義においては、問題は「目標と現状のギャップ」と定義される。
 「目標」についてだけれど、まずチャートでは目標を生む外部要因として、顧客や社会の変化・条件を「環境変化」として定義している。そして「環境変化」に対して組織としてどのような「目標」に向かうべきか目標水準を具体的なレベルで定義する。次に目標の実現手段として「方針」を明確化する。
 次に「現状」について。こちらは方針や目標に対して、「入力」「制約条件」をインプットに、「外乱」受けながらプロセスを動かした結果、生まれるのが「現状」と構造化している。


 なおこのチャートのメリットだけれど、当然チャートを書くことだけが問題解決策になるわけじゃない。ただ構造化することで問題が理解しやすくなるし、問題点や改善対象を切り分けて考えられるようになる。例えば構造化すれば「要素間のつながりに問題がある(目標に対して方針が適切ではない等)」とか「問題点が外乱とプロセス両方にある。自責のプロセスの方で何とかしていこう」といった思考が可能になる。
 あと佐藤允一さんの書籍では、「問題形成チャート」の名の通り、この図を使って問題形成を整理し、問題を改善をしていくアプローチが解説されている。

具体例

 具体例としては、簡易的なものだけど以下の様なものになる。

f:id:goyoki:20140420130952p:plain

その他の定義

 その他、今回の図で紹介していない定義として以下がある。こちらもこの定義を使用すれば問題が解決するという類のものではないけれど、問題についての会話や分析に有用だと感じる。

  • 問題の分類
    • 普通に維持すべき正常状態からの逸脱や、既存の目標に対する未達など、現状のあるべき水準と現状のギャップは「発生型問題」
    • 将来発生しそうな問題など、未来のあるべき水準と現状のギャップは「設定型問題」
    • 「発生型問題」「設定型問題」に分類されないけれど、現状をより良くするために設定する改善目標と現状のギャップは「探索型問題」
  • 問題の原因構造の定義
    • 「問題」を引き起こした原因のうち、対応可能なものは「問題点」と定義
  • 目標や方針の定義
    • 組織として果たそうとしている使命や社会的機能は「目的」
    • 目的の実現のために達成が必要な水準が「目標」。「目標」は達成度を客観的に評価される形で表現される。なお目標は「組織目標」「チーム目標」「個人目標」とブレークダウンの構造でしばしば階層化される
    • 目標の達成手段のアプローチや方向性が「方針」

C++向けテストライブラリQuickCheck++について

Haskellで著名なテストライブラリにQuickCheckがあるが、C++ではそれに似た機能を実現するQuickCheck++というものが開発されている。今回はこのQuickCheck++について位置づけや例をまとめたい。

検査でのQuickCheckの位置づけ

QuickCheckは、対象の実行領域内で対象が検査式を満たしていることをランダムに検査するために用いられる。この考え方はモデル検査にも似ている。とりあえずQuickCheckの位置づけを整理するために、モデル検査やSmallCheckといった似た技術を以下に列挙する。これらの違いは、基本的に集合や実行領域に対して、どこをどこまで検査するかという点になる。

形式手法におけるモデル検査(SMVやSPIN)

以前紹介( Cadence SMVによるアルゴリズムの検査 - 千里霧中 )したCadense SMV等によるモデル検査は、対象の実行領域すべてで検査式を満たしていることを確認する。

QuickCheckによる検査

QuickCheckは、対象の実行領域の一部をランダムに選びとり、そこで検査式を満たしていることを確認する。なおランダムなのは、全網羅が不可能あるいは困難な場合でも領域を全体的にテストしたい事に寄る。

SmallCheckによる検査

QuickCheckと比較されることの多いライブラリにSmallCheckというものがある。こちらは、対象の実行領域の中でも、特定の実行領域内のみに限定して検査式を満たしていることを確認する。

パラメータ化テスト

ユニットテストなどで一般的なパラメータ化テストは、対象の実行領域の中で、特定の一つの状態を選択して検査式を満たしていることを確認する(なお前述の3つは集合や実行領域を対象としているが、パラメータ化テストはそうでない用例が多いという違いがある。今回はあくまで参考として列挙している)。

QuickCheck++の使い方

本題のQuickCheck++の使い方だけれど、基本的に本家(http://software.legiasoft.com/quickcheck/)のドキュメントの説明が充実している。

入手

入手はドキュメントの通り以下でできる。

git clone http://software.legiasoft.com/git/quickcheck.git

なおQuickCheck++はソースコードをそのまま使用するので、特にビルドなどは必要ない。上記コマンドで得られるファイルのうち、「quickcheck」ディレクトリに格納されているヘッダファイルをインクルードして使用する。

コードの記述

QuickCheckのコードの例は以下の通り。今回はテスト対象のTargetに対して、入力となるvectorのオブジェクトをランダムに生成してテストする。

#include <quickcheck/quickcheck.hh>
#include <vector>

using namespace quickcheck;

class PropertyleapCheck : public Property<std::vector<int> >
{
  bool holdsFor(const std::vector<int>& values)
  {
    //targetがテスト対象オブジェクト
    Target target;
    //テスト対象を生成したvaluesをインプットに動作させる
    for (std::vector<int>::const_iterator it = values.begin(); it != values.end(); ++it)
    {
      target.inputControlValue(*it);
    }
    
    //以下が検査式。式を満たしているならばtrueを返す
    if (target.overshoot_ < 20 && target.undershoot_ < 20) {
      return true;
    } else {
      return false;
    }   
  }
};

なおテストランナーは以下の様な記述になる。ランダムに生成する入力を補正したい場合は、check()の引数やAPIで指定する。例えばテスト回数はデフォルトでは100回となっているが、10倍にしたい場合は「prop.check(1000)」と記述する。

int main(void)
{
  PropertyleapCheck prop;
  prop.check();
}

QuickCheck++の用途

QuickCheck++は本家QuickCheckに倣って、集合や実行領域の検査を目的としている。ただ実際はかなりシンプルなコードであり機能も限定的なため、実質的にはユニットテストでのランダムデータジェネレータのような扱いになる。またQucikCheckのヘッダファイルはGNU GPLが適用されているので、そのまま使うとテスト対象にライセンスの制約が適用される点に留意が必要。
そういった背景があるので、QuickCheck++を検討する場合は、コードは独自に実装して、あくまでアイデアのみを活用するやり方が無難かもしれない。