要約
- PICTは、コマンドラインツールであり、独自の文法も持っているので使いにくい
- covertableならば、ペアワイズ法によるテストデータの生成を手軽に実現できる
想定する読者
- ペアワイズ法の原理をなんとなくは理解している
- ペアワイズ法をテストデータの生成に利用したい
この記事を読む利点
- 学習コストが高いPICTよりも迅速にペアワイズ法を体感できる
- ペアワイズ法によるテストデータ生成をmocha+chaiなどの自動テスト環境に簡便に導入できる
記事の目的
ソフトウェアテストにおいてテストデータの網羅性を高めようとすると、値の組合せ爆発という問題に直面します。この問題の解決策の1つとしては、ペアワイズ法(オールペア法)によってテストデータの個数を効率的に絞り込む方法があります。
しかし、この記事の執筆時点で、Qiitaを「ペアワイズ」などのキーワードで検索し[1]た結果、得られた記事のほとんどは以下のいずれかでした:
- ペアワイズ法の原理の説明
- テストデータ生成ツールPICTの使用方法の解説
このうち、PICTは、コマンドラインツールであり、制約条件を指定するための独自の文法を持っています。:
IF [File system] = "FAT" THEN [Size] <= 4096;
IF [File system] = "FAT32" THEN [Size] <= 32000;
――引用元: [2]
こうした特徴は、筆者にとっては使いにくい印象でした。また、筆者は、個人開発プロジェクトでmocha+chaiを自動テスト環境として採用しており、この環境で簡便に利用できるペアワイズ法ライブラリを探索した結果、covertableが最も使いやすいと判断しました。
ところが、「covertable」でQiitaを検索しても解説記事が見当たらず[3]、ドキュメントも英語のみ[2]で日本人開発者には参入障壁が高い状況でした。
そこで、この記事では、covertableを用いたペアワイズ法によるテストデータの効率的な生成方法を解説します。
covertableの使用手順
では、早速、covertableを使っていきましょう。
1. covertableの導入
ライブラリ名: covertable
URL: https://www.npmjs.com/package/covertable
npm install --save-dev covertable
2. make関数のインポートと呼出し
2.1. 各因子の値を2次元配列で渡す場合
コードとその実行結果は、以下の通りです。
import { make } from "covertable";
const foo = [true, false];
const bar = [2, 3, 5];
const baz = ["e", "t", "o", "i", "n"];
const qux = make([foo, bar, baz]);
console.log("テストデータ: ")
console.log(qux);
console.log("\nテストデータの個数の比較: ")
console.log(` 全網羅: ${foo.length * bar.length * baz.length}通り`);
console.log(` ペアワイズ法: ${qux.length}通り`);
テストデータ:
[
[ false, 3, 'e' ], [ true, 3, 'o' ],
[ false, 2, 't' ], [ false, 5, 'i' ],
[ true, 5, 't' ], [ true, 2, 'e' ],
[ false, 2, 'o' ], [ true, 5, 'e' ],
[ false, 5, 'n' ], [ true, 3, 'i' ],
[ true, 5, 'o' ], [ true, 2, 'i' ],
[ true, 3, 'n' ], [ true, 3, 't' ],
[ true, 2, 'n' ]
]
テストデータの個数の比較:
全網羅: 30通り
ペアワイズ法: 15通り
2.2. 各因子の値をオブジェクトで渡す場合
コードとその実行結果は、以下の通りです。
import { make } from "covertable";
const foo = [true, false];
const bar = [2, 3, 5];
const baz = ["e", "t", "o", "i", "n"];
const qux = make({ foo, bar, baz });
console.log("テストデータ: ");
console.log(qux);
console.log("\nテストデータの個数の比較: ");
console.log(` 全網羅: ${foo.length * bar.length * baz.length}通り`);
console.log(` ペアワイズ法: ${qux.length}通り`);
テストデータ:
[
{ foo: false, baz: 'e', bar: 3 },
{ bar: 3, baz: 'o', foo: true },
{ foo: false, baz: 't', bar: 2 },
{ bar: 5, baz: 'i', foo: false },
{ bar: 5, baz: 't', foo: true },
{ foo: true, bar: 2, baz: 'e' },
{ foo: false, baz: 'o', bar: 2 },
{ bar: 5, baz: 'e', foo: true },
{ bar: 5, baz: 'n', foo: false },
{ bar: 3, baz: 'i', foo: true },
{ bar: 5, baz: 'o', foo: true },
{ bar: 2, baz: 'i', foo: true },
{ bar: 3, baz: 'n', foo: true },
{ bar: 3, baz: 't', foo: true },
{ bar: 2, baz: 'n', foo: true }
]
テストデータの個数の比較:
全網羅: 30通り
ペアワイズ法: 15通り
3. 制約条件の追加
制約条件は、make関数の第2引数に指定します。
指定の仕方は、preFilterとpostFilterがあります。
これらは、フィルタリングするタイミングのみが異なります。
それによって、結果も以下のように異なります。
3.1. preFilter
コードとその実行結果は、以下の通りです。
import { make } from "covertable";
import { SuggestRowType } from "covertable/dist/types";
const foo = [true, false];
const bar = [2, 3, 5];
const baz = ["e", "t", "a", "o", "i"];
const factors = { foo, bar, baz };
const qux = make(factors, {
// fooが偽に等しく、bazがiに等しいケースのみ抽出する。
preFilter: (row: SuggestRowType<typeof factors>) =>
row.foo === false && row.baz === "i",
});
console.log(qux);
[
{ foo: false, bar: 3, baz: 'i' },
{ bar: 5, baz: 'i', foo: false },
{ bar: 2, baz: 'i', foo: false }
]
3.2. postFilter
コードとその実行結果は、以下の通りです。
import { make } from "covertable";
import { SuggestRowType } from "covertable/dist/types";
const foo = [true, false];
const bar = [2, 3, 5];
const baz = ["e", "t", "a", "o", "i"];
const factors = { foo, bar, baz };
const qux = make(factors, {
// fooが偽に等しく、bazがiに等しいケースのみ抽出する。
postFilter: (row: SuggestRowType<typeof factors>) =>
row.foo === false && row.baz === "i",
});
console.log(qux);
[ { bar: 5, baz: 'i', foo: false } ]
考察:実際のユースケース
実際の使用場面を考えてみます。
ここでは、数学の定理「相加平均 >= 相乗平均」をプログラミングで表現し、それをペアワイズ法でテストする、という状況を想定しています。
コードとその実行結果は、以下の通りです。
import { make } from "covertable";
// 3数の相加平均を計算する関数
const arithmeticMean = (a: number, b: number, c: number) => {
return (a + b + c) / 3;
};
// 3数の相乗平均を計算する関数
const geometricMean = (a: number, b: number, c: number) => {
return Math.cbrt(a * b * c);
};
// 0から9までの整数の3つの組合せをペアワイズ法で生成する。
const numZeroToNine = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const testData = make({ a: numZeroToNine, b: numZeroToNine, c: numZeroToNine });
console.log("テストデータの個数: ");
console.log(` 全網羅: ${Math.pow(numZeroToNine.length, 3)}通り`);
console.log(` ペアワイズ法: ${testData.length}通り`);
// テストを実施する。
console.log("\nテスト結果: ");
testData.forEach((nums: { a: number; b: number; c: number }) => {
const { a, b, c } = nums;
const aMean = arithmeticMean(a, b, c);
const gMean = geometricMean(a, b, c);
const isGreaterEqual = aMean >= gMean;
console.log(`3数: (${a}, ${b}, ${c}), 命題: ${isGreaterEqual}`);
console.assert(isGreaterEqual, "仮説に反例が見つかりました。");
});
テストデータの個数:
全網羅: 1000通り
ペアワイズ法: 111通り
テスト結果:
3数: (9, 1, 6), 命題: true
3数: (4, 6, 7), 命題: true
3数: (7, 7, 7), 命題: true
3数: (7, 1, 4), 命題: true
3数: (5, 4, 7), 命題: true
3数: (5, 5, 6), 命題: true
3数: (5, 8, 0), 命題: true
3数: (6, 9, 9), 命題: true
3数: (9, 2, 7), 命題: true
3数: (0, 5, 8), 命題: true
3数: (1, 5, 4), 命題: true
3数: (2, 9, 6), 命題: true
3数: (0, 3, 9), 命題: true
3数: (4, 1, 5), 命題: true
3数: (1, 6, 5), 命題: true
3数: (6, 6, 6), 命題: true
3数: (4, 5, 3), 命題: true
3数: (1, 9, 2), 命題: true
3数: (9, 0, 2), 命題: true
3数: (0, 7, 0), 命題: true
3数: (8, 0, 1), 命題: true
3数: (3, 2, 5), 命題: true
3数: (7, 5, 5), 命題: true
3数: (4, 2, 8), 命題: true
3数: (9, 4, 5), 命題: true
3数: (3, 3, 1), 命題: true
3数: (5, 0, 5), 命題: true
3数: (2, 8, 2), 命題: true
3数: (4, 7, 2), 命題: true
3数: (2, 4, 1), 命題: true
3数: (1, 1, 8), 命題: true
3数: (2, 6, 4), 命題: true
3数: (6, 0, 7), 命題: true
3数: (7, 0, 9), 命題: true
3数: (3, 0, 4), 命題: true
3数: (9, 8, 9), 命題: true
3数: (1, 4, 9), 命題: true
3数: (6, 2, 4), 命題: true
3数: (4, 3, 0), 命題: true
3数: (5, 3, 3), 命題: true
3数: (9, 6, 1), 命題: true
3数: (3, 1, 9), 命題: true
3数: (2, 0, 8), 命題: true
3数: (8, 9, 3), 命題: true
3数: (6, 7, 5), 命題: true
3数: (2, 2, 9), 命題: true
3数: (3, 8, 6), 命題: true
3数: (2, 1, 3), 命題: true
3数: (9, 3, 8), 命題: true
3数: (3, 7, 3), 命題: true
3数: (0, 8, 5), 命題: true
3数: (3, 6, 2), 命題: true
3数: (7, 9, 8), 命題: true
3数: (8, 3, 7), 命題: true
3数: (1, 7, 1), 命題: true
3数: (8, 7, 9), 命題: true
3数: (8, 4, 2), 命題: true
3数: (6, 4, 8), 命題: true
3数: (5, 6, 8), 命題: true
3数: (5, 1, 2), 命題: true
3数: (9, 9, 0), 命題: true
3数: (0, 9, 4), 命題: true
3数: (2, 5, 0), 命題: true
3数: (0, 0, 3), 命題: true
3数: (6, 5, 2), 命題: true
3数: (6, 1, 0), 命題: true
3数: (7, 6, 0), 命題: true
3数: (1, 3, 6), 命題: true
3数: (7, 2, 1), 命題: true
3数: (8, 8, 8), 命題: true
3数: (8, 2, 5), 命題: true
3数: (4, 3, 4), 命題: true
3数: (6, 8, 3), 命題: true
3数: (7, 4, 6), 命題: true
3数: (4, 8, 1), 命題: true
3数: (9, 7, 4), 命題: true
3数: (2, 9, 5), 命題: true
3数: (8, 5, 6), 命題: true
3数: (2, 7, 8), 命題: true
3数: (3, 9, 7), 命題: true
3数: (1, 8, 7), 命題: true
3数: (0, 1, 1), 命題: true
3数: (5, 8, 4), 命題: true
3数: (8, 1, 7), 命題: true
3数: (8, 6, 0), 命題: true
3数: (0, 6, 2), 命題: true
3数: (1, 0, 0), 命題: true
3数: (1, 2, 0), 命題: true
3数: (9, 5, 9), 命題: true
3数: (1, 4, 3), 命題: true
3数: (4, 6, 9), 命題: true
3数: (0, 2, 6), 命題: true
3数: (5, 9, 1), 命題: true
3数: (8, 4, 4), 命題: true
3数: (6, 5, 1), 命題: true
3数: (9, 2, 3), 命題: true
3数: (3, 4, 0), 命題: true
3数: (5, 7, 9), 命題: true
3数: (2, 5, 7), 命題: true
3数: (7, 3, 2), 命題: true
3数: (0, 4, 7), 命題: true
3数: (4, 0, 6), 命題: true
3数: (3, 5, 8), 命題: true
3数: (6, 3, 5), 命題: true
3数: (7, 6, 3), 命題: true
3数: (4, 4, 0), 命題: true
3数: (7, 8, 0), 命題: true
3数: (4, 9, 0), 命題: true
3数: (5, 2, 2), 命題: true
3数: (0, 7, 6), 命題: true
3数: (2, 3, 0), 命題: true
結論
covertableならば、ペアワイズ法によるテストデータの生成を手軽に実現できます。
参考文献
- [1]:
- [2]:
- [3]: