テストケースを網羅的に自動生成できるツール(tdt)をTSで開発した
これで何が嬉しいのか
- ソフトウェアテストで利用される「デシジョンテーブル(参考)」の各セルに対して、期待結果を考えて手入力する作業を自動化できる。
- 人的ミスを減らせる。
- 高速化できる。
- 各キーには日本語も利用できるので、可読性を維持できる。
- Typescriptのジェネリクスにより、入力データ同士の整合性は保証され、IDEによる補完もされるため、テストコードの作成が超楽。
なぜ作ったか
自分が開発したこれまでのウェブサービスにおけるブラウザテストでは、Excelで作成したデシジョンテーブルを読ませて効率化を図っていましたが、デシジョンテーブルの作成自体が大変でした。そのうち、テーブル作成は自動化すべきと考えるようになりました。
なぜTypescriptか
はじめは、デシジョンテーブル生成ツールをjs
で開発しましたが、データの誤入力に起因するエラーが頻発し、その対応に時間がとられ、思ったほど楽になりませんでした。
Typescriptのジェネリクスを利用すれば、入力データ間の整合性を保証できると考えました。
どんなものが出力されるのか
出力形式は、オブジェクトの配列です。1つのオブジェクトが1つのテストケースに相当します。
例
[
{
'Condition.User.IsRegistered': 'True',
'Condition.User.IsAdmin': 'True',
'Condition.Device': 'Mobile',
ExpectedResult: 'Failure',
Perspective: 'Only registered users accessed from PC can access.',
ID: '1'
},
{
'Condition.User.IsRegistered': 'True',
'Condition.User.IsAdmin': 'True',
'Condition.Device': 'PC',
ExpectedResult: 'Success',
Perspective: 'Only registered users accessed from PC can access.',
ID: '2'
},
{
'Condition.User.IsRegistered': 'True',
'Condition.User.IsAdmin': 'False',
'Condition.Device': 'Mobile',
ExpectedResult: 'Failure',
Perspective: 'Only registered users accessed from PC can access.',
ID: '3'
},
{
'Condition.User.IsRegistered': 'True',
'Condition.User.IsAdmin': 'False',
'Condition.Device': 'PC',
ExpectedResult: 'Success',
Perspective: 'Only registered users accessed from PC can access.',
ID: '4'
},
{
'Condition.User.IsRegistered': 'False',
'Condition.User.IsAdmin': 'False',
'Condition.Device': 'Mobile',
ExpectedResult: 'Failure',
Perspective: 'Only registered users accessed from PC can access.',
ID: '5'
},
{
'Condition.User.IsRegistered': 'False',
'Condition.User.IsAdmin': 'False',
'Condition.Device': 'PC',
ExpectedResult: 'Failure',
Perspective: 'Only registered users accessed from PC can access.',
ID: '6'
}
]
これは、下記のデシジョンテーブルに相当します。(このMarkdown形式のテーブルも出力できます)
#1 | #2 | #3 | #4 | #5 | #6 | ||
---|---|---|---|---|---|---|---|
Condition.User.IsRegistered | True | X | X | X | X | - | - |
False | - | - | - | - | X | X | |
Condition.User.IsAdmin | True | X | X | - | - | - | - |
False | - | - | X | X | X | X | |
Condition.Device | Mobile | X | - | X | - | X | - |
PC | - | X | - | X | - | X | |
ExpectedResult | Success | - | X | - | X | - | - |
Failure | X | - | X | - | X | X | |
Any | - | - | - | - | - | - |
これらはジェネリクスにより型付けされているので、参照時のキーや値の指定ミスは実行前に検知できます。
例えば、以下のようなありえないキーについては、VSCodeなどの静的解析でエラーになります。
let isRegistered;
isRegistered = tests[0].Conditiob.User.IsRegistered; // error
isRegistered = tests[0].Condition.Usef.IsRegistered; // error
isRegistered = tests[0].Condition.User.IsRegisteref; // error
isRegistered = tests[0].Condition.User.IsRegistered; // ok
以下のようなありえない値についても同様です。
if(tests[0].Condition.User.IsRegistered === 'false'){ // error
if(tests[0].Condition.User.IsRegistered === 'False'){ // ok
さらに、VSCode等による入力補完も効くため、効率的な取扱が可能です。
テストケースの可読性を維持するためにはキー名は長くなりがちですので、これらは意外と役に立ちます。
どんなものを入力するのか
以下の4つの入力を用意します。
- 各因子の水準
- 各因子のデフォルト値
- 各因子の値同士のありえない組み合わせ
- テストの観点
1. 各因子の水準
各因子(期待結果も含む)の水準、即ち、とりうる値です。
唯一、ジェネリクスによる型付けがされていない入力であり、末端要素が「文字列の配列」であるオブジェクトでさえあれば、どのような構造をとっても構いません。
例:
const domain = {
"Condition":{
"User":{
"IsRegistered":[
"True",
"False"
],
"IsAdmin":[
"True",
"False"
]
},
"Device":[
"Mobile",
"PC"
]
},
"ExpectedResult":[
"Success",
"Failure",
"Any"
]
} as const;
type ExampleDomain = typeof domain;
2. 各因子のデフォルト値
全因子のデフォルト値です。因子の値が明示されない場合にのみ、参照されます。各因子の水準からひとつ値を選択すればよいです。
例:
const defaults : Defaults<ExampleDomain> = {
"Condition":{
"User":{
"IsRegistered" : "True",
"IsAdmin" : "False"
},
"Device":"Mobile"
},
"ExpectedResult":"Any"
} as const;
なお、各因子の水準もとにジェネリクスで型付けされているため、不正な指定をすると下記のようにエラーとなります。
3. 各因子の値同士のありえない組み合わせ
因子のありえない値の組み合わせです。ここで指定した組み合わせに該当するものは、出力結果から除外されます。下記の例では1つしか指定していませんが、組み合わせは複数指定できます。
例:
const exclusions:Exclusions<ExampleDomain> = [
{
"Condition.User.IsRegistered" : "False",
"Condition.User.IsAdmin" : "True"
}
] as const;
なお、各因子の水準をもとにジェネリクスで型付けされているため、不正な指定をすると下記のようにエラーとなります。
4. テストの観点
一般的にテストケースは「何をチェックすべきか」という「観点」に基づいて定義されます。このツールでは、観点を指定することでテストケースの生成方法を指定します。
-
title
:観点のタイトル。 -
constants
:組み合わせを網羅するときに定数としたい因子を指定します。具体的な値も指定します。 -
variables
:組み合わせを網羅するときに変数としたい因子を指定します。 -
expect
:各因子の値に応じて期待結果を判断し、指定します。
例では、1つしか指定していませんが、観点は複数指定できます。
例:
const perspectives:Perspectives<ExampleDomain> = [
{
"title": "Only registered users accessed from PC can access.",
"constants": {},
"variables": [
"Condition.User.IsRegistered",
"Condition.User.IsAdmin",
"Condition.Device",
],
"expect": (test:Test<ExampleDomain>)=>{
if(
test["Condition.User.IsRegistered"] === "True" &&
test["Condition.Device"] === "PC"
){
test["ExpectedResult"] = "Success";
}else{
test["ExpectedResult"] = "Failure";
}
return test
},
}
] as const;
なお、各因子の水準をもとにジェネリクスで型付けされているため、不正な指定をすると下記のようにエラーとなります。
テストケース数の爆発を抑える仕組み
因子数や水準数が増えると、指数関数的に組み合わせが増えていくため、すべてテストするのは現実的ではない場合があります。
本ツールでは、CoveringArrayの考え方におけるstrength
を観点ごとに指定できるので、比較的有効性の劣るテストケースを排除し、合計のテストケース数を削減することができます。
strength
は、「2」から「variablesの数」までの自然数です。小さいほど、テストケース数は小さくなります。「2」の場合は、オールペア法と等価となり(おそらく)、「variablesの数」の場合は指定しない場合と同様、全網羅となります。
どこで公開しているのか
今後の予定
実は、現状のものがほぼできあがったあとで、microsoftのPICTというツールや、TableDrivenTests
という文化の存在を知ったので、今後はこれらに馴染みのあるユーザでも利用しやすいよう、適宜仕様を変更する予定です。
また、テストをサポートするツールなのに自身に対するテストコードがまだ用意できていないため、勉強しつつ対応する予定です。
Discussion