パルカワ2

最近はFlutterをやっています

テストデータをランダムで作る

最近モバイルアプリを作り直していて、自分が必要だなーとか安心したいなーと思う範囲でテストを書いている。 テストを書いているとテストデータを作る必要が出てくる。毎回手でテスト用のデータを作っていると大変でそんなことやってられるかという気持ちになってくるので、固定のデータを返すメソッドを作ったりする。

PickingProgressAmount newPickingProgressAmount() {
    return PickingProgressAmount(
        requested: 10,
        pickded: 0,
        ...
    );
}

こうやると色んなテストで使い回せて便利。 パターン的にはオブジェクトマザーと言うらしい。本来はクラスを作るみたいですね。

Object Mother

しかし、これをやっていると知らぬ間に様々な箇所でデータの値に依存したテストが増えていく。そうなると初期値を変更したいときに困ったりする。*1

そうなると生成される値をランダムにして自動で作られる値に依存したテストを書けないようにしちゃう?となるのだが、ランダムな値はテストの再現性が下がるのでやめましょう…と古の記憶が語りかけてくる。ランダムの値を使っているとテストが通る時と通らない時が起きる可能性があり、テストが通らないときの値がわからなかったりテスト時に再現させるのが難しいと困っちゃうということです。

ランダムの値のままテストをしたら再現性はないのはまあそうなのだが、例えば以下のように必要な値だけを固定にするやり方もある。

class Test {
  final int a;
  final int b;
  final int c;
  
  int add(int i) {
    return a + b + i;
  }

  Test copyWith(...) => ...;
}

randomTest() {
    return Test(
        randomInt(min: 0, max: 10),
        randomInt(min: 0, max: 10),
        randomInt(min: 0, max: 10),
    );
}

test('add', () {
  final t = randomTest().copyWith(a: 1, b: 2);
  expect(t.add(10), 13);
});

この実装であれば再現性はあると思われるのだが、このTestクラスを以下のように書き換えたとき、本当はaddするときにcが追加されてるのでcに対応していないテストはコケてほしい。しかし、cがランダムの値なので、cが0になったときうっかり通る。

class Test {
  final int a;
  final int b;
  final int c;
  
  int add(int i) {
    return a + b + c + i;
  }

  Test copyWith(...) => ...;
}

randomTest() {
    return Test(
        randomInt(min: 0, max: 10),
        randomInt(min: 0, max: 10),
        randomInt(min: 0, max: 10),
    );
}

test('add(cを上書きしていない)', () {
  final t = randomTest().copyWith(a: 1, b: 2);
  expect(t.add(10), 13);
});

とはいえ、先のように標準のデータを作ったときに何も知らず人間がcを0にしてしまったら結局テストは通るし、それよりは運悪くテストが成功しても何度か実行すればテストは失敗するはずのランダムのほうがよいかなと思えてきたので、ランダムの値を使っている。

また同僚からproperty based testを教えてもらいfast-checkを眺めたところ、1回のテストで何回も実行することやseedはテストの結果と一緒に表示されるとかそのあたりは安心出来そうだった。どうしても困ったらそのあたりはやってもいいかもしれないが、今のところそこまではしなくていいかなとなっている。

*1:マーティンファウラーも言っているね