while を単体テストする

興味深いポストを見た。曰く:

while 文を「単体テスト」することはできるか。できるならどのように行うか。while 文は条件式 b と文 S を使ってたとえば while b do S のようになる。この文をテストするのではなく、b や S を除いた while という「単体」の働きをテストする。

https://x.com/hatsugai/status/1774878865279869155

単体テストでテストできる対象は「評価できる値」で「比較できるもの」となるため、 while そのものをテストすることは難しい。(単体テストが `Assert.Equals` 的な(結果の)値の比較をするツールだという前提で)
while 文の while は、単体で値を持つものではないからである。

ただ While 関数を実装することで間接的に確かめることはできそう。ざっくりとこんな感じ:

While(b, S): void { while (b) S; }

(一般的な)手続き型の言語だと b や S は呼び出し時に評価されてしまうため、 While 関数内で(必要に応じて何度も)呼び返せるような関数である必要があるだろう。つまり:

While(b: void -> bool, S: void -> void): void { while (b()) S(); }

ここまで準備できれば、あとはテストケースを考えるターン。

評価するたびに bool の値を返す関数 b と、呼びだすことができる(値を戻さない)関数 S とのふたつを受けとり、 b の評価結果が偽になるまで S を呼びだすこと。
単体テストの文脈でなら、以下3つくらいのケースを作るだろうか。

  1. b を、偽を返す関数で与えたときに S が呼び返されないこと

  2. b を、真、続いて偽を返す関数で与えたときに S が一度だけ呼び返されること

  3. b を、真、真、偽と返す関数で与えたときに S が二度呼び返されること

ただし(単体テストが常にそうであるように)対象の完全な性質はテストで確認できない。今回で言えば b の評価結果が「偽になるまで S を呼びだすこと」は確かめられない。(偽にならない b を与えるとテストが停止しなくなる)

先の3つのテストケースは(疑似コードだけれど)、わたしなら次のように実装するだろう。

While(b: void -> bool, S: void -> void): void { while (b()) S(); }

test_While_returns_control_without_calling_S_if_b_returns_false(): void {
  var count = 0;
  var b = () => false;
  var S = () => count += 1;
  While(b, S);
  Assert.True(count == 0);
}

test_While_calls_S_once_if_b_yields_true_and_then_false(): void {
  var count = 0;
  var b = () { yield true; yield false; }
  var S = () => count += 1;
  While(b, S);
  Assert.True(count == 1);
}

test_While_calls_S_two_times_if_b_yields_true_true_and_then_false(): void {
  var count = 0;
  var b = () { yield true; yield true; yield false; }
  var S = () => count += 1;
  While(b, S);
  Assert.True(count == 2);
}


この記事が気に入ったらサポートをしてみませんか?