CodingFirst

C言語、Perl、JavaScript、最近はPythonも。出来上がったものより、プログラムを書くことが好き。あと、スイーツ。

スポンサーサイト

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

GCCでC言語の分岐カバレッジを試す。思ったのとちょっと違った。

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

gcov(gcc)のカバレッジにある "Branchs executed" が
どう計測されるのかを試した。

まず、"Branches executed"について。
分岐網羅の事で、条件文の各条件をどれだけ実行したかで、C1のこと。
分岐網羅の解釈に揺れがあるようで、「条件をただ実行」とか「条件の分岐方向それぞれを実行」などがあるが、
gcovは「条件をただ実行」の意で計測していた。

C言語でソースを作成し、以下のように実行した。

$ gcc -g --coverage gcov-test-branch.c && ./a.out && gcov -bc gcov-test-branch.c && cat gcov-test-branch.c.gcov
P
P
P
P
P
File 'gcov-test-branch.c'
Lines executed:90.48% of 42
Branches executed:100.00% of 10
Taken at least once:60.00% of 10
Calls executed:81.25% of 16
gcov-test-branch.c: 'gcov-test-branch.c.gcov' を作成しています

作成された gcovファイルを見る。冒頭と、main関数付近。

        -:    0:Source:gcov-test-branch.c
        -:    0:Graph:gcov-test-branch.gcno
        -:    0:Data:gcov-test-branch.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:

function main called 1 returned 100% blocks executed 100%
        1:   51:int main(int c,char*v[]){
        1:   52:  branch1_ok(0);
call    0 returned 1
        1:   53:  branch1_ok(1);
call    0 returned 1
        1:   54:  branch2_not(0);
call    0 returned 1
        1:   55:  branch3_not(0);
call    0 returned 1
        1:   56:  branch4_ok(0);
call    0 returned 1
        1:   57:  branch5_ok(0);
call    0 returned 1
        1:   58:  branch6_ok(0);
call    0 returned 1
        1:   59:  branch7_ok(0);
call    0 returned 1
        1:   60:  branch8_ok(0);
call    0 returned 1
        1:   61:  return 0;
        -:   62:}

branch1_ok()から見ていく

function branch1_ok called 2 returned 100% blocks executed 100%
        2:    3:void branch1_ok(int i){
        2:    4:  if(i==0||i>=0){
branch  0 taken 1 (fallthrough)
branch  1 taken 1
branch  2 taken 1 (fallthrough)
branch  3 taken 0
        2:    5:    puts("P");
call    0 returned 2
        -:    6:  }
        2:    7:}

branch の後ろに "never executed" がなく、パス(全網羅)と判定されている。
条件(分岐)は i==0 と、i>=0 で、main関数から i=0, i=1 でコールしてるので、
この判定は true にしかならない。
したがって、条件がtrue/falseによらず実行されていればパス(全網羅)扱いになると言える

function branch2_not called 1 returned 100% blocks executed 100%
        1:    8:void branch2_not(int i){
        -:    9:  if(0){
        -:   10:    puts("P");
        -:   11:  }
        1:   12:}

if(0)は分岐とすらみなさない。

function branch3_not called 1 returned 100% blocks executed 100%
        1:   13:void branch3_not(int i){
        -:   14:  int j;
        1:   15:  j=(i==0)?0:1;
        1:   16:}

3項演算子も同様に、分岐とみなさない。

function branch4_ok called 1 returned 100% blocks executed 67%
        1:   17:void branch4_ok(int i){
        1:   18:  switch(i){
branch  0 taken 1
branch  1 taken 0
        -:   19:    case 0:
        1:   20:      puts("P");
call    0 returned 1
        1:   21:      break;
        -:   22:    default:
    #####:   23:      puts("N");
call    0 never executed
    #####:   24:      break;
        -:   25:  }
        1:   26:}

switchは分岐とみなす。
ただし、if文同様にtrue/falseは関係しない。

function branch5_ok called 1 returned 100% blocks executed 60%
        1:   27:void branch5_ok(int i){
        -:   28:  int j;
        1:   29:  for(j=0;j<i;j++){
branch  0 taken 0
branch  1 taken 1 (fallthrough)
    #####:   30:    puts("N");
call    0 never executed
        -:   31:  }
        1:   32:}

forも同様。分岐にはなる

function branch6_ok called 1 returned 100% blocks executed 75%
        1:   33:void branch6_ok(int i){
        2:   34:  while(i>0){
branch  0 taken 0
branch  1 taken 1 (fallthrough)
    #####:   35:    puts("N");
call    0 never executed
        -:   36:  }
        1:   37:}
function branch7_ok called 1 returned 100% blocks executed 100%

whileも同様。分岐にはなる

        1:   38:void branch7_ok(int i){
        -:   39:  do{
        1:   40:    puts("P");
call    0 returned 1
        1:   41:    break;
        -:   42:  }while(i==0);
        1:   43:}

途中で break した場合、if(0)と同様に whileの行は分岐とみなされない

        -:   44:#define b8(i) if(i==0||i==1){\
        -:   45:  puts("P");\
        -:   46:  }
function branch8_ok called 1 returned 100% blocks executed 100%
        1:   47:void branch8_ok(int i){
        1:   48:  b8(0);
call    0 returned 1
        1:   49:}
        -:   50:

マクロにすると分岐とみなさなくなる。これは意外。

gcovのブランチカバレッジは単純に条件を実行したかだった。
意味はわかりやすかったけど、コードカバレッジとしては使いにくいと感じる。



実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)
(2006/07)
岸 哲夫

商品詳細を見る


テスト駆動開発入門テスト駆動開発入門
(2003/09)
ケント ベック

商品詳細を見る

もともとのソースコードは続きに

続きを読む »

スポンサーサイト

GCCでC言語のラインカバレッジ。ちょっと怖いぞ。

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

gcc(gcov)のラインカバレッジを試した(C言語)。

そもそも、ラインカバレッジに関しては、
「カバレッジとしてはちょっと弱い」程度の知識しかなかった。
だから、どう弱いのか実感したかった。
ただし、やって見た結果は、たしかにラインのカバレッジだ!...ではあったけどね...

まず、おさらい。カバレッジの測り方。
作成したソース gcov-test-line.c をコンパイル、実行して、gcovで集計する。

$ gcc -g --coverage gcov-test-line.c && ./a.out && gcov gcov-test-line.c
P
P
P
P
P
File 'gcov-test-line.c'
Lines executed:93.75% of 64
gcov-test-line.c: 'gcov-test-line.c.gcov' を作成しています

できた gcov-test-line.c.gcov を見る。

        -:    0:Source:gcov-test-line.c
        -:    0:Graph:gcov-test-line.gcno
        -:    0:Data:gcov-test-line.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:

        1:   74:int main(int c,char*v[]){
        1:   75:  line1_ok(0);
        1:   76:  line2_ok(0);
        1:   77:  line3_ng(0);
        1:   78:  line4_ok(0);
        1:   79:  line5_ok(0);
        1:   80:  line6_ok(0);
        1:   81:  line7_ng(0);
        1:   82:  line8_ok(0);
        1:   83:  line9_ng(0);
        1:   84:  line10_ok(0);
        1:   85:  line11_ok(0);
        1:   86:  line12_ok(0);
        1:   87:  line13_ok(0);
        1:   88:  return 0;
        -:   89:}

冒頭とmain関数付近。特になし。

        1:    7:void line1_ok(int i){
        1:    8:  if(i==0||i==1){
        1:    9:    puts("P");
        -:   10:  }
        1:   11:}

複数の条件があり、実行されない条件(i==1)がある場合。
これはラインカバレッジとしては、OKと判定される。
やはり、文字通り、行をちょっとでも実行したらOKのようだ。

        1:   12:void line2_ok(int i){
        1:   13:  if(i==2){puts("N");}
        1:   14:}
        1:   15:void line3_ng(int i){
        1:   16:  if(i==2){
    #####:   17:    puts("N");
        -:   18:  }
        1:   19:}

if文の条件不成立の場合。
13行目のように 1行でまとめてしまえば OK。
複数行にすると実行されない扱いになる。

        1:   20:void line4_ok(int i){
        -:   21:  if(0){
        -:   22:    puts("N");
        -:   23:  }
        1:   24:}

意外にも if(0)の場合はラインカバレッジは OK。
特に最適化をかけたつもりはないので、明らかに実行されないパスは
OKという事なのだろう。

        1:   25:void line5_ok(int i){
        -:   26:  int j;
        1:   27:  j=(i==0)?0:1;
        1:   28:}

3項演算子による代入。
(i==0)のフォルスパスが実行されないが、ラインカバレッジ的には OK。
だんだんノリがわかってきた。

        1:   29:void line6_ok(int i){
        1:   30:  switch(i){
        1:   31:    case 0:puts("P");break; default:puts("N");break;
        -:   32:  }
        1:   33:}
        1:   34:void line7_ng(int i){
        1:   35:  switch(i){
        -:   36:    case 0:
        1:   37:      puts("P");
        1:   38:      break;
        -:   39:    default:
    #####:   40:      puts("N");
    #####:   41:      break;
        -:   42:  }
        1:   43:}

switch - caseで実行されないパスがある場合。
1行にまとめてしまえば OK。

        1:   44:void line8_ok(int i){
        -:   45:  int j;
        1:   46:  for(j=0;j<i;j++){puts("N");}
        1:   47:}
        1:   48:void line9_ng(int i){
        -:   49:  int j;
        1:   50:  for(j=0;j<i;j++){
    #####:   51:    puts("N");
        -:   52:  }
        1:   53:}
        1:   54:void line10_ok(int i){
        1:   55:  while(i>0){puts("N");}
        1:   56:}

forもwhilieも同じ。1行ならOK

        1:   57:void line11_ok(int i){
        1:   58:  while(i>=0){
        1:   59:    puts("P");
        1:   60:    break;
        -:   61:    puts("N");
        -:   62:  };
        1:   63:}
        1:   64:void line12_ok(int i){
        -:   65:  do{
        1:   66:    puts("P");
        1:   67:    break;
        -:   68:    puts("N");
        -:   69:  }while(++i>=0);
        1:   70:}

whileやdo-whileの途中で強制breakした場合。
break以降の処理は通過しないが、ラインカバレッジは OK。

        -:    3:#define m1(i) if(i==3){\
        -:    4:  puts("N");\
        -:    5:  }
        -:    6:

        1:   71:void line13_ok(int i){
        1:   72:  m1(i);
        1:   73:}

複数行なマクロの場合。
連結してるから、ラインカバレッジは OK なのだろう。

以上から、
gcc(gcov)のラインカバレッジは確かにラインのカバレッジだった。
とにかく行をまとちゃえば OK と考えていいだろう。

実際、テストにたっぷり時間をかけられなかったり、
仕様変更が頻繁におきたりするときには
これくらいの粒度で丁度よいかなぁと思う。

だけど、これで慣れてしまうのは怖い。
なまじデータが計測できてるだけにこれ以上を目指さなくなるし、
カバレッジを上げる事が目的になって、かえってソースコードの品質が落ちてしまうような
そんな気がしてならない。


実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)
(2006/07)
岸 哲夫

商品詳細を見る

ソフトウェアテスト入門 押さえておきたい<<要点・重点>>ソフトウェアテスト入門 押さえておきたい<<要点・重点>>
(2008/04/11)
ソフトウェア・テストPRESS編集部

商品詳細を見る

続きを読む »


GCCでC言語のコードカバレッジを計測する

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

GCCC言語のコードカバレッジを計測する手順をまとめる。

gcc は標準的に gcov というカバレッジユーティリティがついていて、
下記の3ステップで簡単にカバレッジ計測できるようになっている。

  1. プログラムを gccでカバレッジ計測オプション付きでコンパイル
  2. プログラムを実行する
  3. gcovでカバレッジの集計する

まず、コンパイル。
gccでコンパイル時にカバレッジ計測オプション、
--fprofile-arcs -ftest-coverage -lgcov をつける。
でも、最近のバージョンでは、 --coverage を付けるだけになり、より簡単になった。
(ちなみにシノニム)。

例)
$ gcc -fprofile-arcs -ftest-coverage -lgcov source.c
or
$ gcc --coverage source.c

次に、プログラムの実行。
実行すると、カバレッジログがオブジェクトと同じディレクトリに記録される。
ファイルの拡張子が .gcno と .gcda(古いと .gcna だったかな)。
便利なのは、
プログラムを複数回実行したい場合。
単に何度も実行すれば良い。
カバレッジログは、実行ごとに上記ファイルに累積されていく。

最後にカバレッジの集計(今回はラインカバレッジのみ)。
gcov で集計したいソースコードのファイル名を指定する。

例)
$ gcc --coverage source.c
$ ./a.out
$ gcov source.c
File 'source.c'
Lines executed:75.00% of 4
source.c: 'source.c.gcov' を作成しています

もし、オブジェクトファイルが別ディレクトリにある場合。
gcov を -o DIR でディレクトリ指定する。

例)
$ gcc -c --coverage -o obj/source.o source.c
$ gcc --coverage obj/source.o
$ ./a.out
$ gcov -o obj source.c
File 'source.c'
Lines executed:75.00% of 4
source.c: 'source.c.gcov' を作成しています

gcov で集計した結果は、標準出力されるんだけど、
詳細は .gcov の拡張子で出力されるので、エディタなどで見る。

$ cat source.c.gcov 
        -:    0:Source:source.c
        -:    0:Graph:obj/source.gcno
        -:    0:Data:obj/source.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        1:    2:int main(int argc,char*argv[]){
        1:    3:  if(argc==2){
    #####:    4:    puts("hello");
        -:    5:  }
        1:    6:  return 0;
        -:    7:}

カバレッジを100%にしてみる。

$ ./a.out a
hello

$ gcov -o obj source.c 
File 'source.c'
Lines executed:100.00% of 4
source.c: 'source.c.gcov' を作成しています

$ cat source.c.gcov 
        -:    0:Source:source.c
        -:    0:Graph:obj/source.gcno
        -:    0:Data:obj/source.gcda
        -:    0:Runs:2
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        2:    2:int main(int argc,char*argv[]){
        2:    3:  if(argc==2){
        1:    4:    puts("hello");
        -:    5:  }
        2:    6:  return 0;
        -:    7:}

C言語でラインカバレッジをとりたいケースはよくあると思う。
そういう場合、上記の手順でぱぱっとすませてしまいたい。

でも、ラインカバレッジは、
実行されない文があってもカバレッジ100%になってしまうケースがあるので、
そういうのをわかった上でコードを書かないと痛い目に遭う。
そのへんは調べて書こうと思う。

あと、gcov はオプションで分岐や関数カバレッジも調べられるので、
それもまた、いずれ調べて書く予定。


実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)実例で学ぶGCCの本格的活用法―高機能コンパイラのオプション・コマンドを一つ一つていねいに解説 (TECHI―Embedded Software)
(2006/07)
岸 哲夫

商品詳細を見る

ソフトウェア・テスト PRESS 総集編ソフトウェア・テスト PRESS 総集編
(2011/07/15)
ソフトウェア・テストPRESS編集部 編

商品詳細を見る

コードカバレッジとは

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

コードカバレッジ(網羅率)について調べた。

コードカバレッジはソースコードをどれだけ動かしてみたかの指標のこと。
有名どころのカバレッジは以下のもの。

Statement Coverage:
命令網羅、文網羅、C0とも言う。
ラインカバレッジと考えると手っ取り早い。

Decision Coverage:
分岐網羅、判定網羅、デシジョンカバレッジ、C1。
条件式(if文)の成立/不成立のカバレッジ。
命令網羅との違いはelseが無いとかの暗黙的な分岐の網羅が含まれる所かな?

Condition Coverage:
条件網羅、コンディションカバレッジ、C2。
条件式にある各条件の網羅。

Path Coverage:
経路網羅、パスカバレッジ。
全ての経路を通せたかどうか。

MC/DC Coverage:
Modified Condition/Decision Coverage。
分岐の各条件が判定(成立/不成立)にどれだけ関与したかの網羅。
一番現実的に経路網羅率を測れると考えられている。

とりあえず、有名なのは、C0、C1、C2で、
一番使われてるのが、ラインカバレッジ(C0)で、
高品質の指標として妥当なのは、MC/DC というところかな。

さて、gcc/gcov で実際にカバレッジをとろうかと思ったけど、
長そうなので次回。


ソフトウェア・テスト PRESS 総集編ソフトウェア・テスト PRESS 総集編
(2011/07/15)
ソフトウェア・テストPRESS編集部 編

商品詳細を見る

C/C++の#if 0ハック。すごいなー

  • このエントリーをはてなブックマークに追加
  • web拍手 by FC2

このハックすごいなー。

http://d.hatena.ne.jp/Seasons/20090504/1241390314

C/C++はコンパイラ型言語だけど、それをインタプリタのイメージで実行するのが特徴かな。

普通、「Cインタプリタツールを使って...」だと思うのだけど、そうするとどうも C言語感が薄れる。「CインタプリタツールはWindowsでしか動きません」とか「printfは可能だけど、ライブラリのリンクはできません」とか。なんだ、Cの文法を持つ、Cインタプリタ言語じゃん...みたいな。結局、C言語とインタプリタの間になんか壁があるなぁ...みたいな。けど、この方法では使ってない。まー sh gcc は使うけど、それは壁なのか?どうなのさ?んー。悩ましい。

コンパイルして実行してるけど、インタプリタっぽい。これはどういう分類なんだろうか。とりあえず C はコンパイラ型の言語としても、もしこう書く新規の言語があった場合、どう思うだろう?...んー、たぶん、コンパイラとかインタプリタとか、そういう区別自体、どうでもいいことなのかも知れない。まーいいじゃん。そんなことよりコード書けよ...かな。

すごいなー。感動。

自分用に貼っとこっと。

#if 0
#!/bin/sh
gcc -o ${0%.*} $0 && ${0%.*} $* ; exit
#endif
#include 
int main(int c,char*v[]){
 while(c--) printf("%s ",*v++);
 printf("\n");
 return 0;
}

を hoge.c に書いたとして、

% ./hoge.c

とする。あれ? でも、これって

alias hoge='gcc -o hoge hoge.c && ./hoge'

と書くのと変らない気がする。いや、hoge.c に書くのがいいとこか。

#if 0
#!/bin/sh
gcc -DTEST -o ${0%.*} $0 && ${0%.*} $* ; exit
#endif
#include 
#include 
void func(void) {
 ....
}
#ifdef TEST
int main(int c,char*v[]){
 int ret = foo();
 if ( ret == 0 ) return 1;
 return 0;
}
#endif

とするといいかな。.c を実行した場合は単体テストみたいな。かなり昔だけど、Java 使ってたとき、こういうコードが好きでよく書いてたかも。

これはいいネタだなー。おぼえとこっと。

もちろん、こういうコードは嫌われやすいので使う場面には気をつけよう。


 | HOME | 

Search

Recent Entries

Foot Print



Categories

Monthly

Recent Comments

Recent Trackbacks

Profile

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。