テストに関連するパラメータが複数あると、その組み合わせの数が膨れ上がる。ただし、あるパラメータがある値の場合は別のパラメータがどんな値であってもテスト結果には影響しない、ということがある。
こうしたどれでもいいパラメータの値を扱う方針は3つあると思う。
- どれか1つの値に固定してテストする
- とりうる値すべてをテストする
- とりうる値から1つランダムに選んでテストする
例: ユーザー登録
emailとpasswordがどちらも入力されていれば成功、いずれかが入力されていなければ失敗、という単純化した例で考えてみる。
password | 結果 | |
---|---|---|
有 | 有 | 成功 |
有 | 無 | 失敗 |
無 | 有 | 失敗 |
無 | 無 | 失敗 |
この場合、emailが入力されていない場合はpasswordがどの値であっても失敗するので、passwordの値をどう扱うかそれぞれの方針で考えてみる。
例としてRubyとRSpecを使うけど、言語とフレームワークには依存しない。
1. どれか1つの値に固定してテストする
describe "POST /users" do
context "when email is empty" do
let(:email) { nil }
# passwordの値を固定する
let(:password) { "password" }
it "fails to create a user"
end
end
- pros: 組み合わせの数を抑えられる。実行時間が短くなるし、テストコードも読みやすくなる。
- cons: passwordの値が空でも結果が変わらないというのが実装者の勘違いだった場合、テストすべきケースを取りこぼすことになる。
2. とりうる値すべてをテストする
describe "POST /users" do
context "when email is empty" do
let(:email) { nil }
# passwordがとりうる値どちらもテストする
context "and password is empty" do
let(:password) { nil }
it "fails to create a user"
end
context "but password isn't empty" do
let(:password) { "password" }
it "fails to create a user"
end
end
end
- pros: 上記のようなテストケースのとりこぼしが少なくなる。それでもとりうる値が網羅できていなければとりこぼしは生じうる。
- cons: 組み合わせの数が膨れ上がる。今回のような単純化したケースでは問題にはならないものの、現実にははるかに多くのパラメータととりうる値の組み合わせが存在する。その分、実行時間が伸び、テストコードも複雑になる。
consの補足として、現実世界ではすべての組み合わせを網羅できず、とりうる値の一部をテストすることが多いと思う。その場合はprosで上げたような利点も部分的にしか享受できないことになる。
3. とりうる値から1つランダムに選んでテストする
describe "POST /users" do
context "when email is empty" do
let(:email) { nil }
# passwordの値をとりうる値からランダムに選ぶ
let(:password) { [nil, "password"].sample }
it "fails to create a user"
end
end
- pros: 組み合わせの数を抑えられる。実行するテストケースは1つのみなので1.と同じ。
- pros: CIで継続的にテストを実行することでとりうる値の組み合わせを網羅でき、上記のようなテストケースのとりこぼしが少なくなる。
- cons: 予期しない形でテストが失敗することが出てくるため、チームに混乱をもたらすかもしれない。例えば、自分の変更とは無関係な場所でテストが落ちることがあると、誰がそのテストをパスさせるかコミュニケーションが必要になる。
補足としては、テストで乱数を使うと再現できないのでは?というツッコミが考えられる。テスティングフレームワークによるかもしれないが、失敗したときのシード値を再利用することで再現できるため、この点は特に問題はないと思う。
個人的見解
個人的にはまず3.を採用したいと考える。その上で開発上の懸念が出てくるようであれば1.を採用する。2.は現実的にはconsがprosを上回っていると感じるため、採用しないだろう。