テストカバレッジとか

いま放り込まれているプロジェクトに適用される設計基準は結構厳し目でなのだが、どうやって守っていいのか全然わからんという感じである。


過去から流用されたコードベースがごちゃごちゃっと入り交じっていて単純な品質保証も結構難しそうなんである。コーディング規約とかテストカバレッジが厳しいところに適応してきたコードには奇妙なものも散在している。


たとえば

/**
 * bar1, bar2, bar3 はすべて
 * ・引数がなにかの条件を満たしているかどうかの判定結果を返す関数
 * ・副作用はない
 *
 * foo()は全部の条件を満たすかどうかを返す関数
 */
BOOL foo(int a) {
  BOOL ret = TRUE;

  BOOL ret1 = bar1(a);
  ret = ret && ret1;
  BOOL ret2 = bar2(a);
  ret = ret && ret2;
  BOOL ret3 = bar3(a);
  ret = ret && ret3;
  return ret;
}

こんな感じのコードがあった。


これは普通に考えれば

BOOL foo(int a) {
  BOOL ret = TRUE;
  if (!bar1(a)) { ret = FALSE; }
  if (!bar2(a)) { ret = FALSE; }
  if (!bar3(a)) { ret = FLASE; }
  return ret;
}

だし、そもそも

BOOL foo(int a) {
  return bar1(a) && bar2(a) && bar3(a);
}

でよいのではないかという気すらしてくる。元ソースを作ったやつはダメプログラマなのではないかと最初は思った。


しかし元のソースと次のソースでいわゆる条件網羅(C1カバレッジ)を通すためにどれだけのテストをしないといけないかを考えて、あえて前者にしたのではないかという気がした。最後に挙げたコードはbar1()の結果次第でbar2()やbar3()が呼び出されるかどうかが変化し、つまりbar1()の結果に応じて分岐が発生するということなのだ。真ん中のコードについてもret=FALSEが実行されるかされないかが変わるので、なんにせよbar1()やbar2()の戻り値を変えたテストが必要になる。
そうなるとbar1()やbar2()が所望の結果を返すようにmockを作らなければいけなくて、それはJavaScriptでは簡単かもしれないがCやC++では結構めんどくさい。


一方、一番最初に挙げたコードであればテストは1ケースでC1カバレッジを満たすことができる。bar1()やbar2()が何を返してきても問題ないのである。



こうしてとりあえず基準を満たしたテストを通したコードを作ることができる。しかしコードは冗長な感じになり、実行速度も落ちて、全くもって要らぬ工夫でしかないのだった。