😌

テストのカバレッジを楽して増やす検討

に公開

カバレッジを増やすにはテストを増やすのが一般的でしょうか。
テストを増やさずにカバレッジを増やせないか検討してみました。
(既にこのような考えがあれば、すでにあるよ!って言ってください)

今回考えた事:
コードにちょっとした仕掛けを入れて、if文の分岐を無理やり曲げて、C0カバレッジの%を増やしたい。

同じ実行だけど違う分岐ができないか

C++のコード、1~9までを全部通したい

#include <stdio.h>

/* ここに仕掛けを入れる予定 */

int func(int i){
  printf("func %d passed\n",i);
  return 0;
}

int mainfunc(){
  int rtn = 0;
  rtn = func(1);
  if(rtn==0){
    rtn = func(2);
    if(rtn==0){
      rtn = func(4);
    }else{
      rtn = func(5);
    }
  }else{
    rtn = func(3);
    if(rtn==0){
      rtn = func(6);
      if(rtn==0){
        rtn = func(8);
      }else{
        rtn = func(9);
      }
    }else{
      rtn = func(7);
    }
  }
  return 0;
}

int main(){
  for(int i=0;i<8;i++){
    mainfunc();
  }
  return 0;
}

基本的に成功フローを流れるものだから、
なんとかしてif文を失敗に制御できないだろうか。

作戦:if文を2回に1回強制的に失敗させる

こういうマクロを考えてみました。

//=========================================
// 基本的には成功するのだから、たまに失敗させる
#include <string>
#include <map>
bool sw(const char* func, int l){
  static std::map<std::string,int> bb;
  std::string k = func;
  k += "_" + std::to_string(l);
  if(bb.count(k)==0){
    bb[k] = 0; // init value
  }else{
    bb[k] = 1-bb[k];
  }
  //DBG//printf("L.%s %d.\n",k.c_str(),bb[k]);
  return (bb[k]==0);
}
#define EEE sw(__func__,__LINE__) &&
//=========================================

if文の関数名と行数を使ってユニークなIDを作って、mapで通過回数を管理して、
偶数回目に通過したらreturn falseするような仕掛けです。0・1を繰り返しているため。
(IDについて、ファイル名も加味すればもっとユニークでしょうか。ファイル内で閉じる仕掛けになりそうなので、今回はこれで良しとしました)

元のプログラムの方は、コンパイル前に「if(」を「if(EEE 」に置換するようにします。
(ifの前が文字列でないとか、正規表現とかを駆使する必要があると思います。置換する場合は、コメント消してインデンターとかフォーマッターとかでキレイなコードにすると事故が少なくて簡単です)

int mainfunc(){
  int rtn = 0;
  rtn = func(1);
  if(EEE rtn==0){
    rtn = func(2);
    if(EEE rtn==0){
      rtn = func(4);
    }else{
      rtn = func(5);
    }
  }else{
    rtn = func(3);
    if(EEE rtn==0){
      rtn = func(6);
      if(EEE rtn==0){
        rtn = func(8);
      }else{
        rtn = func(9);
      }
    }else{
      rtn = func(7);
    }
  }
  return 0;
}

結果・考察

1-9、全部通っているでしょうか

ifが3階層あるので、2^3で8回同じテストを流せば全部通ると思って、そうなりました。
(funcも1度通過したらprintしないようにすれば良かったです)

ifが3階層あるようなコードを書くのは稀だと思います。
if文で不正な入力や予期せぬエラーでスカッと抜けるようなコードの方が多いと思います。

if(変な入力){return 1;}
rtn = func();
if(失敗){return 2;}
rtn = func();
if(失敗){return 3;}
rtn = func();
if(失敗){return 4;}
return 0; // 0 が OK

こういう場合は、最初の1回だけ失敗する方が効率がよさそうです。

追記

・エラー埋込み法 とは違いますよね。
https://note.com/yukio_tada/n/na8c304e5fbdc
・カオスエンジニアリング とも違いますよね。

・どちらかというと スタブ ですかね
https://atgo.rgsis.com/column/about-stub/

スタブを活用することで、外部サービスの応答時間やエラーレスポンスをシミュレートし、
テストの多様性と信頼性を高めることができます。

Discussion