先日、AAAというテスト自動化のイベントで、25分の短時間枠ですが、テスト自動化に関わる品質モデルや品質特性についてのセッションの機会を頂きました。
http://www.slideshare.net/goyoki/ss-36405244
イベントも大変エモい感じで、楽しい一日でした。
運営の方々、参加者の方々、大変ありがとうございました。
先日、AAAというテスト自動化のイベントで、25分の短時間枠ですが、テスト自動化に関わる品質モデルや品質特性についてのセッションの機会を頂きました。
http://www.slideshare.net/goyoki/ss-36405244
イベントも大変エモい感じで、楽しい一日でした。
運営の方々、参加者の方々、大変ありがとうございました。
だいぶ前に探索的テストの入門解説を行う機会があったのですが、最近その資料を公開しました。
探索的テスト入門改訂版/Introduction to exploratory testing - Speaker Deck
なお今年読んだExplore It!の内容を一部加筆しています。
一般的な割に意外と探索的テストの日本語資料は少ないようですが、何かしらの一助になれば幸いです。
※Classification Tree法のまとまった解説として以下資料を作成しました:
クラシフィケーションツリー法入門/Introduction to Classification Tree Method - Speaker Deck
最新は上記参照ください。以降はバックアップです。
●●●●
ソフトウェアテストの分野では、日本語圏と英語圏で話題や志向が違うことが結構ある。
その違いの代表例の一つに、テスト技法であるClassification Tree法がある。この技法は、海外ではISO/IEC 29119が代表的なテスト技法として挙げているなどそれなりの知名度を持っているそうだが、日本国内では知名度がかなり低い。
今回はそのClassification Tree法について、簡単に紹介したいと思う。
Classification Tree法(Classification Tree Method。クラシフィケーションツリー法。分類木法。略称はCTM)は、テスト観点や同値クラスのモデリング手法に属する。
決定木と同じく、元々Classification Treeは用途をテストに限らない汎用的なモデリング手法として使われてきた。テスト設計ではそれにいくつかのサブセットルールと意味論的な定義を加えて用いることになる。
用途だけれど、テスト設計でのClassification Tree法は、分類木を使って組み合わせや同値分割を階層構造でモデル化するのに用いられる。それによって、同値分割のズームイン・ズームアウトを容易にする、組み合わせの分布を抽象・具象両面で評価可能にする、テスト観点の分析をやりやすくする、といったメリットを確保する。またツールを使って、テスト網羅度のビジュアライズやテストの自動生成をサポートさせる場合もある。
なおこの手法と類似している手法として、テスト観点のモデリングを扱うNGTや、テスト設計手法であるFOTで似た図を用いている。違いとしては、前者はテストアーキテクチャ設計を行うためのテスト観点のパッケージングなどが補強されている。後者は原因結果グラフと同じように、組み合わせの制約を図中に記述できるように拡張されている。
Classification Tree法ではモデリングやテスト設計支援でツールを用いるのが一般的になっている。
ツールによっては、Classification Treeのモデルを作成すると、それに基づいて組み合わせテストを自動生成してくれるものがある。そのためClassification Tree法はモデルベースドテストの実現手段として見られることもあるようだ。
Classification Tree法では、モデル上の各要素をテストケースにおいてどう組み合わせるかは自由度をもたせている。そこではそれぞれのテストの目的や制約に応じて、テスト設計者やツールが全網羅や2因子間網羅などを選択することになる。
Classification Treeは以下の構成要素でモデルを構成する
また、最上位のAspectを「Aspect of interest」と呼称し扱いを区別することもある。
モデルでは上記をツリー上でつなげて記述する。具体的には、以下のようなパターンでツリーを作成する。
なおモデルの表記法は文献によってばらつきがある。ツールによるアシストが一般的な手法のため、ツールの違いが表記のばらつきにつながっているようだ。表記法の例をいくつか以下に紹介する。
書籍 「Guide to Advanced Software Testing」 では以下のような書式スタイルをとる。
例を以下に示す。
割と著名な資料 「Test Case Design Using Classification Trees」 では以下のようなスタイルをとる。
例を以下に示す。
Classification Tree法はツールによって拡張されることが多い。例えばCTEというツールでは、原因結果グラフと同じような制約条件を各構成要素に設定し、テストケースを自動生成できるようにする機能をサポートしている。
最近キーワード駆動テストがややバズワード化している傾向を感じている。というのも、キーワード駆動テストの導入で無用な手間を増やしている場面を見るようになっているためだ。
キーワード駆動テストはフレームワークによっては手間を増やすことがあるので、その導入にあたっては、導入内容が目的に見合っているか多少の注意を向ける必要があると感じる。基本的な事柄であえて言及する必要もない内容かも知れないが、今回はそれについて簡単に触れたい。
言及するまでもないかもしれないけれども、何かしらの改善を行う際は、その手段が目的に見合っているか留意する必要がある。ではキーワード駆動テストの目的は何かというと、大雑把にまとめて以下の3つがある。なおこれは排他ではなく、一緒に目指しても良い。
まず目的の一つに、テスト設計やテスト実装物の保守性改善のための構造化手段として、キーワード駆動テストを導入する場合がある。例えば以下のような目的だ。
まず、見ていて注意が必要だと感じているのが、上記の目的(1)目的(3)の達成のために、目的(2)の用途のフレームワークや技術を導入してしまうパターンだ。
目的(2)のために作られたフレームワークは、非技術者でもテスト設計を担当できるようにする。その代償として、保守性の作りこみに制限をかけたり、キーワードをテストスクリプトに落としこむ手間でテスト実装の効率性を落としたりすることがある。極端な例だと、「テストケース仕様をExcelで書くようにして構成管理コストを悪化させる」「テストケースのフォーマットをキーワード形式に無理に合わせるためにテストの可読性を犠牲にする」といったものがある。
そのため、目的(2)のフレームワークを導入すると、目的(1)(3)の達成を難しくする場合がある。そして上記でも軽く触れたけれど、商用のキーワード駆動テストのフレームワークは目的(2)の用途を想定したものが多いので注意が必要だと感じる。目的(2)の達成も十分価値があることだけれど、その価値が活かせない環境ならば、手間が増えるだけになってしまうかもしれない。
一方、目的(1)目的(3)が要求される状況では、背景としてそもそもテストスクリプトの品質の悪さが根本原因である場合が少なくない点に留意が必要だ。フレームワークを導入してキーワード形式でフォーマットを固定するよりも、愚直にテストスクリプトの設計改善を行ったほうが問題が解決することがある。
コードやスクリプトの形式をフレームワークで固定して設計品質を向上させるのはDIやバインディングなどであるけれども、それは設計品質の向上を意図して作られているからできるというのを忘れてはいけない。
また目的(2)が要求される場合、そもそもスキル的な問題が根本原因にあることがある。そこではフレームワークを導入するより、テストスクリプトを書けるように教育・人員確保を行ったり、スクリプトの保守性を上げて部分的にでも非開発者がメンテナンスできるようにしたほうが、全体の生産性が高まる場合がある。
あと目的(2)については、キーワード駆動テストにスクリプトを無理に合わせこむことで、ユーザ等にとってかえってテストがわかりにくくなり書きづらくなることがある。今では柔軟なDSLでテストを書けるフレームワークが複数出ているので、キーワード駆動テスト以外の選択肢の必要性も考慮したほうが良いと思う。
Gaucheを扱う機会があったのだけれど、とりあえずGaucheでもTDDをやりたいということで、その標準の単体テストライブラリである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)
katzchangさんがMatcher付きのassertを公開されている(gauche unitを書いた)
これを使うと以下の様な形式でテストが書けるようになる。
(assert (my-quick-sort '(2 3 1)) (is '(1 2 3)))
少し前に佐藤允一さんの問題構造の書籍を読んでいたが、そこで説明される問題形成チャートが書籍で解説される問題構造をうまくまとめていて良かったので、今回紹介したい。なおこのチャートは結構昔に提唱されたものだけれど、REBOKに似た図が使われている等、今でもある程度普及しているようだ。
問題形成チャートは以下のような形を取る。主に問題の構造を明示化するのに使われる。
なお注意として、これは問題の因果関係を図化するのではなく、問題を産んだ一連の活動を図化する。例えば「テスト漏れによるバグ流出」という問題について図を描くならば、Whyツリーのようなバグ流出の要因の連なりを描くわけではない。その時のテストのやり方を図に展開して、結果として目標と現状の間にバグ流出という問題が発生している、という図を記述する。
各部の説明だけれど、この図の定義においては、問題は「目標と現状のギャップ」と定義される。
「目標」についてだけれど、まずチャートでは目標を生む外部要因として、顧客や社会の変化・条件を「環境変化」として定義している。そして「環境変化」に対して組織としてどのような「目標」に向かうべきか目標水準を具体的なレベルで定義する。次に目標の実現手段として「方針」を明確化する。
次に「現状」について。こちらは方針や目標に対して、「入力」「制約条件」をインプットに、「外乱」受けながらプロセスを動かした結果、生まれるのが「現状」と構造化している。
なおこのチャートのメリットだけれど、当然チャートを書くことだけが問題解決策になるわけじゃない。ただ構造化することで問題が理解しやすくなるし、問題点や改善対象を切り分けて考えられるようになる。例えば構造化すれば「要素間のつながりに問題がある(目標に対して方針が適切ではない等)」とか「問題点が外乱とプロセス両方にある。自責のプロセスの方で何とかしていこう」といった思考が可能になる。
あと佐藤允一さんの書籍では、「問題形成チャート」の名の通り、この図を使って問題形成を整理し、問題を改善をしていくアプローチが解説されている。
具体例としては、簡易的なものだけど以下の様なものになる。
その他、今回の図で紹介していない定義として以下がある。こちらもこの定義を使用すれば問題が解決するという類のものではないけれど、問題についての会話や分析に有用だと感じる。
Haskellで著名なテストライブラリにQuickCheckがあるが、C++ではそれに似た機能を実現するQuickCheck++というものが開発されている。今回はこのQuickCheck++について位置づけや例をまとめたい。
QuickCheckは、対象の実行領域内で対象が検査式を満たしていることをランダムに検査するために用いられる。この考え方はモデル検査にも似ている。とりあえずQuickCheckの位置づけを整理するために、モデル検査やSmallCheckといった似た技術を以下に列挙する。これらの違いは、基本的に集合や実行領域に対して、どこをどこまで検査するかという点になる。
以前紹介( Cadence SMVによるアルゴリズムの検査 - 千里霧中 )したCadense SMV等によるモデル検査は、対象の実行領域すべてで検査式を満たしていることを確認する。
QuickCheckは、対象の実行領域の一部をランダムに選びとり、そこで検査式を満たしていることを確認する。なおランダムなのは、全網羅が不可能あるいは困難な場合でも領域を全体的にテストしたい事に寄る。
QuickCheckと比較されることの多いライブラリにSmallCheckというものがある。こちらは、対象の実行領域の中でも、特定の実行領域内のみに限定して検査式を満たしていることを確認する。
ユニットテストなどで一般的なパラメータ化テストは、対象の実行領域の中で、特定の一つの状態を選択して検査式を満たしていることを確認する(なお前述の3つは集合や実行領域を対象としているが、パラメータ化テストはそうでない用例が多いという違いがある。今回はあくまで参考として列挙している)。
本題の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(); }