ソフトウェアテストにおける循環的複雑性(例付き)

⚡ スマートサマリー

循環的複雑度とは、1976年にトーマス・マッケイブによって開発されたソフトウェアの指標であり、プログラム内の独立したパスの数を数えるものです。これは制御フローグラフから計算され、完全な分岐網羅に必要なテストケースの数を示します。

  • 📐 2つの公式: グラフから V(G) = E – N + 2 または、決定点の数から V(G) = P + 1 となります。
  • 🧮 直接的な意味: この値は、独立したパスの最大数、つまり必要なテストケースの最大数に相当します。
  • おいおいおいそ️ グラフの基準: ノードは処理ステップを表し、エッジはそれらの間の制御の流れを表します。
  • 🟢 1から10へ: 構造化され、適切に記述されたコードであり、テスト容易性が高く、保守コストが低い。
  • 🟠 21から40へ: テスト容易性が低い非常に複雑なコードであり、通常、リファクタリングにかかる​​コストはテストにかかるコストよりも低い。
  • 🛠️ ツーリング: SonarQube, Visual Studio Code Metrics、Radon、Lizardはこれを自動的に計算します。

ソフトウェアテストにおける循環的複雑性

McCabe の循環的複雑度とは何ですか?

ソフトウェアテストにおける循環的複雑性 ソフトウェア プログラムの複雑さを測定するために使用されるテスト メトリックです。これは、ソフトウェア プログラムのソース コード内の独立したパスを定量的に測定するものです。循環的複雑度は、制御フロー グラフを使用するか、ソフトウェア プログラム内の関数、モジュール、メソッド、またはクラスに関して計算できます。

独立したパスは、他のパスで以前に通過したことのない少なくとも XNUMX つのエッジを持つパスとして定義されます。

このメトリクスは、1976 年に Thomas J. McCabe によって開発され、プログラムの制御フロー表現に基づいています。 制御フローは、プログラムをノードとエッジで構成されるグラフとして表現します。

グラフでは、ノードは処理タスクを表し、エッジはノード間の制御フローを表します。

マッケイブの循環的複雑度

プログラムのフローグラフ表記法

プログラムのフロー グラフ表記は、エッジを介して接続された複数のノードを定義します。 以下は、if-else、while、until などのステートメントと通常のフロー シーケンスのフロー図です。

プログラムのフロー グラフ表記

循環的複雑度の計算方法

数学的表現:

数学的には、グラフ図を通る独立した経路の集合です。 Code プログラムの複雑さは、次の式を用いて定義できます。

V(G) = E - N + 2

ここで、

E – エッジの数

N – ノードの数

V (G) = P + 1

ここで、 P = 述語ノード (条件を含むノード) の数

例–

i = 0;
n=4; //N-Number of nodes present in the graph

while (i<n-1) do
j = i + 1;

while (j<n) do

if A[i]<A[j] then
swap(A[i], A[j]);

end do;
j=j+1;

end do;

このプログラムのフローグラフは次のようになります。

循環的複雑度を計算する

数学的に計算すると、

  • V(G) = 9 – 7 + 2 = 4
  • V(G) = 3 + 1 = 4 (条件ノードは 1,2、3、XNUMX ノード)

基底関数4つの独立した実行パス:

  • 1、7
  • 1、2、6、1、7
  • 1、2、3、4、5、2、6、1、7
  • 1、2、3、5、2、6、1、7

サイクロマティック複雑性の特性

循環的複雑度の特性は次のとおりです。

  1. V (G) はグラフ内の独立したパスの最大数です。
  2. V (G) >=1
  3. V (G) = 1 の場合、G には XNUMX つのパスが存在します。
  4. 一般的に用いられるガイドラインは、単一モジュールの場合、V(G)を10以下に抑えることである。

この指標がソフトウェアテストにどのように役立つか

基本パス テストはホワイト ボックス テクニックの 1 つであり、テスト中に少なくとも 1 つのステートメントが実行されることを保証します。プログラム内の各線形独立パスをチェックします。つまり、 必要なテストケースの数は、プログラムの循環的複雑度に等しい。.

この指標は循環的複雑度(M)の特性により有用である。

  1. M は分岐カバレッジを達成するためのテスト ケースの数です (上限)
  2. M はグラフを通るパスの数です。 (下限)

この例を考えてみましょう –

If (Condition 1)
Statement 1

Else
Statement 2

If (Condition 2)
Statement 3

Else
Statement 4

このプログラムの循環的複雑度は 8-7+2=3 になります。

複雑度は 3 と計算されたため、上記の例ではパス カバレッジを完了するには XNUMX つのテスト ケースが必要です。

従うべき手順

循環的複雑度の計算とテストケースの設計については、次の手順に従う必要があります。

ステップ 1 – コードからのノードとエッジを含むグラフの構築

ステップ 2 – 独立したパスの特定

ステップ 3 – 循環的複雑度の計算

ステップ 4 – テストケースの設計

基本セットが完成したら、 テストケース すべてのパスを実行するように記述する必要があります。

V(G)の詳細

プログラムが小さい場合、循環的複雑度は手動で計算できます。プログラムが非常に複雑な場合は、フロー グラフが多くなるため、自動化ツールを使用する必要があります。複雑度の数値に基づいて、チームは対策に必要なアクションを決定できます。

以下の表は、複雑度番号とv(G)に対応する意味の概要を示しています。

複雑度数 意味
1〜10

構造化され、適切に記述されたコード

高いテスト容易性

コストと労力が少なくて済む

11〜20

複雑なコード

中程度のテスト可能性

コストと労力は中程度

21〜40

非常に複雑なコード

低いテスト可能性

コストと労力が高い

> 40

まったくテストできません

非常に高いコストと労力

循環的複雑度を計算するツール

アプリケーションの複雑さを判断するためのツールは多数あります。特定のテクノロジには、いくつかの複雑さ計算ツールが使用されます。複雑さは、プログラム内の決定ポイントの数で判断できます。決定ポイントとは、ソース コード内の if、for、for-each、while、do、catch、case ステートメントです。

ツールの例としては、

  • OCLint – C および関連言語用の静的コード アナライザー
  • SonarQube – 25以上の言語にわたる循環的複雑性と認知的複雑性を報告する
  • Visual Studio Code メトリクス – .NETアセンブリ向けの組み込み循環的複雑度分析
  • RadonとLizard – コマンドライン複雑性アナライザー Python 多言語プロジェクトの場合、それぞれ
  • Gメトリクス – メトリクスを検索します Java 関連アプリケーション

循環的複雑性の用途

循環的複雑性は、次のような場合に非常に役立ちます。

  • 開発者とテスターが独立したパスの実行を決定するのに役立ちます
  • 開発者は、すべてのパスが少なくとも一度はテストされていることを保証できます。
  • 覆われていない道にもっと集中できるようになります
  • コードカバレッジを改善する ソフトウエアエンジニアリング
  • アプリケーションまたはプログラムに関連するリスクを評価する
  • サイクルの早い段階でこれらの指標を使用すると、プログラムのリスクがさらに軽減されます

循環的複雑度を低減する方法

複雑度が高い数値は、あくまでも目安であり、最終的な判断ではありません。実際には、4つのリファクタリングによって、ほとんどの削減効果が得られます。

  • Extract法。 分岐を独立した関数に移動することで、複雑さが2つのモジュールに分割されます。システム全体の複雑さは変わりませんが、各ユニットが独立してテスト可能になります。
  • 条件分岐をルックアップに置き換えます。 同じ変数をテストする長いif-else-if文は、マップまたはスイッチに変換され、多くの判断ポイントが1つにまとめられます。
  • ガード条項を使用してください。 無効な入力に対して早期に処理を終了することで、単一の大きなif-elseブロックが作り出すネスト構造を解消しつつ、動作を変更することなく問題を解決できます。
  • 条件分岐をポリモーフィズムに置き換える。 条件分岐が型に基づいて切り替わる場合、各分岐を独自のクラスに移動すると、その判断自体がなくなります。

以前は、V(G) = 4 の場合:

if (user != null) {
    if (user.isActive()) {
        if (user.hasRole("admin")) {
            return grantAccess();
        }
    }
}
return denyAccess();

その後、同じ動作でネスト構造を削除した場合:

if (user == null) return denyAccess();
if (!user.isActive()) return denyAccess();
if (!user.hasRole("admin")) return denyAccess();
return grantAccess();

指標に関する注意点。 循環的複雑度は、難易度ではなく決定回数を数えます。単純なケースが20個あるswitch文は21点ですが読みやすく、一方、深くネストされたブロックは8点でも理解がはるかに難しい場合があります。この数値は、レビュー対象を見つけるための指標として使用し、不正行為の標的として利用しないでください。

よくあるご質問

モジュールあたりのコード数は10以下が一般的な目安です。11~20の場合はコードは複雑ですが管理可能です。20を超えるとテスト容易性は著しく低下し、40を超えると現状ではテスト不可能とみなされます。

どちらも同じ結果になります。P + 1 は、決定点だけを数えるため、手計算では高速です。E – N + 2 は、制御フローグラフを既に構築しているツールが使用する方法です。

必ずしもそうとは限りません。循環的複雑度は難易度ではなく決定回数を数えるため、単純なケースが20個しかないフラットスイッチは読みやすさを保ちつつ高いスコアを獲得します。この数値は復習のきっかけとして捉えてください。

彼らはそれを変更頻度や欠陥履歴と組み合わせて、どのモジュールが最もリスクが高いかをランク付けし、最も障害が発生しやすいコードにレビューとテストの労力を集中させる。

はい。AIアシスタントはガード条項を提案します。tracted メソッドと、カウントを削減するルックアップ テーブルを使用します。ロジックを変更するリファクタリングは目的を損なうため、既存のテスト スイートを使用して動作を確認してください。