この関数の最後にある分岐は何ですか。どうすればそれらをカバーできますか?
3 に答える
静的ストレージ期間 (グローバル) 変数を破棄するために gcc によって生成されたコードを観察しています。
あなたのカバレッジは、関数foo
が 3 回入力されたことを示していますが、スコープの終わり近くのカウンターは、コードが 8 回実行されたことを示しています。
ここで、コンパイラがヘッダー ファイルを変換単位に配置し、gcov がコードをそのままではなく、グラフのエッジとして分岐するアセンブリ命令の制御フロー グラフとして認識することを考慮する必要があります。
したがって、foo
lcov html 出力の「スコープの終わり」は、実際にはfoo
メソッド スコープの終わりではなくfoo
、ヘッダー ファイルで宣言されたグローバル変数の破棄を含む、翻訳単位全体の後に含まれるすべてのものです。
ヘッダー自体は質問に含まれていませんが、gcc が生成する最も基本的な__static_initialization_and_destruction
アセンブリでさえ、多数のブランチが含まれています。
グローバル変数が含まれている場合と含まれていない場合があることに注意してください。gcc は、すべての翻訳単位に対してこのコードを生成する可能性があります。
gcov の基本的な出力を見てください。
function _Z3fooi called 1 returned 100% blocks executed 50%
1: 4:int foo(int x) {
1: 5: if (x==1) {
branch 0 taken 0% (fallthrough)
branch 1 taken 100%
#####: 6: std::cout << "foo" << std::endl;
call 0 never executed
call 1 never executed
#####: 7: return 0;
-: 8: }
1: 9: return 1;
function _GLOBAL__sub_D__Z3fooi called 1 returned 100% blocks executed 100%
function _GLOBAL__sub_I__Z3fooi called 1 returned 100% blocks executed 100%
function _Z41__static_initialization_and_destruction_0ii called 2 returned 100% blocks executed 100%
6: 10:}
call 0 returned 100%
call 1 returned 100%
branch 2 taken 50% (fallthrough)
branch 3 taken 50%
branch 4 taken 100% (fallthrough)
branch 5 taken 0%
-: 11:
そして、要点を明確にするためにトリミングされた、生成されたアセンブリを見てください。
...
ret
.seh_endproc
.def _Z41__static_initialization_and_destruction_0ii; .scl 3; .type 32; .endef
.seh_proc _Z41__static_initialization_and_destruction_0ii
_Z41__static_initialization_and_destruction_0ii:
.LFB978:
...
mov QWORD PTR __gcov0._Z41__static_initialization_and_destruction_0ii[rip], rax
cmp DWORD PTR 16[rbp], 1
jne .L5 <-- BRANCH
mov rax, QWORD PTR __gcov0._Z41__static_initialization_and_destruction_0ii[rip+8]
add rax, 1
mov QWORD PTR __gcov0._Z41__static_initialization_and_destruction_0ii[rip+8], rax
cmp DWORD PTR 24[rbp], 65535
jne .L5 <-- BRANCH
...
.L5:
cmp DWORD PTR 16[rbp], 0
je .L6 <-- BRANCH
void関数でカバーされていないエンドブラケットでも同じ問題がありました。
私は2つの回避策を見つけました:
最初に最後の関数呼び出し行にエンドブラケットを追加して、個々の行として表示されないようにします
2 番目以降: ランダムな "return;" を追加します。関数の最後でコードを強制的に実行する
非常に単純な答えとして、ブランチはIF/ELSEブランチを意味します。そのため、すべての if/else には 2 つの新しいブランチがあります (カバーする必要があります)。ネストされている場合、指数関数的に成長します。
function twoNewBranches() {
if () {
// code
} else {
// code
}
}
function twoNewBranchesNotAparent() {
if () {
// code
}
}
function fourNewBranches() {
if () {
if () {
// code
} else {
// code
}
}
}
• 最初の関数twoNewBranchesは、カバーする必要がある 2 つの新しいブランチを作成します
• if ステートメントを満たさないテストをカバーする必要があるため、2番目の関数twoNewBranchesNotAparentも 2 つの新しい分岐を作成します。
• 3 番目の関数fourNewBranchesは、カバーする 4 つの (2^2=4) 新しい分岐を作成します。ネストされた 2 つ、ネストされたものの親、および非表示の Else。
ブランチをカバーするということは、条件ステートメントをカバーすることです。