見出し画像

第108回: 制御フローテストの設計

≡ はじめに

ASTERセミナー標準テキスト」の142ページについてです。

前回は、ユーザビリティテスト設計について書きました。今回はホワイトボックステストの1つである制御フローテストの設計について書きます。

「制御フローテスト」は、プログラマーが、自分が作ったコードに対して実施することが多いテストです。

「制御フローテスト」は、ホワイトボックステストに位置付けられます。ホワイトボックステストとはISTQBの用語集では次の定義です。

ホワイトボックステスト(white-box testing)
コンポーネントまたはシステムの内部構造の分析に基づいたテスト。
Synonyms: クリアボックステスト(clear-box testing), コードベースドテスト(code-based testing), グラスボックステスト(glass box testing), 論理カバレッジテスト(logic-coverage testing), 論理駆動テスト(logic-driven testing), 構造テスト(structural testing), 構造ベースドテスト(structure-based testing)

同義語(類義語、同意語)がたくさんあります。
クリアボックステストやグラスボックステストは、「ホワイトボックスでは“白い箱”なので中が(透けて)見えない」という意図でついた名前です。なんかいちゃもんっぽいですね。
あまり流行っていないので使わない方が良いと思いますが、グラスボックステストと書いてある本もありますので、知識として知っておいてください。

その他の同義語は、いずれもテストの対象や方法を端的に表しており、誤解する余地が無いので、良い用語だと思います。
同義語を繋げば、「ホワイトボックステストは、(ソース)コードをテスト対象として、その論理の網羅性を分析しながら、プログラムの構造をテストするもの」となります。概ねホワイトボックステストのイメージと合うのではないでしょうか。
キーとなる「論理カバレッジ」を中心に、「制御フローテスト」について以下に説明します。


≡ 「制御フローテスト」の対象

テキストに「関数やメソッドのロジックを網羅する」と書いてあります。
ここから、ソースコードを対象とすることがわかりますが、制御フローテストは、テスト対象の商品やサービスの全ソースコードを一度に対象とするテスト技法ではなく、個々の関数やメソッドを対象としてテストする方法です。

もちろん、全ての関数やメソッドをテストすれば、全コードをテストすることになりますが、テスト設計・実行するときのテスト対象は、関数やメソッドであることに注意してください。
逆に言えば、関数間の組合せや関数間の構造を網羅的にテストするものではありません。

そのような大きな構造(アーキテクチャ)に対してテストを考えてテストしても良いです。
もし、テストするならそれは「構造ベースのテスト」です。ISTQBでは、構造ベースのテストのことをホワイトボックステストと呼んでいますので、ちょっと混乱しますね。今回のノートはあくまでも、ホワイトボックステストの1つである「制御フローテスト」の解説です。
「ホワイトボックステスト=制御フローテスト」ではないことに注意してください。ベテランの方の方がイコールと思っていることが多いかもしれません。
関数(function)とメソッド(method)の違い
ひとかたまりの処理を関数やメソッドと言います。例えば、「配列に格納された数値の合計を求める処理」のことです。
「配列に格納された数値の合計を求める処理」は色々なところで使いまわせそうです。そこで、プログラミング言語には、それぞれの場所に書くのではなく、一か所にまとめて書いて使いまわすことができる仕組みがあります。その「処理のまとまり」のことを関数やメソッドと呼びます。
制御フローテストではそれだけ知っていたら大丈夫です。以下は補足説明です。
なぜ、「処理のまとまり」に、関数やメソッドという別の言葉がついているのかといいますと、それは、プログラミング言語の歴史と関係しています。
・・・
プログラミングをしていると、「さっきも同じような処理を書いたじゃーん」と思うことがあります。例えば、上に書いた「配列に格納された数値の合計を求める処理」のようなものです。
これを、それぞれの場所に書く以外の方法がないとしたら、、、。書くときにはコピペで済むかもしれませんが、もしも、その処理にバグがあったらコピペした全ての箇所を直さないとなりません。
それはとても面倒で間違いが入りやすい作業となるでしょう。

そこで、プログラミング言語では、同じような処理を抜き出して名前を付けて、呼び出せるようにしました。これを「メインルーチン」に対して、「サブルーチン」と呼びます。

ふつう、サブルーチンはメインルーチンから引数付きで呼ばれます。そしてサブルーチンで処理が行われ、引数の値を書き変えてメインルーチンに戻ります。
この仕組みでもプログラミングはできるのですが、「引数の値を書き変えるのは、ちょっとなー」ということになり、処理結果の値を返すようになりました。たとえば、sin(30°)なら0.5を返すという具合です。
値を返すようになったサブルーチンのことを“関数”と呼びます。“関数”という用語は、sin関数とか指数関数とか、数学でおなじみですよね。
(“関数”は“函数”って書いた方が文字が意味を表しますね。どうでもいいですが)

そんなこんなで、FortranやBASICあたりの昔の言語では、サブルーチンと関数が混在していたのですが、C言語が関数で統一したあたりから、サブルーチンはスクリプト言語以外では、あまり見かけなくなったように思います。

しばらくは、平穏な日々が続いていました。
ところが、オブジェクト指向言語というのが生まれたときに、「オブジェクトはデータと処理を1つにまとめたもの」という“カプセル化”という概念が誕生しました。そのときに、オブジェクトが持っている処理のことを関数ではなくメソッドと呼ぶようになりました。

ですから、関数は関数名だけで利用できるのに対して、メソッドはオブジェクト名+メソッド名で呼び出すような違いが生じました。(メソッドの方は、たとえば、robot.run(20)と書いて、robotというオブジェクトに“20走れ”と指示するような使い方となります)

ということで、長いうんちくが続きましたが、制御フローテストのテスト設計やテスト実行をするときの対象である関数やメソッドの意味はご理解いただけたことと思います。

一つの関数の適切な行数は、、、と書くとこちらも長い話になってしまいますがテスト担当者としても知っておいた方が良いと思いますので、簡単に書きます。

一つの関数の行数
人それぞれ流儀はあると思うのですが、変数の定義部はともかく処理は一画面に収まった方が作りやすいので、処理が20行とそれ以外が10行として、30行以内にしておくのが目安としては良いのではないでしょうか。(switch文のような“行数は多くなってもロジックを追うのが楽な処理”もありますので、あくまでも目安です)
さらに、関数やメソッドの役割はひとつだけにして、インデントも2階層くらいで済むようにすると良いと思います。
なかには、「関数は10行以内」といった、厳しいルールを自分に課している人もいますが、どうなんでしょうね? かえってトリッキーで自分以外の人が保守しにくいコードになっていないのでしょうか?? 実物を見たいです。


≡ カバレッジ

「ホワイトボックステストの対象」が分かりましたので、いよいよ「制御フローテストの設計」の話なのですが、「制御フローテスト」自体は、「ソースコードの処理の流れを網羅するテスト」といったざっくりした意味です。

そこで、ここでは、処理の流れを網羅する方法について書きます。

代表的な網羅方法には、「ステートメントカバレッジ(statement coverage)」、「デシジョンカバレッジ(decision coverage)」、「条件網羅(condition coverage)」、「複合条件網羅(multiple condition coverage)」、「MC/DC、改良条件判定(modified condition / decision coverage)」の5つがあります。JSTQBのFL-AuTやAL-TTAの出題範囲ですので、まずはこの5つを説明できるようになれば良いと思います。
他にもブランチカバレッジ(branch coverage)や、Lcsaj(Linear Code Sequence And Jump)は知っておいた方が良いと思いますが、使用頻度が低いですし、今回は長くなるので説明しません。興味のある方は、カズさんのページが分かりやすいと思います。


■ 条件と判定
以前、「93号:デシジョンテーブル(前編)」で、条件と判定について書きました。再掲します。

if ((a > 3) & (b < 4)) { xxx } else { yyy }

といった(疑似)コードがあったときに、「a > 3」と「b < 4」を条件(Condition)と言って、(if文で)分岐するために、真・偽を明らかにすることを判定(Decision)と呼ぶ。

制御フローテストではいくつかカバレッジ基準が出てきますが、「条件と判定」が区別できていないと混乱します。

それでは、次の5つの網羅基準について、順番に説明していきます。
 ・ ステートメントカバレッジ(statement coverage)
 ・ デシジョンカバレッジ(decision coverage)
 ・ 条件網羅(condition coverage)
 ・ 複合条件網羅(multiple condition coverage)
 ・ MC/DC、改良条件判定(modified condition / decision coverage)


■ ステートメントカバレッジ(statement coverage)

命令網羅とも呼びます。ソースコード中の実行可能な行のうち、何行を実行したかで測ります。

 ステートメントカバレッジの網羅率
   = 実行した行 ÷ 実行可能な行 × 100

当たり前な説明ですが、分母となる測定対象が「実行可能な行」であることに注意してください。
空行やコメント行は含みません。定義や「{」だけの行も含みません。
ツールを使ってカバレッジ(網羅率)を計測するときに、「あれ?」って思わないようにしましょう。(手作業で行うときには、実行可能な行にこだわらずに全ての行でも構いません。網羅率の値がほんの少し変わるだけですから)

なお上述の通り、テストは関数単位に実施します。
デバッガーで関数のソースコードを表示しながら1行ずつ進めるという方法(ワンラインデバッグ:one line debugと呼ぶ人もいます)でもステートメントカバレッジは100%となります。

ステートメントカバレッジを計測するツールを裏で実行しながら、ブラックボックステストやTDDのテストコードを先にざっと流して、ブラックボックステストで実行されずに漏れたステートメントをカバーする方法もあります。良い方法です。

ステートメントカバレッジの例):
 目的: numという変数に格納された数値が偶数か奇数か判定する
 演算子:%は、割り算の余りを求める演算子

1 if( num % 2 == 0 ){
2  printf("%d は偶数です\n", num);
3 } else {
4  printf("%d は奇数です\n", num);
5 }

上記のソースコードのステートメントカバレッジを100%にするためには、numが偶数と奇数(例えば、4と5)の2つのテスト実行が必要です。
このコードでは、デシジョンカバレッジもnumが偶数と奇数の2つのテスト実行をします。つまり、ブロック「{……}」の中が空っぽ出ない限り、ステートメントカバレッジとデシジョンカバレッジの実施内容は同じです。

なお、ステートメントカバレッジのことをC0カバレッジ(シーゼロ)と呼ぶ人が多いです。ただし、C0やC1という呼び方は何度か定義が変わっているようなので、当該テストの定義をきちんと決めてから使うことが必要です。(そんなことをするよりも、ステートメントカバレッジと書いた方が誤解が起こらないので良いと思います)


■ デシジョンカバレッジ(decision coverage)

判定網羅とも呼びます。ソースコード中の判定の真偽によるフローの分岐をどれだけ網羅したかで測ります。

 デシジョンカバレッジの網羅率
   = 判定の真・偽 ÷ 全ての判定の真・偽 × 100

上の方にある「条件と判定」のところで書いた判定の真と偽を網羅するということです。

if ((a > 3) & (b < 4)) { xxx } else { yyy }

なら((a > 3) & (b < 4))部分が真と偽になるテストケースを作ります。例えば、
 a = 10で、b=1なら((a > 3) & (b < 4))は(真 and 真)なので真
 a = 10で、b=5なら((a > 3) & (b < 4))は(真 and 偽)なので偽
ですので、「a = 10で、b=1」と「a = 10で、b=5」の2つのテストを行えば、デシジョンカバレッジは100%となります。


■ 条件網羅(condition coverage)

今度は判定ではなく条件です。同じ例で見ていきましょう。

if ((a > 3) & (b < 4)) { xxx } else { yyy }

このif文には「a > 3」と「b < 4」の2つの条件があります。それぞれの条件の真偽をテストするのが状態網羅です。

例えば、
 a = 10で、b=5なら「a > 3」と「b < 4」は、真と偽
 a = 2で、b=1なら「a > 3」と「b < 4」は、偽と真
です。この2つのテストを行えば、「a > 3」と「b < 4」のそれぞれについて真と偽のテストがされますので、条件網羅は100%となります。

※ このとき、デシジョンカバレッジは「偽」のフローしか通りませんので50%であることに注意してください。条件網羅が100%であってもデシジョンカバレッジは100%とは限りません。同様にステートメントカバレッジも100%とは限りません。実行されないブロックの中に実行可能な行があるかもしれないからです。


■ 複合条件網羅(multiple condition coverage)

複合条件網羅は条件の組合せを網羅する意味です。同じ例で見ていきましょう。

if ((a > 3) & (b < 4)) { xxx } else { yyy }

このときに、
 a = 10で、b=1なら「a > 3」と「b < 4」は、真と真
 a = 10で、b=5なら「a > 3」と「b < 4」は、真と偽
 a = 2で、b=1なら「a > 3」と「b < 4」は、偽と真
 a = 2で、b=5なら「a > 3」と「b < 4」は、偽と偽
です。この4つのテストを行えば、真と偽の組合せを網羅していますので、複合条件網羅は100%となります。

※ このケースでは条件が2つでしたので、2^2 = 4つのテストですみました。しかし、条件が3つなら2^3 = 8つのテストとなります。つまり、1つの判定の中に条件が1つ増えると倍々でテスト回数が増えます。したがって、複合条件網羅はテスト回数が多くなるため、あまり実用的ではありません。


■ MC/DC、改良条件判定(modified condition / decision coverage)

複合条件網羅は条件の組合せを網羅するため、テスト回数が倍々に増えるデメリットがありました。それを改良したものが、MC/DCです。

MC/DCは、条件の組合せを考えるときに、その条件が判定結果に影響するかどうかを考慮することによりテスト回数を減らします。同じ例で確認します。

if ((a > 3) & (b < 4)) { xxx } else { yyy }

MC/DCでは、このときに、
 a = 10で、b=1なら「a > 3」と「b < 4」は、真と真
 a = 10で、b=5なら「a > 3」と「b < 4」は、真と偽
 a = 2で、b=1なら「a > 3」と「b < 4」は、偽と真
の3つを行います。

この3つのテストを行えば、MC/DCは100%となります。
1行目の条件の組合せは、真×真です。この時の判定は真ですが、&(and)は両方とも真のときだけ真になりますから、1行目では、変数aの条件で真になることと、変数bの条件で真になることが確認されています。

次に、2行目の条件の組合せは、真×偽です。この時の判定は偽ですが、&(and)は片方が偽なら相手が真であっても偽になります。つまり2行目では、変数bの条件で偽になることが確認されています。

最後に、3行目の条件の組合せは、偽×真です。同様の考え方で、3行目では、変数aの条件で偽になることが確認されています。

※ MC/DCでは条件が増えると倍々でテスト回数が増える複合条件網羅とは異なり、条件の数+1のテスト回数になります。したがって全ての条件の真偽が判定に正しい影響を与えていることを必要最小限で網羅的に確認できます。

※ 3つの条件がANDでつながっていれば、真×真×真、真×真×偽、真×偽×真、偽×真×真の4つです。考え方は2つのときと同じです。
※ ANDでなくORの場合は、偽×偽×偽、偽×偽×真、偽×真×偽、真×偽×偽です。ORなので判定が偽になるには、全ての条件が偽でないとだめですし、条件が一つだけでも真なら判定は真になるからです。
※ ANDとORが混在する場合は、CEGTESTツールなどで原因結果グラフを書いてデシジョンテーブルを作って、その条件組合せ(ルール)を全てテストすればMC/DCカバレッジは100%となります。

※ MC/DCが100%であれば、ステートメントカバレッジ、デシジョンカバレッジ、条件網羅は100%となります。


■ どのカバレッジ基準で何%を目標にすればよいか?

テストは状況次第ですので、テスト対象に求められる品質によって変わりますが、私のお勧めは、以下の通りです。

規格などで、MC/DCまで網羅することが求められる場合もありますので、そのときにはそうするしかありません。

 テストレベル: ユニットテスト
 カバレッジ基準:デシジョンカバレッジ
 目標値:100%

 ただし、実行しなかったステートメントやデシジョンについては実行しなかった理由と、次工程のテストへの申し送りがされ、次工程で了解されていれば良い。


※ この目標値を満たす、最小のテストケース数が「McCabeのサイクロマティック複雑度」と同じになることを知っておくと良いです。リンク先の方法で計算した値とユニットテストのテストケース数を比較することで、ユニットテストの回数が少なすぎたり、多すぎたりしていないかの判断の目安に使えるからです。


≡ 終わりに

今回は「制御フローテストの設計」について書きました。

正直なところ、「制御フローテストのカバレッジを上げること」と「品質の向上」に正の相関があるかどうか分かりません。

けれども、カバレッジを計測することで開発者が「たったこれしかテストしていないのか」と気が付くきっかけになるのは間違いありません。

ですから、「デシジョンカバレッジ」について、80%が良いのか、100%が良いのかについての答えはどこにも無いと思います。それよりも「テストしていない箇所が明らかになること」、「テストしていない箇所について、テストしないまま(リスクとして)リリースするのか、何とかテスト(もしくはコードレビューを)して欠陥があったらデバッグしてその箇所のリスクをゼロにしてからリリースするのか」について意思決定をすることが大切と思います。

さて、テキストは、「制御フローテスト」の次は、「データフローテスト」へと続くのですが、次回は「経験ベースのテスト技法」について書きます。「データフローテスト」は私が詳しくないからです。「データフローテスト」は、プログラミング言語特有なところもありますし、そうでないところは常識で考えたら分かりますし。

「データフローテスト」は、勉強する価値がないとは言いません。
静的解析ツールの出力の一つを理解するために「データフローテスト」について勉強するのは悪くないと思います。

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