テストの数を減らそう!プリキュアで学ぶPICT

  • 457
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

ソフトウェアのテストはたいへんだなあ

ソフトウェアのテスト、きちんとしてますか?最近は、スマートフォンやタブレットの普及に伴って、ユーザが使うデバイスの種類が多様化しています。

使われるOSやブラウザ、画面サイズの種類が増える中、プリキュア1の多様化も著しいですね。「プリキュアで学ぶワンライナーWebスクレイピング」で検証した通り、昨年までは43人、今年は「魔法つかいプリキュア」が加わることで、プリキュアの数は総勢45人になりました2。プリキュアはキャラクターによって専用デバイスを持ったり3、感情が昂ぶると常識を覆す事象を起こしたりするので、ITサービスを提供するエンジニアの方々は、ユーザ満足度向上のため、当然プリキュアがユーザになった場合も考慮した動作テストをされていると思います。

とはいえ、プラットフォームとプリキュアの組み合わせの数は、既にかなりの数です。全てのパターンを試すととてもテストの数が多くなってしまうので困ってしまいますね。そんな時は、テストの品質をそれほど落とさずに、テスト数を大幅に削減するPairwise法(オールペア法とも呼ばれます)と呼ばれる組み合わせテスト技法を活用しましょう。

この記事では、簡単なPairwise法の考え方と、テストケース4を作ってくれるPICTというソフトウェアの使い方を紹介します。

環境

基本的にMac OSX Yosemite 10.10.5の端末上で実施。zshを使って動作確認してますが、多分bashでも動きます。

Terminal
$ zsh --version
zsh 5.0.6 (x86_64-apple-darwin13.3.0)

PICTの用意

インストール

PICTは、Microsoftが公開している、テストケースを生成するためのソフトウェアです。コマンドライン上で動作します。当然Windowsのコマンドプロンプトでも動きます。Windowsはこちらからファイルを落とせるようです。ここではMac OSXとUbuntu系ディストロでの導入方法を紹介します。

MacOSXの場合
# リポジトリをクローン
$ git clone https://github.com/Microsoft/pict.git
$ cd pict/

$ make

# 成功したらpictコマンドのパスを通す
$ sudo install -m 0755 pict /usr/local/bin/pict
Ubuntuなどの場合
# ディストロによってclangとC++のライブラリを明示的に入れる
$ sudo apt-get install clang libc++-dev

# 後は同じ
$ git clone https://github.com/Microsoft/pict.git
$ cd pict/
$ make
$ sudo install -m 0755 pict /usr/local/bin/pict

インストール後にpictコマンドが使えるようになります。そのまま打つとヘルプがでます。

Terminal
$ pict
Pairwise Independent Combinatorial Testing

Usage: pict model [options]

Options:
 /o:N    - Order of combinations (default: 2)
 /d:C    - Separator for values  (default: ,)
 /a:C    - Separator for aliases (default: |)
 /n:C    - Negative value prefix (default: ~)
 /e:file - File with seeding rows
 /r[:N]  - Randomize generation, N - seed
 /c      - Case-sensitive model evaluation
 /s      - Show model statistics

簡単な動作確認

こんなファイルを用意する。

test.txt
DATA1: A, B, C
DATA2: 1, 2, 3

コマンドの引数に渡すとテストケースを作ってくれます。1行1ケースになっています。

Terminal
$ pict test.txt
DATA1   DATA2
B       1
B       3
A       2
C       1
A       3
C       3
B       2
A       1
C       2

入力するファイルは下記のようなフォーマットに従うテキストファイルにします。テストのパラメータ名をコロン:で区切り、パラメータの値をカンマ,で区切ります5。なお、このようなpictがテストケースを生成するための元の情報をモデル、それを含むファイルのことをモデルファイルと呼びます。

<テストのパラメータ名1>: <パラメータ値>, <パラメータ値>, ...
<テストのパラメータ名2>: <パラメータ値>, <パラメータ値>, ...

.
.
.

Pairwise法ってなに?

下記のようなモデルファイルを用意して、テストケースを作成してみましょう。
「OS」が2種類、「通信キャリア」が3種類、「プリキュア」が2種類あります。

test.txt
OS: iOS, Android
通信キャリア: DoCoMo, Au, SoftBank
プリキュア: キュアブラック, キュアホワイト

ということは、全通り試すと2 * 3 * 2 = 12通りのテストケースができるはずですね?
PICTを使うと、テストケースを削り、下記の6種類の効率的なテストケースを作成してくれます。

$ pict test.txt
OS       通信キャリア   プリキュア
Android  Au            キュアブラック
Android  DoCoMo        キュアホワイト
iOS      Au            キュアホワイト
iOS      SoftBank      キュアホワイト
iOS      DoCoMo        キュアブラック
Android  SoftBank      キュアブラック

ここで、「OS」と「通信キャリア」の組み合わせに注目してみてください。この2つに関しては、存在する全組み合わせが網羅されていると思います。同様に「通信キャリア」と「プリキュア」、そして「プリキュア」と「OS」に関しても同様です。このようにテストのパラメータ2つでペアを作ったとき、どんなペアの組み合わせでもお互いのパラメータの組み合わせを全て網羅するようにケースを作る方法のことをPairwise法と呼びます。

それって信頼できるテストなの?

ちょっと古い研究ですが、バグの70%は2つ以下のパラメータ6に、90%くらいは3つ以下のパラメータに何かしらの関連があったようです7

つまり「バグの原因が同時に出てくる場所って、大半は一度に1,2パラメータくらいなんでしょ?テストでは、2パラメータ(ペア)同時に何か起きても問題ないことを確認すれば十分じゃね?全部のパターン試してたらキリないし。」というのがPairwise法の考え方です。(後述しますがもちろん、3パラメータ以上の組み合わせパターンの生成もできます。)

どれくらいケース数が削減できるの?

MicrosoftのPICTについての論文8があります。とっても減るみたいですね(雑)

IC91126.gif

詳しく知りたい人は論文を読んで下さい。

とにかくテストケースを作ってみよう

基本的な使い方は説明したので、ここからは、pictのちょっとした応用をしてみたいと思います。pictの出力結果ですが、以降は幅を揃えてソートするなどして、適宜見やすくしたものをお見せします。実際の出力とは順番が違うかもしれません。整形方法に関しては、後述するPICT用整形用コマンドを作るで説明してあります。

2パラメータを超える網羅

下記はテストケースの材料となるモデルファイルです。ちなみにシェルスクリプトみたいに#で始まる行はコメントになり、pictに解釈されません。

test.txt
# OSの種類
OS: iOS, Android
# ブラウザの種類
Browser: Firefox, Chrome, Safari
# 画面の大きさ
Inch: 3.5, 4.0, 4.5, 5.0, 5.5, 6.0
# 言語のロケール
Lang: JA, EN
# プリキュア
Precure: キュアブラック, キュアホワイト, シャイニールミナス

pictコマンドを使うとこんな出力が出ます。この結果は2パラメータ間の組み合わせを網羅したものです。これはデフォルトの挙動です。18ケース作成されました。

Terminal
$ pict test.txt
OS       Browser  Inch  Lang  Precure
Android  Chrome   3.5   JA    キュアブラック
Android  Chrome   4.5   JA    キュアブラック
Android  Chrome   5.5   JA    キュアホワイト
Android  Chrome   6.0   EN    シャイニールミナス
Android  Firefox  4.0   JA    キュアホワイト
Android  Firefox  4.5   EN    シャイニールミナス
Android  Firefox  5.5   JA    キュアブラック
Android  Safari   3.5   EN    シャイニールミナス
Android  Safari   4.0   EN    キュアブラック
Android  Safari   5.0   JA    キュアブラック
Android  Safari   6.0   EN    キュアホワイト
iOS      Chrome   4.0   JA    シャイニールミナス
iOS      Chrome   5.0   EN    シャイニールミナス
iOS      Firefox  3.5   EN    キュアホワイト
iOS      Firefox  5.0   EN    キュアホワイト
iOS      Firefox  6.0   JA    キュアブラック
iOS      Safari   4.5   JA    キュアホワイト
iOS      Safari   5.5   EN    シャイニールミナス

ただし先ほど説明したように、確率は低いですが3つのパラメータが同時に関連したバグ出てくるかもしれないです。特にバグが出尽くしていない新しいシステムでは不安ですね。そういう時は、ケースは増えてしまいますが、3パラメータ間を網羅したテストケースを作成しましょう。

/o:3というオプションを付けましょう。これにより、3パラメータ間の組み合わせを網羅した55ケースが作成されます。

$ pict test.txt /o:3
OS      Browser Inch    Lang    Precure
iOS     Firefox 4.5     JA      キュアブラック
iOS     Firefox 3.5     EN      シャイニールミナス
Android Chrome  3.5     EN      キュアブラック
Android Firefox 5.5     EN      キュアブラック
iOS     Chrome  6.0     JA      シャイニールミナス
Android Firefox 5.5     JA      キュアホワイト
iOS     Safari  6.0     EN      キュアブラック
Android Firefox 6.0     EN      キュアホワイト
iOS     Chrome  5.5     EN      シャイニールミナス
# ...(以下続く。)

同様に、網羅したいパラメータの組み合わせ数を増やしたい場合は/o:4,/o:5...という風に数字を指定します。(Microsoft製なだけあり/で始まるコマンドラインオプションは、UNIXな感じではなくMS-DOSな感じですね。)

統計情報表示:プリキュアオールスターズ

さて、プリキュア3人だけのテストではさすがにサービスレベルが保てないので、テストケースに全プリキュアをいれましょう。

allstars.txt
OS: iOS, Android
Browser: Firefox, Chrome, Safari
Inch: 3.5, 4.0, 4.5, 5.0, 5.5, 6.0
Lang: JA, EN
# プリキュア45人を追加する
Precure: キュアブラック,キュアホワイト,シャイニールミナス,キュアブルーム,キュアブライト,キュアイーグレット,キュアウィンディ,キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア,ミルキィローズ,キュアピーチ,キュアベリー,キュアパイン,キュアパッション,キュアブロッサム,キュアマリン,キュアサンシャイン,キュアムーンライト,キュアメロディ,キュアリズム,キュアビート,キュアミューズ,キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,キュアハート,キュアダイヤモンド,キュアロゼッタ,キュアソード,キュアエース,キュアラブリー,キュアプリンセス,キュアハニー,キュアフォーチュン,キュアフローラ,キュアマーメイド,キュアトゥインクル,キュアスカーレット,キュアミラクル,キュアマジカル
Terminal
$ pict allstars.txt
OS      Browser Inch    Lang    Precure
iOS     Firefox 3.5     JA      キュアミント
iOS     Safari  6.0     EN      キュアマリン
Android Chrome  5.0     JA      キュアベリー
Android Chrome  6.0     JA      キュアピーチ
Android Chrome  4.0     EN      キュアミント
Android Firefox 6.0     JA      キュアソード
Android Safari  3.5     EN      キュアルージュ
iOS     Safari  5.5     JA      キュアダイヤモンド
iOS     Firefox 6.0     EN      ミルキィローズ
(..以下略..)

ケースが多い場合は/sオプションを付けましょう。すると、実際に出力する前に、どれくらいのケースができるのか表示してくれます。

$ pict allstars.txt /s
Combinations:   643
Generated tests:270
Generation time:0:00:00

270ケース作成されるようですね。

条件付き制約:みゆきちゃんが英語読めるわけ無いだろ。

恐らく全プリキュアの中で最も頭が弱い子はキュアハッピーこと星空みゆきちゃんで間違いないでしょう。私には、とてもではありませんが彼女に英語表記のインターフェースが使いこなせるとは思えません。
しかし残念なことに先ほど作成した270のテストケースの中で、キュアハッピーが英語の端末を操作しなければいけないケースが3つも含まれています。

Terminal
$ pict allstars.txt| grep キュアハッピー
iOS     Firefox 5.0     EN      キュアハッピー
Android Chrome  4.0     JA      キュアハッピー
iOS     Safari  6.0     EN      キュアハッピー
iOS     Chrome  4.5     JA      キュアハッピー
Android Firefox 5.5     EN      キュアハッピー
iOS     Chrome  3.5     JA      キュアハッピー

このようなケースはテスト実施ができないため、除外しなければいけません。こんな時に役に立つのが条件付き制約(Conditional Constraints)の機能です。

下記の一文を、テストの材料のテキストの末尾に追加しましょう。

if [Precure] = "キュアハッピー" then [Lang] = "JA";

このように[パラメータ名] <演算子> [値]で、条件式を記述できます。
if 条件式1 then 条件式2 ;で、条件式1が真の場合、条件式2が真になるようなテストケースが作成されます。

さて、下記のようなモデルファイルができると思います。これをpictを使って処理します。

allstar-happy_ja.txt
OS: iOS, Android
Browser: Firefox, Chrome, Safari
Inch: 3.5, 4.0, 4.5, 5.0, 5.5, 6.0
Lang: JA, EN
Precure: キュアブラック,キュアホワイト,シャイニールミナス,キュアブルーム,キュアブライト,キュアイーグレット,キュアウィンディ,キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア,ミルキィローズ,キュアピーチ,キュアベリー,キュアパイン,キュアパッション,キュアブロッサム,キュアマリン,キュアサンシャイン,キュアムーンライト,キュアメロディ,キュアリズム,キュアビート,キュアミューズ,キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,キュアハート,キュアダイヤモンド,キュアロゼッタ,キュアソード,キュアエース,キュアラブリー,キュアプリンセス,キュアハニー,キュアフォーチュン,キュアフローラ,キュアマーメイド,キュアトゥインクル,キュアスカーレット,キュアミラクル,キュアマジカル

if [Precure] = "キュアハッピー" then [Lang] = "JA";

すると、見事にキュアハッピーが出現するテストケースは日本語(JA)のみの組み合わせになりました。

$ pict allstars-happy_ja.txt | grep キュアハッピー
iOS     Safari  5.0     JA      キュアハッピー
Android Firefox 4.0     JA      キュアハッピー
iOS     Chrome  4.5     JA      キュアハッピー
Android Chrome  6.0     JA      キュアハッピー
Android Safari  5.5     JA      キュアハッピー
iOS     Safari  3.5     JA      キュアハッピー

ふと気付いたのですが、Android端末でSafariが使えるのはおかしいですね。このケースも取り除きましょう。下記のように、Not条件も<>という演算子を使うことで指定できます。

if [OS] = "Android" then [Browser] <> "Safari";

ちなみに特にテストケース数が削られている訳ではないので、少なくとも網羅率9は下がってはいないようです。

Terminal
$ pict allstars-happy_ja.txt /s
Combinations:   642
Generated tests:270
Generation time:0:00:00

使える演算子や記法については、Githubにある資料に詳しくあります。

ワイルドカードを使った制約:「キュア○○」しか妖精の力で変身できない

皆さんご存知の通り、女の子がプリキュアに変身するためには妖精の力が必要です。厳密なテスト実施のためには、妖精もテストのパラメータとして入れるべきでしょう。

allstars-fairies.txt
OS: iOS, Android
Browser: Firefox, Chrome, Safari
Inch: 3.5, 4.0, 4.5, 5.0, 5.5, 6.0
Lang: JA, EN
Precure: キュアブラック,キュアホワイト,シャイニールミナス,キュアブルーム,キュアブライト,キュアイーグレット,キュアウィンディ,キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア,ミルキィローズ,キュアピーチ,キュアベリー,キュアパイン,キュアパッション,キュアブロッサム,キュアマリン,キュアサンシャイン,キュアムーンライト,キュアメロディ,キュアリズム,キュアビート,キュアミューズ,キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,キュアハート,キュアダイヤモンド,キュアロゼッタ,キュアソード,キュアエース,キュアラブリー,キュアプリンセス,キュアハニー,キュアフォーチュン,キュアフローラ,キュアマーメイド,キュアトゥインクル,キュアスカーレット,キュアミラクル,キュアマジカル
# 新しく妖精一覧を追加
Fairy: メップル,ミップル,ポルン,ルルン,フラッピ,ムープ,チョッピ,フープ,ピルン,ブルン,キルン,アカルン,シプレ,コフレ,ポプリ,コロン,ドリー,ミリー,レリー,ファリー,ラリー,ソリー,ドドリー,シリー,シャルル,ラケル,ランス,ダビィ,アイちゃん,リボン,ぐらさん,モフルン

しかし、ここで問題があります。変身に妖精の力が必要のない戦士がいます。例えば、シャイニールミナスやミルキーローズです。彼女らは厳密には「伝説の戦士 プリキュア」ではないです。対して「キュア○○」となっている戦士は妖精が必要なプリキュアとざっくり言ってよいでしょう10

このような場合に備え、pictでは、SQLのようなLIKE述語が使えます。これにより、値が文字列のパターンに合致するときのテストケースを制御できます。

if [Precure] like "キュア*"
then [Fairy] <> "N/A"
else [Fairy] = "N/A";

これは、もしも「Precure」のパラメータ値が"キュア*"の時、そのテストケースの「Fairy」のパラメータは"N/A"以外になり、その他全ては"N/A"になる。という命令になります。この命令を動かすためには、「Fairy」のパラメータに"N/A"を追加します。

allstars-fairies2.txt
OS: iOS, Android
Browser: Firefox, Chrome, Safari
Inch: 3.5, 4.0, 4.5, 5.0, 5.5, 6.0
Lang: JA, EN
Precure: キュアブラック,キュアホワイト,シャイニールミナス,キュアブルーム,キュアブライト,キュアイーグレット,キュアウィンディ,キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア,ミルキィローズ,キュアピーチ,キュアベリー,キュアパイン,キュアパッション,キュアブロッサム,キュアマリン,キュアサンシャイン,キュアムーンライト,キュアメロディ,キュアリズム,キュアビート,キュアミューズ,キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,キュアハート,キュアダイヤモンド,キュアロゼッタ,キュアソード,キュアエース,キュアラブリー,キュアプリンセス,キュアハニー,キュアフォーチュン,キュアフローラ,キュアマーメイド,キュアトゥインクル,キュアスカーレット,キュアミラクル,キュアマジカル

# 妖精がいないことを示す"N/A"を追加。
Fairy: N/A, メップル,ミップル,ポルン,ルルン,フラッピ,ムープ,チョッピ,フープ,ピルン,ブルン,キルン,アカルン,シプレ,コフレ,ポプリ,コロン,ドリー,ミリー,レリー,ファリー,ラリー,ソリー,ドドリー,シリー,シャルル,ラケル,ランス,ダビィ,アイちゃん,リボン,ぐらさん,モフルン

# 新しく3行を追加
if [Precure] like "キュア*"
then [Fairy] <> "N/A"
else [Fairy] = "N/A";

たしかに、妖精(Fairy)の箇所が"N/A"となっているプリキュアは、ミルキィローズとシャイニールミナスのみになっています。

$ pict allstars-fairies2.txt | grep 'N/A'
Android  Firefox  3.5  EN  シャイニールミナス  N/A
iOS      Safari   5.0  JA  ミルキィローズ      N/A
Android  Chrome   6.0  EN  ミルキィローズ      N/A
iOS      Firefox  4.0  EN  ミルキィローズ      N/A
iOS      Chrome   4.0  JA  シャイニールミナス  N/A
iOS      Safari   5.5  JA  シャイニールミナス  N/A
iOS      Safari   4.5  JA  シャイニールミナス  N/A
iOS      Safari   3.5  EN  ミルキィローズ      N/A
iOS      Chrome   6.0  JA  シャイニールミナス  N/A
Android  Safari   5.5  JA  ミルキィローズ      N/A
iOS      Firefox  4.5  EN  ミルキィローズ      N/A
Android  Chrome   5.0  EN  シャイニールミナス  N/A

数値比較による制約:アコちゃんはそんなおっきなディスプレイもてない!

アコちゃんことキュアミューズは、小学3年生の最年少プリキュアです。そんな小さなプリキュアでは、iPhone6s plusのような5インチ以上の大きな画面の端末を片手で操作するのは厳しいでしょう。可哀想なので、これもテストケースから除かないといけません(使命感)。
さて、pictには、こういう時に使える数値の比較をして条件として指定できる機能もあるので活用しましょう。

もう察しの良い方はおわかりかと思いますが、<>のような比較演算子が使えます。下記の行をテスト用テキストに追記してみましょう。

allstars-fairies-muse.txt
# allstars-fairies2.txtに追記
if [Precure] = "キュアミューズ" then [Inch] < 5.0;

結果は下記のようになります。見事、キュアミューズのテストケースは全て5インチ未満になっていますね。

Terminal
$ pict allstars-fairies-muse.txt | grep キュアミューズ
OS       Browser  Inch  LANG  Precure             Fairy
iOS     Chrome  4.0     JA      キュアミューズ  アカルン
Android Firefox 4.5     EN      キュアミューズ  ラリー
iOS     Safari  4.5     JA      キュアミューズ  コフレ
iOS     Firefox 4.0     EN      キュアミューズ  ポプリ
iOS     Chrome  3.5     JA      キュアミューズ  シプレ
iOS     Firefox 4.5     JA      キュアミューズ  ミップル
iOS     Chrome  3.5     JA      キュアミューズ  シャルル
iOS     Firefox 3.5     EN      キュアミューズ  ブルン
iOS     Firefox 4.0     JA      キュアミューズ  ランス
iOS     Chrome  3.5     JA      キュアミューズ  ルルン
(..以下略..)

pictの演算

ここまで、比較演算子や文字列のマッチングを見てきました。pictは簡単なインタプリタを実装しています。オペランドには2種類のデータ型が使えます。数値型と、文字列型です。
ダブルクオートで囲まれたものは文字列としてみなされ、先ほど紹介したLIKEなどが使えます。細かい仕様は把握していないのですが、数値型は整数だけでなく、浮動小数点数も含んでいるようです。また、文字列型に>のような比較演算子を使った場合、辞書順での比較をします(英語のみだと思います)。再掲になりますが詳しくは、Githubのマニュアルを見ていただければと思います。

エイリアス:パワーアップ前後で名前が違うプリキュア

テストケースでは、同物異名、すなわち違う名前だけど同じ値として扱いたいものがよくあると思います11。さて、このテストケースの中にも同物異名、もとい同人異キュアとしてとらえても良いプリキュアがいますね。はい、もう皆さんご存知、キュアブルームとキュアブライトは日向咲ちゃんが、キュアイーグレットとキュアウィンディは美翔舞ちゃんがそれぞれ変身するプリキュアです。パワーアップ前後で名前が違いますが中の人は同一人物なので、ケースは一緒で良いかもしれませんね12

こういう場合はpictのエイリアスを使いましょう。エイリアスを使うと、複数のパラメータの値を、一つの値のように扱えます。値A|値Bのように値を,ではなく|で区切ると、値Aと値Bは同じものとして扱われます。

今回の場合、45人のプリキュアが記述してあるallstars.txtには下記の記述があります。

allstars.txt
...
Precure: (..中略..),キュアブルーム,キュアブライト,キュアイーグレット,キュアウィンディ,(..中略..)
...

これを、こうします。

allstars-unique.txt
...
Precure: (..中略..),キュアブルーム|キュアブライト,キュアイーグレット|キュアウィンディ,(..中略..)
...

さて、pictを動かしてみましょう。まず既に述べたように45人のプリキュアを記述したallstars.txtでは270ケースのテストケースが生成されました。つまり、プリキュア1人あたり6ケースを占めます。
エイリアス適用後は、2人分のプリキュアのケースが減るため、下記のように12ケース減り、258ケースになります。

Terminal
$ pict allstars-unique.txt /s
Combinations:   617
Generated tests:258
Generation time:0:00:00

大文字と小文字

なおpictの条件式は、デフォルトではパラメータ名の英字の大文字と小文字を区別しません。

allstars-happy_jaja.txt
(..中略..)
# JAに加えて、jaも増やした。
Lang: ja, JA, EN
(..中略..)
# みゆきちゃんは日本語オンリーだから"JA"を指定
if [Precure] = "キュアハッピー" then [Lang] = "JA";

Jajaが混在してしまいます。
2つはエイリアス状態になっています。

Terminal
$ pict allstars-happy_jaja.txt | grep キュアハッピー
Android Chrome  6.0     JA      キュアハッピー
iOS     Firefox 5.0     ja      キュアハッピー
iOS     Safari  4.5     JA      キュアハッピー
Android Firefox 3.5     ja      キュアハッピー
Android Safari  5.5     ja      キュアハッピー
iOS     Firefox 4.0     ja      キュアハッピー

/cオプションをつけると、区別してくれます。

Terminal
$ pict allstars-happy_jaja.txt  /c | grep キュアハッピー
iOS     Chrome  6.0     JA      キュアハッピー
Android Safari  4.0     JA      キュアハッピー
iOS     Firefox 3.5     JA      キュアハッピー
Android Chrome  5.5     JA      キュアハッピー
Android Safari  5.0     JA      キュアハッピー
iOS     Safari  4.5     JA      キュアハッピー

無条件制約: ふたりでプリキュア

最近のプリキュアは2人組というパターンが減っていますね。Yes!プリキュア5は、初の5人組プリキュアです。5人もいると、視聴者は覚えるのが大変です。下記のモデルで2人抜き出してペアにしてしまいましょう。

precure5.txt
# 1キュア
Precure1:キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア
# 2キュア
Precure2:キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア

結果は以下になります。しかしよく見ると、同じ名前のプリキュアでペアを組んでしまっています。

Terminal
$ pict precure5.txt
Precure1        Precure2
キュアドリーム  キュアルージュ
キュアミント    キュアドリーム
キュアレモネード        キュアレモネード
キュアアクア    キュアミント
キュアミント    キュアレモネード
キュアルージュ  キュアドリーム
キュアドリーム  キュアレモネード
キュアレモネード        キュアドリーム
キュアアクア    キュアルージュ
キュアアクア    キュアドリーム
キュアルージュ  キュアレモネード
キュアルージュ  キュアミント
キュアアクア    キュアアクア
キュアドリーム  キュアドリーム(..中略..)

こういう時は「無条件制約 (Unconditional Constraints)」を使いましょう。
既に説明したifから始まる「条件付き制約」とほぼ同じですが、ifをつけず、そのまま条件式を書きます。下記のような感じです。

precure5-pair.txt
# 1キュア
Precure1:キュアドリーム,キュアルージュ,キュアレモネード,キュアミント, キュアアクア
# 2キュア
Precure2:キュアドリーム,キュアルージュ,キュアレモネード,キュアミント, キュアアクア

# 同一キュアがペアにならないようにする
[Precure1] <> [Precure2];

すると全ての結果は、その条件式を満たすようになります。簡単ですね。
andorという演算子で論理的な条件の組み合わせも表現できます。

precure5-trio.txt
# 1キュア
Precure1:キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア
# 2キュア
Precure2:キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア
# 3キュア
Precure3:キュアドリーム,キュアルージュ,キュアレモネード,キュアミント,キュアアクア

# andを使い、同一キュアがトリオにならないようにする
[Precure1] <> [Precure2] and [Precure2] <> [Precure3];

異常系テスト: バッドエンドプリキュア

エラーを検知し、意図的に動作を停止させるパターンもテストしたい場合があると思います。pictは、正常系だけでなく、異常を検知するテストのパターンの生成にも使えます。

異常系のテスト13のケース作成では、意図的にパラメータに異常値を入れておくことがあるんじゃないかと思います。下記の例は、先ほどと同じようにスマイルプリキュアの5人から、2人ピックアップしてペアを作るモデルファイルです。

badend.txt
# スマイル勢5人 + バッドエンド5人
Precure1:キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,バッドエンドハッピー,バッドエンドサニー,バッドエンドピース,バッドエンドマーチ,バッドエンドビューティ
# 同上
Precure2:キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,バッドエンドハッピー,バッドエンドサニー,バッドエンドピース,バッドエンドマーチ,バッドエンドビューティ

# 同一キュアがペアにならないようにする
[Precure1] <> [Precure2];

おっと、よく見ると、バッドエンドプリキュアが混じっています。悪い奴らなので、しっかりとpictでケースを作って、入ってたら失敗にしてやりましょう。

# 90ケース作成される
$ pict badend.txt
Precure1                Precure2
バッドエンドピース      バッドエンドサニー
バッドエンドサニー      バッドエンドピース
キュアハッピー          バッドエンドビューティ
キュアビューティ        バッドエンドビューティ
バッドエンドビューティ  キュアマーチ
キュアハッピー          バッドエンドピース
キュアピース            バッドエンドハッピー
バッドエンドピース      キュアピース
キュアハッピー          キュアピース
(..中略..)

ここで、作成されたテストケースに注目して下さい。バッドエンドプリキュア同士のペアが存在しますね。つまり、与えられるパラメータが全て異常値のケースです。

ところで、一度のプログラムの実行で「検知可能な全てのエラーを拾う実装」って、結構珍しくないでしょうか?
どちらかと言えば、「途中で何かしらのエラーを検知したら処理を中断して例外を投げる実装」の方がお目にかかることが多いと思います14
例えば、下記のような例外の投げ方です。

cure_validation.js
// 異常値検知のイメージ
function cure_validation(precure1, precure2) {

    // プリキュア1がバッドエンドだったら終了
    if (precure1.match(/^バッドエンド/)) {
        throw new Error("バッドエンド");
    }

    // プリキュア2がバッドエンドだったら終了
    if (precure2.match(/^バッドエンド/)) {
        throw new Error("バッドエンド");
    }

    return "ハッピーエンド!";
}

このような場合、precure1がバッドエンドプリキュアだった場合、precure2の入力がどんなものだったとしてもエラーとなります。与えられるパラメータが全て異常値のケースを試しても、一つの異常値の入力が、他の値の存在を隠蔽してしまうかもしれないので、テストの実施効率が悪いです。(ホワイトボックステスト的な言い方をするのであれば、カバレッジを向上させるのに非効率なケースとなります。)
そこで「1ケースあたり異常値は1つ」にして効率化をはかるための機能がpictにはあります。異常値となる値の先頭に~(チルダ)をつけてみましょう15。この記号をつけた値は、他の値を隠蔽するものとして扱われるので、1ケースに1つしか出現しなくなります。モデルファイルは下記のような感じです。

badend2.txt
# "バッドエンド" → "~バッドエンド"
Precure1:キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,~バッドエンドハッピー,~バッドエンドサニー,~バッドエンドピース,~バッドエンドマーチ,~バッドエンドビューティ
# "バッドエンド" → "~バッドエンド"
Precure2:キュアハッピー,キュアサニー,キュアピース,キュアマーチ,キュアビューティ,~バッドエンドハッピー,~バッドエンドサニー,~バッドエンドピース,~バッドエンドマーチ,~バッドエンドビューティ

[Precure1] <> [Precure2];

確かに結果は下記のように、1ケースに異常値は一つになっています。

Terminal
$ pict badend2.txt | grep バッドエンド | head
キュアビューティ        ~バッドエンドマーチ
キュアサニー    ~バッドエンドピース
キュアビューティ        ~バッドエンドピース
キュアピース    ~バッドエンドピース
~バッドエンドハッピー   キュアビューティ
キュアビューティ        ~バッドエンドサニー
~バッドエンドピース     キュアハッピー
~バッドエンドサニー     キュアビューティ
~バッドエンドハッピー   キュアピース
~バッドエンドサニー     キュアハッピー

これは、ペアではなくトリオにしても同じになります。

サブモデル:新人育成のための重点的なOJT

プリキュアオールスターズという映画が毎年3月に公開されます。これは、今までのTVシリーズの全てのプリキュアが集まり、協力して戦う作品です。2月に放映が始まったばかりのいわば「新人のプリキュア」が、歴代の先輩のプリキュアに心なしか敬意を払いながら協力する場面もあり、「新人研修」などと一部のファンから言われることもあります。

では、新人研修のために古参のプリキュアと比較的新参のプリキュアを混ぜて5人チームを作るケースを作ってみましょう。

ojt.txt
# 古参キュア
ふたりはプリキュア: キュアブラック, キュアホワイト

# 古参キュア
ふたりはプリキュア Splash Star: キュアブルーム, キュアイーグレット

# 新参キュア
ハピネスチャージプリキュア!: キュアラブリー, キュアプリンセス, キュアハニー, キュアフォーチュン

# 新参キュア
Go!プリンセスプリキュア: キュアフローラ, キュアマーメイド, キュアトゥインクル, キュアスカーレット

# 新参キュア
魔法つかいプリキュア: キュアマジカル, キュアミラクル
Terminal
$ pict ojt.txt /s
Combinations:   76
Generated tests:16
Generation time:0:00:00

上記のように16ケースを作成してみました。

ふと思いましたが、全体的にコミュ力が高く経験のある古参組は良いものの、新参組には問題が見受けられます。特にキュアプリンセスはかなりのコミュ障な上、「キュアプリンセスはプリンセスプリキュアではない」などと、入ったばかりの魔法つかいプリキュア勢に説明しても混乱を招くだけでしょう。場慣れの機会がもっと必要なことは、プリキュアファンの方なら容易にお分かりいただけると思います。

プリキュアもソフトウェアも、真新しい時にしっかりと問題をあぶり出すのが安心です。こういう時は2パラメータを超える網羅で説明した方法で、単純に網羅率を上げるのも良いですが、いたずらにテストケースが多くなりがちです。

Terminal
# パラメータの組み合わせ数を3に設定。
# 38ケースにもなる。ふたりはプリキュア勢は一人19ケースもこなさなければいけない。
$ pict ojt.txt /o:3 /s
Combinations:   200
Generated tests:38
Generation time:0:00:00

OJTといえば、ひとつ上、ふたつ上くらいの先輩が重点的に面倒を見るのが理想ですし、
それと同時に、人数の少ない古参プリキュアにかかる負担は極力減らすべきでしょう。

そんな時にはpictのサブモデル(Sub-models)を活用しましょう。サブモデルを使うことで、特定のパラメータ同士の組み合わせを重点的に試すケースを作ることができます。
モデルファイルに下記のフォーマットに従った記述を加えましょう。すると、指定したパラメータをグループにできます。このグループが1つのサブモデルです。

{ <パラメータ名1>, <パラメータ名2>, <パラメータ名3>, ... } @ <パラメータ組み合わせ数>

pictは、サブモデルを、一旦は一つの大きなパラメータとして扱い、テストケースの生成をすると同時に、
サブモデル内のパラメータ同士を<パラメータ組み合わせ数>を使ってPairwise法でテストケースの生成をしてくれます。最終的な結果として、それらを両立したものを出してくれます。

実際にやってみましょう。

ojt2.txt
# 古参キュア
ふたりはプリキュア: キュアブラック, キュアホワイト

# 古参キュア
ふたりはプリキュア Splash Star: キュアブルーム, キュアイーグレット

# 新参キュア
ハピネスチャージプリキュア!: キュアラブリー, キュアプリンセス, キュアハニー, キュアフォーチュン

# 新参キュア
Go!プリンセスプリキュア: キュアフローラ, キュアマーメイド, キュアトゥインクル, キュアスカーレット

# 新参キュア
魔法つかいプリキュア: キュアマジカル, キュアミラクル

# 新参キュアを2のパラメータ組み合わせ数でサブモデルとして登録する
{ ハピネスチャージプリキュア!, Go!プリンセスプリキュア, 魔法つかいプリキュア } @ 2

このファイルを実行すると、32ケース完成します。単純に3のパラメータ組み合わせ数で実施した時よりは6ケース少ないです。

Terminal
$ pict ojt2.txt /s
Combinations:   104
Generated tests:32
Generation time:0:00:00

ここで、新参プリキュアの結果のみに注目してみましょう。

Terminal
# 新参プリキュアのみ出力し、重複部分を調べる。
$ pict ojt2.txt | awk -F '\t' 'NR > 1 {print $3,$4,$5}' | sort | uniq -c | column -t
2  キュアハニー        キュアフローラ      キュアマジカル
2  キュアハニー        キュアマーメイド    キュアミラクル
2  キュアハニー        キュアスカーレット  キュアマジカル
2  キュアハニー        キュアトゥインクル  キュアミラクル
2  キュアラブリー      キュアフローラ      キュアミラクル
2  キュアラブリー      キュアマーメイド    キュアマジカル
2  キュアラブリー      キュアスカーレット  キュアミラクル
2  キュアラブリー      キュアトゥインクル  キュアミラクル
2  キュアプリンセス    キュアフローラ      キュアマジカル
2  キュアプリンセス    キュアマーメイド    キュアミラクル
2  キュアプリンセス    キュアスカーレット  キュアミラクル
2  キュアプリンセス    キュアトゥインクル  キュアマジカル
2  キュアフォーチュン  キュアフローラ      キュアミラクル
2  キュアフォーチュン  キュアマーメイド    キュアマジカル
2  キュアフォーチュン  キュアスカーレット  キュアマジカル
2  キュアフォーチュン  キュアトゥインクル  キュアマジカル

新参プリキュアの部分のみに限っては、同じパターンが2回網羅されています。これは、新参プリキュアのパラメータ全体が、一つのパラメータとしてもまた網羅されているからです。

この機能は、幾つかのパラメータと、それらを含んだ集合自体もまたパラメータとしてテストをしたい時に便利です。16

ちなみにサブモデルは下記のように、複数設定したり、一部重複するパラメータを入れたりできます。

# 何もパラメータ組み合わせ数を指定しないとデフォルト値の2が使われる。
{ ふたりはプリキュア, ハピネスチャージプリキュア! }
{ ふたりはプリキュア, 魔法つかいプリキュア } @ 2
{ ハピネスチャージプリキュア!, Go!プリンセスプリキュア, 魔法つかいプリキュア } @ 3

重み調整:キュアソードは優先したい

Pairwise法で算出した結果は、実はランダム要素が入り込む余地があります。入力に対して、最低条件として「2パラメータ間の組み合わせを全部網羅する」パターンを出すため、複数の候補が生まれる場合があります。pictでは、そのような場合はランダムに1パターン提示してくれるようになっています。どういうことなのか具体例で見ていきましょう。

例えばこのような、紫プリキュアと一緒にごはんを食べるテストケースを考えるとします。

pregohan.txt
時間帯: 朝, 昼, 夜
種類: 和食, 洋食, 中華
紫プリキュア: キュアソード, キュアフォーチュン

これをpictで実施するとこうなります。

$ pict pregohan.txt
# 分かりやすいように、ソートして行番号をつけてます。
行番号  時間帯  種類  紫プリキュア
1      夜     中華  キュアフォーチュン
2      夜     和食  キュアフォーチュン
3      夜     洋食  キュアソード
4      昼     中華  キュアフォーチュン
5      昼     和食  キュアソード
6      昼     洋食  キュアソード
7      朝     中華  キュアソード
8      朝     和食  キュアフォーチュン
9      朝     洋食  キュアフォーチュン

まず「時間帯」と「種類」に注目して下さい。「朝、昼、夜」と「和食、洋食、中華」の組み合わせが全部網羅され、3 * 3の9ケースできていることがわかると思います。ここまではいいですね?次に一緒にごはんを食べる「紫プリキュア」に注目して下さい。「キュアフォーチュン」が5ケース、「キュアソード」が4ケースできています。ちょっと待ってください。お分かりになりますでしょうか?「紫プリキュア」に関しては「2パラメータ間の組み合わせを全部網羅する」という条件は、8行目の時点でクリアしてます。なので、9行目はぶっちゃけどちらのプリキュアでも良いはずです。なぜなら、どちらだったとしても網羅率は変わらないからです。

どちらでもいいなら、なぜまこぴー(キュアソード)が選ばれていないのでしょうか?なぜまこぴーが選ばれていないのでしょうか?(怒) これは、pictが適当に選んだ運命の結果です。しかしながら、私はまこぴーを優先したいです。まこぴーの出現頻度を上げてやりましょう17

特定の値に重みをつけて、出現優先度を調整するのは簡単です。括弧と数字を使います。

pregohan2.txt
時間帯: 朝, 昼, 夜
種類: 和食, 洋食, 中華
紫プリキュア: キュアソード(10), キュアフォーチュン

「キュアソード」に(10)という数字がありますね?これが重みです。さて、このテキストから作成されたテストケースが以下になります。「キュアソード」を含んだものは4ケースしかありませんでしたが、なんと今回は6ケースになっています。「キュアフォーチュン」は、網羅率をクリアするための最小回数だけ使われています。

$ pict pregohan2.txt
時間帯  種類  紫プリキュア
夜      中華  キュアソード
夜      和食  キュアフォーチュン
夜      洋食  キュアソード
昼      中華  キュアフォーチュン
昼      和食  キュアソード
昼      洋食  キュアソード
朝      中華  キュアソード
朝      和食  キュアソード
朝      洋食  キュアフォーチュン

重みは整数値で指定できます。数が大きいほど重い(優先度が高い)です。何も指定しないと1の扱いになります。例えばキュアソードを含んだテストケース、一番最初(pregohan.txt)は4ケース、2回め(pregohan2.txt)は6ケースでしたが、間を取って5ケースくらいにしたいという場合は、下記のように2指定し、差が1になるようにしてやると良いです。

pregohan3.txt
時間帯: 朝, 昼, 夜
種類: 和食, 洋食, 中華
紫プリキュア: キュアソード(2), キュアフォーチュン

すると、結果はこうなります。

$ pict pregohan3.txt
時間帯  種類  紫プリキュア
夜      中華  キュアソード
夜      和食  キュアフォーチュン
夜      洋食  キュアソード
昼      中華  キュアフォーチュン
昼      和食  キュアソード
昼      洋食  キュアソード
朝      中華  キュアソード
朝      和食  キュアフォーチュン
朝      洋食  キュアフォーチュン

シード処理:新プリキュア途中参戦!作成済みのテストケースは……?

既に運用しているシステムに新機能が追加された時のテスト、どうしていますか?改修内容にもよりますが、きちんとテストケースを管理している方は、既存の機能のテストケースをベースにして、新機能向けのテストケースを考える方もいるんじゃないかと思います。
いずれにせよ、新機能の動作テストだけではなく、元々存在する機能のデグレが怖いので、リグレッションテストはしておきたいですね。

実は、プリキュア業界でもデグレ検知はとても重要です。プリキュアや妖精は物語の途中で増えることが多いからです。新プリキュアが途中参戦すると、新参者が水を差す形で、せっかく仲が良かったプリキュア達の関係が悪化することがよくあります。この現象をキュアデグレと言います18

具体例で見てみましょう。下記は、ドキドキプリキュアの登場人物から、プリキュア、妖精、サポート役の脇役の中から一人ずつピックアップして、トリオを作成するモデルファイルです。

dokidoki.txt
# ドキドキプリキュア達
Precure: キュアハート, キュアダイヤモンド, キュアロゼッタ, キュアソード

# 妖精達
Fairy: シャルル, ラケル, ランス, ダビィ

# サポート役の男達
Support: 岡田, セバスチャン

テストケースを作成し、その結果を一旦case_dokidoki.txtに格納します。16ケース作成されました。

Terminal
$ pict dokidoki.txt > case_dokidoki.txt
$ cat result_dokidoki
Precure             Fairy     Support
キュアソード        ダビィ    岡田
キュアソード        ランス    岡田
キュアハート        ラケル    岡田
キュアハート        ランス    岡田
キュアソード        ラケル    セバスチャン
(..中略..)

ドキドキプリキュアでは、物語の途中で新しくキュアエースとアイちゃんが仲間に加わります19。そこで、モデルファイルは下記のように修正しなければいけません。

dokidoki2.txt
# ドキドキプリキュア達(+ キュアエース)
Precure: キュアハート, キュアダイヤモンド, キュアロゼッタ, キュアソード, キュアエース

# 妖精達(+ アイちゃん)
Fairy: シャルル, ラケル, ランス, ダビィ, アイちゃん

# サポート役の男達
Support: 岡田, セバスチャン

モデルファイルが修正されてしまったので、また新しいテストケースが作られます。
こちらはcase_dokidoki2.txtに格納しましょう。

Terminal
$ pict dokidoki2.txt > case_dokidoki2.txt
$ cat case_dokidoki2.txt
Precure             Fairy       Support
キュアエース        ランス      岡田
キュアハート        ラケル      岡田
キュアハート        ランス      岡田
キュアエース        ダビィ      セバスチャン
キュアエース        ラケル      セバスチャン
(..中略..)

1回目のテスト(case_dokidoki.txt)とプリキュアと妖精が増えた2回目のテスト(case_dokidoki2.txt)にどれくらい差があるでしょう?調べみましょう。

Terminal
# 一回目のケースと二回目のケースを比較。一回目に存在した8ケースが残る。
$ cat case_dokidoki.txt case_dokidoki2.txt | sort | uniq -c | sed -n '2,$p' | grep 2
   2 キュアハート       ラケル  岡田
   2 キュアハート       ランス  岡田
   2 キュアソード       ラケル  セバスチャン
   2 キュアハート       ダビィ  セバスチャン
   2 キュアハート       シャルル        セバスチャン
   2 キュアロゼッタ     シャルル        セバスチャン
   2 キュアダイヤモンド ダビィ  岡田
   2 キュアダイヤモンド ランス  セバスチャン

1回目と2回目では8ケースがそのまま残っていました。つまり、1回目に作った16ケースのうち、半分が無くなったことになります。
皆さんは、この差分のケース数はできるだけ無くならない方が嬉しいのではないでしょうか?例えば、1回目のテストの実施中に考慮漏れが発覚してパラメータの値が増えた場合や、古いテストは既に自動化していて、せっかく自動化したものは極力残しておきたいなんて状況、結構ありそうじゃないでしょうか?

pictには、作成済みのテストケースをシード値として読込んで、できるだけそれに似たテストケースを作るためのシード処理(Seeding)という機能があります。シード処理には/e:ファイル名のオプションを指定します。このオプションで作成済みのテストケースの内容を記述したファイル(ここではシードファイルと呼びます)を指定します。例えば今回の場合、下記のようになります。

Terminal
# 2回目のモデルファイル(dokidoki2.txt)を、1回目の結果(case_dokidoki.txt)を読み込みつつ実行
$ pict dokidoki2.txt /e:case_dokidoki.txt > cat_dokidoki2-seeding

# 一回目のケースと二回目(Seeding有効)のケースを比較。一回目に存在した16ケースが残る。
$ cat case_dokidoki.txt case_dokidoki2-seeding.txt | sort | uniq -c | sed -n '2,$p' | grep 2
   2 キュアソード       ダビィ  岡田
   2 キュアソード       ランス  岡田
   2 キュアハート       ラケル  岡田
   2 キュアハート       ランス  岡田
   2 キュアソード       ラケル  セバスチャン
   2 キュアハート       ダビィ  セバスチャン
   2 キュアソード       シャルル        セバスチャン
   2 キュアハート       シャルル        セバスチャン
   2 キュアロゼッタ     ダビィ  岡田
   2 キュアロゼッタ     ラケル  岡田
   2 キュアロゼッタ     ランス  セバスチャン
   2 キュアロゼッタ     シャルル        セバスチャン
   2 キュアダイヤモンド ダビィ  岡田
   2 キュアダイヤモンド ラケル  セバスチャン
   2 キュアダイヤモンド ランス  セバスチャン
   2 キュアダイヤモンド シャルル        岡田

シード処理を使うことで、一回目に作成された16ケースが全て残りました。ケースは全てPariwiseにもなっているはずです。ただし、この機能はキッチリ使わないと意図しないケースが生まれることがあるので、下記の点に気をつけて下さい。

  • シードファイルには存在するが、読んでいるモデルファイルには存在しないパラメータがあるときは、それに該当するファイルの列が丸ごと無視される。
  • シードファイルには存在するが、読んでいるモデルファイルに存在しないパラメータ値があるときは、行ごとに無視される。
  • シードファイルに記載されているケースが、読んでいるモデルファイルの条件文と矛盾がある場合は、それは無視される。

むすび

本記事で紹介しているPICT自体は、結構前からありますが、昨年10月にオープンソースとして再公開されたようです20。Microsoft社の最近のオープン戦略のおかげかどうかは分かりませんが、MacやLinuxでも使えるので、とてもありがたいですね。

pictは紹介したとおり強力なツールですが、テクニックに溺れて、テスト自体の目的を見失ってはいけません。このような組み合わせテスト技法は、あくまで人間の要件に合わせて使いましょう。Pairwise法以外でも、不必要なパラメータを削ることを忘れてはいけません。
その辺りの話はテストがうまくいかないプロジェクトに捧ぐ、正しいテストの考え方でよくまとめられています。本記事の内容は、この記事の「統計情報に基づいて組み合わせを絞る」方法を更に深掘りしたというスタンスです。ケース数を削る考え方の1つに過ぎません。「独立している要素を間引く」ことと「重要度の低いパターンはテストしない」の2つと併せ、ぜひ本記事の内容も活用していただければと思います。
また「こんなやり方もあるよ!」「ここで言ってること間違ってるよ!」といったコメントも、していただけると嬉しい限りです。

[付録]

文字化け対策

pictコマンドですが、Linuxでの動作の場合、入力ファイルはJISコードではないといけないようです。Ubuntuだと文字化けしてしまいました。

Terminal
# 日本語を含んだファイル。
$ cat test.txt
CASE1: a,b,c
CASE2: 1, 2, 3
CASE3: あ, い

# 日本語だけ表示されない。。。
$ pict test.txt
CASE1   CASE2   CASE3
b       3
a       2
b       2
c       2
b       1
c       3
a       3
a       1
c       1

そこで、pictコマンドを少しだけ再開発しましょう。

Terminal
# ``nkf``をインストール
$ sudo apt-get install nkf

~/.zshrc (~/.bashrcでも可) に下記の関数を追加しましょう。これで、pictコマンドの入力ファイルがJIS以外のエンコードだったとしても21JISコードに変換されてきちんと処理されます。また、出力は自動的にUTF-8に変わります。既にご紹介した/o:Nなどのコマンドライン引数もちゃんと認識されます。

~/.zshrcの一部
pict() {
    _model="$1"
    shift
    _options="$*"
    /usr/local/bin/pict <(cat "$_model" | nkf --jis) $_options | nkf -w
}
$ pict test.txt
CASE1   CASE2   CASE3
b       3       あ
b       1       い
b       2       い
c       2       あ
c       1       い
a       3       い
c       3       い
a       1       あ
a       2       あ

PICT用整形用コマンドを作る

この設定おすすめなので、良ければ~/.zshrcに追加しましょう。

~/.zshrc
alias pict-format="column -s$'\t' -t | tee >(sed -n '1,1p') | sed '1,1d' | sort"

説明

このaliasで作成されたpict-formatコマンドは、2つの改善点を含んでいます。

  • pictコマンドの出力はタブ区切りです。そのままだと見にくいのでcolumnコマンドを使ってテーブル幅を揃えて見やすくできます。こんな感じに。
Terminal
$ pict test.txt | columnt -s$'\t' -t
  • pictコマンドの出力にtailとかgrepとかすると、一行目の項目名の行が消えてしまって分かりにくいです。そこで、一行目は、後続のパイプの操作で消えないようにします22。また、見やすいようにsortします。
~/.zshrc
$ pict test.txt | tee >(sed -n '1,1p') | sed '1,1d' | sort
  • 両者を合体させるとこうなります。
$ pict test.txt | column -s$'\t' -t | tee >(sed -n '1,1p') | sed '1,1d' | sort

# alias化
$ alias pict-format="column -s$'\t' -t | tee >(sed -n '1,1p') | sed '1,1d' | sort"

# 使用例 -- キュアブラックを含んだテストケースのみを抽出。見やすい。一行目の項目名はgrepの影響を受けず残る。
$ pict test.txt| pict-format | grep  キュアブラック
OS       通信キャリア  プリキュア
Android  Au            キュアブラック
Android  SoftBank      キュアブラック
iOS      DoCoMo        キュアブラック

参考


  1. 日本を代表する国民的アニメの一つ。 

  2. 基本的に、TVシリーズに出演するメインキャラクターのみを対象とします。キュアエコーやキュアフラワー、キュアゴリラ、アローハプリキュア、キュアテンダーなどのプリキュアは必須ではないとします。また、ふたりはプリキュアSplash☆Star勢は、pictのとある機能の説明のためあえて重複して数えています。シャイニールミナスやミルキィローズは正確に言うと妖精の力を使って変身をしていないため「伝説の戦士 プリキュア」かどうか微妙ですが今回は含みます。ただし夢見る乙女はみんな「プリキュア」です。 

  3. Yes! プリキュア5 GoGoの変身アイテムであるキュアモや、ハピネスチャージプリキュアが使うキュアライン、特にドキドキプリキュアが使うラブリーコミューンはスマホそのものです。小さい子供が大人の象徴だと認識するモノが使われる傾向があります。 

  4. Pairwise法はブラックボックステストを想定したものらしいので、今回はブラックボックステストを対象とします。キュアブラックボックステストです。 

  5. pictコマンドのコマンドラインオプションで区切り文字は変更できます。 

  6. ブラウザやOSの種類、ユーザの属性値やAPIのバージョンなど、テスト対象の挙動に影響する存在、くらいに捉えてもらえれば良いと思います。「テストのパラメータをどういう粒度で定義するべきか」という議論もあると思いますが、そういうお話は求めるサービスレベルによって違うのであまり議論したくないです。興味のある方は論文の方を読んでいただければいいんじゃないでしょうか。 

  7. Proceedings of the 27th NASA/IEEE Software Engineering Workshop, NASA Goddard Space Flight Center, December 2002: http://csrc.nist.gov/acts/kuhn-reilly-02.pdf 

  8. Pairwise Testing in the Real World: Practical Extensions to Test-Case Scenarios: https://msdn.microsoft.com/en-us/library/cc150619.aspx 

  9. 網羅率は、存在し得る全パターンを試した時に100%になる比率のことです。なのでPairwise法で作られるテストケースは、網羅率100%とは限らないわけです。 

  10. 一応他にも妖精の力は直接無くても変身できるプリキュアはいます。それも結構います。しかしここでは条件が複雑になるので割愛します。 

  11. 例えば、「"Windows8"と"Windows8.1"はもう面倒だからテストケース分けなくていいや!」といったような状況です。 

  12. 「ふたりはプリキュア Splash Star」の第30話以降から登場する形態。フラッピとチョッピに加えムープとフープの精霊の力を合わせて変身する月と風のプリキュア。キュアブルームはキュアブライトに、キュアイーグレットはキュアウィンディに、それぞれパワーアップする。 

  13. ここで異常系テストとは、全く意図していなかったイレギュラーなエラーで停止するパターンではなく、エラーメッセージを出すような、意図的にエラーを検知するパターンのテストとします。 

  14. 例えばAPIのレスポンスなどです。GETとPOSTを間違えて、かつパラメータの数字を間違えていたとしても、最初のレスポンスではメソッドの違いしか注意してくれないですよね。とはいえ、勿論ケースによります。分かりやすい例だと、会員登録フォームでの入力チェックなどは後者のものもあります。(例:パスワードの長さが不十分かつ、規約への同意のチェックボックスにチェックが入ってなかったら同時に警告を出す。) 

  15. /nオプションを使えば~(チルダ)以外の文字も使えます。 

  16. pictの本家のマニュアルでは「プロセッサやメモリ、HDDの組み合わせをパラメータとして網羅したけど、それらを含んだ完成したマシンとしてもテストを網羅しないと不安だよね」という場合を例に挙げています。 

  17. この重み付けをしても生成されるケースは全く変わらないかもしれません。ただし例のように、あるパラメータが変化しても網羅率が変わらない状況の時、優先度の高いパラメータが使われます。これは、特定のパラメータ値を含んだテストが比較的実施しやすいケースの場合、効率化に繋がります。例えば、IEはテスト用の設定がしにくいためできるだけ使いたくない、という場合。同じテストの網羅率であればFireFoxやChromeの優先度を上げ、IEの出現頻度を下げる、などの方法が考えつきますね。 

  18. 言いません。 

  19. ドキドキプリキュア22話で何の前触れもなく登場。敵を撃退し、主人公のマナ達を助けるものの、心の折れたマナから変身アイテムを剥ぎ取り、プリキュアの資格を剥奪。一番年下にも関わらず先輩風を吹かせるなどして、ドキドキ勢の関係を一時的にではあるが悪化させ、視聴者をドキドキさせた。典型的なキュアデグレ。 

  20. https://github.com/Microsoft/pict/commits/master Initial commit, Oct 17, 2015 

  21. nkfがエンコードを特定できればOKです。滅茶苦茶なバイナリ情報など渡したらそのまま処理されます。 

  22. sed -n '1,1p' というコマンドをサブシェルで実行して、後続のパイプで繋がったコマンドの影響は受けないようにしています。詳しくは、コマンドとコマンドをつなぐ糊で説明しています。