データフローテストとは?具体例でわかるやり方・DUペア・制御フローテストとの違い

ソフトウェアの内部構造に基づいてテストケースを設計する「ホワイトボックステスト」には、さまざまな技法があります。

その一つがデータフローテスト(Data Flow Testing)です。

データフローテストでは、単に「どの命令や分岐を通ったか」を確認するだけではありません。

プログラム内の変数に着目し、

  • どこで値が定義されたのか
  • その値がどこで使用されたのか
  • 使用される前に別の値で上書きされていないか
  • 定義されないまま使用されていないか

といったデータの流れを確認します。

そのため、分岐網羅などの制御フローテストだけでは気付きにくい「未初期化変数の使用」「無駄な代入」「不適切な再定義」といった問題を発見するのに役立ちます。

この記事では、データフローテストの意味から、DUペア、定義クリアパス、All-Defs・All-Usesなどのカバレッジ基準、具体的な実施方法まで解説します。

さらに、混同されやすい「データフロー解析」や「制御フローテスト」との違いについても整理します。

▼テストの種類について詳しい内容はこちら▼

目次

データフローテストとは?

データフローテストとは、プログラム内の変数がどこで定義され、どこで使用されるかという関係に着目してテストケースを設計するホワイトボックステスト技法です。

特に重要なのが、変数の「定義(Definition)」と「使用(Use)」の組み合わせです。

この組み合わせをDUペア(Define-Use Pair/定義‐使用ペア)と呼びます。

例えば、次のような処理があるとします。

price = 1000

total = price * 1.1

この場合、

  • price = 1000:priceの定義(D)
  • price * 1.1:priceの使用(U)

となり、この2地点がpriceに対する一つのDUペアです。

データフローテストでは、このようなDUペアをプログラムから特定し、必要なDUペアを実際に通過するテストケースを設計します。

ISTQBの用語集でも、データフローテストは「変数の定義と使用の組を実行するようにテストケースを設計するホワイトボックステスト技法」とされています。

具体例でわかるデータフローテスト

データフローテストがどのように不具合を見つけるのか、簡単なコードで確認してみましょう。

次のPythonコードには問題があります。

def calc_price(member, price):

    if member:

        discount = 0.1

    final_price = price * (1 – discount)

    return final_price

会員の場合には、次の流れになります。

member = True

    ↓

discount = 0.1  ← 定義(D)

    ↓

1 – discount    ← 使用(U)

discountが定義された後に使用されているため、この経路では問題ありません。

一方、member=Falseの場合はどうでしょうか。

member = False

    ↓

discountの定義を通らない

    ↓

1 – discount    ← 使用(U)

discountが定義されないまま使用されます。

つまり、

「discountの使用地点まで到達できるにもかかわらず、その経路上にdiscountの定義が存在しない」

という問題があります。

Pythonでは実行時にエラーになりますが、言語や処理内容によっては、未初期化値や意図しない以前の値が使用され、より発見しにくい不具合になる場合もあります。

例えば、次のように先に初期化すれば問題を解消できます。

def calc_price(member, price):

    discount = 0.0

    if member:

        discount = 0.1

    final_price = price * (1 – discount)

    return final_price

このコードなら、member=TrueでもFalseでも、discountの定義を通ってから使用地点へ到達します。

このように、変数の定義と使用の関係を実行経路ごとに確認することが、データフローテストの基本的な考え方です。

データフローテストの目的

データフローテストの主な目的は、変数やデータの不適切な扱いによって発生する不具合を検出することです。

命令網羅や分岐網羅では、特定の命令・分岐を実行したかどうかは確認できます。

しかし、

「その処理で利用した変数は適切に初期化されていたのか」

「代入された値は、本当に使用されているのか」

「別の値で上書きされたことで、意図した値が使われなくなっていないか」

といったデータの状態までは、十分に確認できない場合があります。

データフローテストは、このような制御フローだけでは捉えにくい問題を補完するために利用します。

データフローテストで発見しやすい不具合

代表的なものには次のような不具合があります。

不具合例
未定義使用・未初期化使用値を設定していない変数を計算や条件判定で使用する
未使用の定義値を代入したものの、一度も使用しないまま処理が終了する
不要な再定義値を使用する前に別の値で上書きしてしまう
破棄後の使用解放・スコープ外となったデータを再び参照する
不適切な値の引き継ぎ本来更新すべき値が更新されず、以前の値を使用する

これらは「プログラム自体は実行できるものの、条件によって誤った結果になる」といった不具合につながる場合もあります。

データにおける「定義・使用・消滅」とは?

データフローを理解するうえでは、変数の状態を「定義(D)」「使用(U)」「消滅(K)」に分けて考えると理解しやすくなります。

定義(Definition:D)

変数に値が設定されることです。

例えば、

count = 10

では、countが定義されています。

関数の引数として値を受け取る場合なども、処理の入口で変数が定義されたと考えることがあります。

使用(Use:U)

変数の値を参照することです。

例えば、

total = count * 100

では、countが使用されています。

また、

if count > 5:

のように条件判定で参照する場合も「使用」です。

消滅(Kill:K)

変数が利用できなくなることを指します。

例えば、

  • スコープ外に出る
  • オブジェクトやリソースを解放する
  • 変数が存在しない状態になる

といったケースです。

データフローテストでは主にDとUの関係を分析しますが、データフローの不正を分析するときにはKも重要になります。

D・U・Kの組み合わせから異常を探す

データの状態遷移を見ると、問題になりやすいパターンを発見できます。

状態の遷移状況一般的な見方
D → D使用前に再定義最初の定義が不要な可能性
D → U定義後に使用正常
D → K使用せず消滅不要な定義の可能性
U → D使用後に新しい値を定義通常あり得る
U → U同じ値を複数回使用正常
U → K使用後に消滅正常
K → D消滅後に新たに定義言語や処理によっては正常
K → U消滅後に使用不具合の可能性が高い
K → K重複して消滅処理不具合の可能性

ただし、これらはあくまで「不具合を疑うための手掛かり」です。

例えばD→Dであっても、意図的な再代入であれば問題ありません。実際の仕様やプログラムの文脈と合わせて判断する必要があります。

DUペアとは?

DUペア(Define-Use Pair)とは、同じ変数について「値を定義した地点」と「その値を使用する地点」を組み合わせたものです。

例えば、

x = 10        # D1

y = x + 5     # U1

if x > 0:     # U2

    print(x)  # U3

の場合、xには、

  • D1 → U1
  • D1 → U2
  • D1 → U3

という複数のDUペアが考えられます。

データフローテストでは、このDUペアをどこまで網羅するかによってテストの強度を決めます。

定義クリアパスとは?

DUペアを理解するうえで重要なのが定義クリアパス(definition-clear path)です。

これは、ある変数が定義された地点から使用される地点までの間に、その変数が再定義されていない経路を意味します。

例えば、

x = 10        # D1

y = x + 1     # U1

なら、D1からU1まではxが再定義されていないため、定義クリアです。

一方、

x = 10        # D1

x = 20        # D2

y = x + 1     # U1

では、D1で設定された値はU1に到達する前にD2で上書きされています。

そのため、D1→U1はD1の値に対する定義クリアパスではなく、実際にU1へ到達している定義はD2になります。

この「どの定義で作られた値が、どの使用地点まで届いているのか」を追跡することが、データフローテストの重要なポイントです。

データフローテストとデータフロー解析の違い

「データフローテスト」と似た用語にデータフロー解析(Data Flow Analysis)があります。

両者は関連性が高いため混同されやすいですが、厳密には分けて考えると理解しやすくなります。

データフロー解析

データフロー解析では、ソフトウェアを実際に実行しなくても、ソースコードや制御フローグラフを分析して変数の定義・使用・破棄などを調べます。

JSTQBのAdvanced Level テクニカルテストアナリストのシラバスでも、データフロー解析は静的解析の一つとして扱われています。

例えば、

  • 定義されないまま使用される変数
  • 使用されない値
  • 破棄された後に使用される変数

などの候補をソースコードから検出します。

データフローテスト

一方、データフローテストでは、変数の定義と使用の関係を基に実際のテストケースやテストパスを設計します。

整理すると、

データフロー解析データフローテスト
主な目的データの流れの異常を分析するDUペアを基にテストケースを設計する
実行の必要性基本的に不要テストでは実際にプログラムを実行する
主な手段静的解析、制御フローグラフなどDUペア、テストパス、テストケース
関係テスト設計の材料になる解析結果を利用してテストできる

実務では両者が密接に関連するため、「データフローテスト」という言葉を広い意味でデータフロー解析まで含めて使う場合もあります。

重要なのは、静的なコード解析と、DUペアを実行するテストケースの設計・実行を区別して考えることです。

データフローテストのカバレッジ基準

すべてのデータフローを完全にテストしようとすると、テストケース数が膨大になります。

そこで、データフローテストでは「どこまでDUペアを網羅するか」というカバレッジ基準を設定します。

代表的なのが、All-Defs、All-Uses、All-DU-Pathsです。

All-Defs(全定義網羅)

All-Defsは、それぞれの変数の定義について、その定義から到達できる使用箇所を少なくとも一つテストする考え方です。

例えば、

D1 → U1

   → U2

   → U3

という関係がある場合、D1からU1・U2・U3のうち少なくとも一つへ到達する経路をテストします。

3つの中では比較的網羅性が低く、テストケース数を抑えやすい基準です。

All-Uses(全使用網羅)

All-Usesでは、それぞれの定義から到達可能なすべての使用箇所との関係を網羅します。

先ほどの例なら、

  • D1 → U1
  • D1 → U2
  • D1 → U3

をすべて確認します。

All-Defsより多くのデータフローを確認できる分、必要なテストケース数も増えます。

All-DU-Paths(全DUパス網羅)

All-DU-Pathsでは、定義から使用へ到達する定義クリアな経路そのものまで考慮します。

例えば同じD1→U1であっても、

D1 → A → U1

D1 → B → U1

という複数の経路があれば、それぞれを対象にします。

All-Usesより強い網羅基準ですが、分岐やループの多いプログラムでは対象となるパスが急激に増えるため、実務ではコストとのバランスが重要です。

3つの基準の違い

基準確認する対象網羅性テスト量
All-Defs各定義から少なくとも一つの使用低め少ない
All-Uses各定義から到達可能なすべての使用中程度中程度
All-DU-Paths定義から使用への複数の定義クリアパス高い多い

「網羅率が高ければ常に良い」というわけではありません。

重要度、欠陥発生時の影響、コードの複雑さ、テスト工数などから適切な基準を選択します。

データフローテストのプロセス

ここからは、実際にデータフローテストを実施する流れを見ていきましょう。

1.テスト対象の変数を決める

まず、どの変数やデータを分析対象にするかを決めます。

小規模なプログラムであればすべての変数を対象にできますが、大規模なシステムでは現実的ではありません。

例えば、

  • 外部から入力されるデータ
  • 金額や数量など重要な計算に利用する変数
  • 状態遷移を管理する変数
  • 多くの条件分岐を通過する変数
  • 過去に不具合の多かった処理

などを優先すると効果的です。

2.制御フローと定義・使用箇所を整理する

次に、プログラムの処理順序を整理し、各地点で変数が定義・使用されている箇所を特定します。

必要に応じて制御フローグラフを作成し、

ノード1:xを定義

   ↓

ノード2:条件判定でxを使用

   ↓

ノード3:xを再定義

   ↓

ノード4:計算でxを使用

のようにD/U情報を追加すると、データの流れを把握しやすくなります。

3.DUペアを洗い出す

各変数について、

どの定義から、どの使用地点へ値が到達できるのか

を調べます。

例えば、

x:D1 → U1

x:D1 → U2

x:D2 → U3

のように整理します。

このDUペアが、データフローテストの基本的なテスト対象です。

4.カバレッジ基準を決める

次に、

  • All-Defs
  • All-Uses
  • All-DU-Paths

などから、どこまでテストするかを決めます。

障害発生時の影響が大きい処理ではAll-Usesなどを採用し、それ以外の処理ではAll-Defsにするといったリスクベースの使い分けも考えられます。

5.テストパスを選ぶ

設定したカバレッジ基準を満たす実行経路を選びます。

例えば、All-Usesを採用した場合には、対象となるすべてのDUペアを実行できるテストパスの組み合わせを考えます。

6.テストケースを作成する

選んだ経路を通過させるために、

  • 入力値
  • 事前条件
  • 操作
  • 期待結果

を決め、具体的なテストケースに落とし込みます。

7.テストを実行して結果を確認する

テストを実施し、

  • 想定したDUペアを通過したか
  • 正しい値が使用されたか
  • 未定義使用が発生していないか
  • 不要な定義や再定義がないか

などを確認します。

問題が見つかった場合は、原因となるデータの流れを特定して修正します。

データフローテストと制御フローテストの違い

データフローテストと比較されることが多いのが、制御フローテストです。

両方ともホワイトボックステストですが、着目する対象が異なります。

データフローテスト制御フローテスト
主に着目する対象変数の定義・使用命令、条件分岐、実行経路
主な目的データの不適切な利用を検出プログラムの処理経路を確認
基本単位DUペアステートメント、分岐、パスなど
発見しやすい問題未定義使用、不要な定義、誤った再定義未実行処理、条件分岐の誤りなど
代表的な基準All-Defs、All-Uses、All-DU-Paths命令網羅、分岐網羅、条件網羅など

例えば分岐網羅が100%だったとしても、

if condition:

    x = 100

result = x * 2

のようなコードで、condition=Falseの場合にxが適切に定義されているかという問題まで自動的に保証されるわけではありません。

一方、データフローだけ確認していても、すべての重要な分岐条件を十分にテストできるとは限りません。

そのため、制御フローテストとデータフローテストは競合する技法ではなく、互いを補完する技法と考えるのが適切です。

データフローテストのメリット

データに起因する不具合を体系的に検出できる

未初期化変数や不要な再定義など、変数の扱いに起因する問題を、経験や勘だけに頼らず体系的に確認できます。

制御フローテストだけでは見つけにくい問題を補完できる

同じ実行経路を通っていても、どの定義によって作られた値が使用されるかによって結果が変わるケースがあります。

DUペアに注目することで、このような問題を検出しやすくなります。

複雑な計算処理の検証に利用できる

多数の変数が相互に依存する処理や、値の更新が繰り返される処理では、データの流れそのものが不具合原因になることがあります。

そのため、計算ロジックや状態管理が複雑なプログラムと相性の良い技法です。

静的解析と組み合わせやすい

定義・使用箇所や不自然なデータフローの候補は、静的解析ツールを使って効率的に抽出できます。

ツールによる解析と人によるテスト設計を組み合わせることで、作業効率を高められます。

データフローテストのデメリット・注意点

プログラムが大きいとDUペアが急増する

変数や分岐が増えるほど、確認すべきDUペアや実行経路も増えます。

すべてを網羅しようとすると、テストケース数が膨大になる可能性があります。

All-DU-Pathsは特にテスト量が増えやすい

同じ定義と使用の組み合わせでも、そこへ到達する経路が複数あれば、それぞれがテスト対象になります。

複雑なループや分岐を含むプログラムでは現実的にすべてを網羅することが難しいため、リスクに応じた絞り込みが必要です。

内部構造に対する知識が必要

データフローテストはソースコードや制御フローを分析するホワイトボックス技法です。

そのため、ブラックボックステストと比較すると、プログラム構造や実装についての理解が求められます。

仕様そのものの誤りは検出できない

プログラム内のデータが設計通りに流れていたとしても、その設計自体がユーザーの要求と異なっている可能性があります。

データフローテストだけで品質を保証するのではなく、仕様ベースのテストなど他の技法と組み合わせることが重要です。

データフローテストが向いているケース

特に効果を期待できるのは、次のようなプログラムです。

  • 金額や料金など重要な計算処理がある
  • 多数の変数が互いに依存している
  • 同じ変数が何度も更新される
  • 条件分岐によって初期化処理が変わる
  • 状態を保持しながら処理する
  • 未初期化値や古い値の利用が大きな障害につながる
  • 重要度の高い単体テストを実施する

逆に、すべての変数について高いデータフローカバレッジを求めると費用対効果が悪くなる場合があります。

重要な処理から優先して適用することがポイントです。

データフローテストを成功させるポイント

テスト範囲に優先順位を付ける

すべての変数を同じレベルでテストする必要はありません。

例えば、

  • 外部入力
  • 金額
  • 権限
  • ステータス
  • 在庫
  • 日付
  • 重要な計算途中の値

など、不具合が発生した場合の影響が大きいデータを優先します。

また、重要度に応じてAll-DefsとAll-Usesを使い分けるなど、カバレッジの強さを調整することも重要です。

静的解析ツールを活用する

変数の定義・使用箇所を人手ですべて洗い出すと、多くの時間が必要になります。

静的解析ツールやIDE、コンパイラの警告機能などを利用すれば、

  • 未初期化変数
  • 未使用変数
  • 到達不能コード
  • 不自然な代入

などを効率的に発見できます。

ただし、ツールが指摘した内容が必ず不具合とは限りません。

最終的には仕様やプログラムの意図を踏まえてエンジニアが判断する必要があります。

制御フローテストと組み合わせる

データフローテストは制御フローテストの代替ではありません。

命令・分岐という「処理の流れ」と、変数の定義・使用という「データの流れ」の両方を確認することで、より多角的にプログラムを検証できます。

コードレビューや単体テストと組み合わせる

データフロー上の問題には、実行して初めて明らかになるものだけでなく、コードレビューや静的解析で早期に発見できるものもあります。

そのため、

静的解析 → コードレビュー → 単体テスト → 必要に応じたデータフローカバレッジ確認

といった形で、開発工程全体の中に取り入れると効果的です。

データフローテストに関するよくある質問

データフローテストはホワイトボックステストですか?

はい。

プログラムの内部構造や変数の定義・使用関係を基にテストケースを設計するため、ホワイトボックステスト技法の一つです。

DUペアとは何ですか?

DUペアとは「Define-Use Pair」の略で、同じ変数について「値を定義する地点」と「その値を使用する地点」を組み合わせたものです。

データフローテストでは、このDUペアをどこまで網羅するかを基にテストケースを設計します。

データフローテストとデータフロー解析は同じですか?

密接に関連していますが、分けて考えることができます。

データフロー解析は、主にソースコードを実行せずに変数の定義・使用・破棄などを調査する静的解析です。

一方、データフローテストは、その分析結果やDUペアを基に、実際に通過させるテストケースを設計する技法です。

All-DefsとAll-Usesの違いは何ですか?

All-Defsは、各定義について少なくとも一つの使用地点まで到達することを確認します。

All-Usesは、各定義から到達可能なすべての使用地点との関係を確認します。

そのため、一般的にはAll-Usesの方がAll-Defsより強い網羅基準になります。

制御フローテストとの違いは何ですか?

制御フローテストは、命令や分岐など「処理がどの経路を通るか」に着目します。

データフローテストは、「どこで定義された値が、どこで使用されるか」に着目します。

両者を組み合わせることで、処理とデータの両面からプログラムを検証できます。

データフローテストは単体テストで使えますか?

利用できます。

特に、計算ロジックや状態管理など、変数の更新・利用関係が複雑な関数やモジュールの単体テストと相性があります。

ただし、すべてのコードに一律で適用するのではなく、リスクの高い処理を中心に使う方が現実的です。

まとめ

データフローテストとは、プログラム内の変数についてどこで定義され、どこで使用されるかというDUペアに着目してテストケースを設計するホワイトボックステスト技法です。

命令や分岐を中心に確認する制御フローテストとは異なり、データフローテストでは値の流れそのものを追跡します。

そのため、

  • 定義されていない変数の使用
  • 使用されない値の定義
  • 使用前の不要な再定義
  • 破棄後のデータ使用
  • 意図しない古い値の利用

といったデータに関する問題を発見するのに役立ちます。

データフローテストを実施する際には、まず変数の定義・使用箇所を整理してDUペアを特定し、All-Defs・All-Uses・All-DU-Pathsなどの基準から必要な網羅レベルを決めます。

ただし、大規模なプログラムですべてのデータフローを網羅しようとするとテストケース数が急増します。

そのため、重要な変数や障害発生時の影響が大きい処理に対象を絞り、静的解析ツールや制御フローテストと組み合わせて活用することが重要です。

データの「定義」と「使用」という視点をテスト設計に取り入れることで、制御フローだけでは見つけにくい不具合を発見し、より信頼性の高いソフトウェア開発につなげることができるでしょう。

QA業務効率化ならPractiTest

テスト管理の効率化についてお悩みではありませんか?そんなときはテスト資産の一元管理をすることで工数を20%削減できる総合テスト管理ツール「PractiTest」がおすすめです!

PractiTest(プラクティテスト)に関する
お問い合わせ

トライアルアカウントお申し込みや、製品デモの依頼、
機能についての問い合わせなどお気軽にお問い合わせください。

この記事の監修

Dr.T。テストエンジニア。
PractiTestエバンジェリスト。
大学卒業後、外車純正Navi開発のテストエンジニアとしてキャリアをスタート。DTVチューナ開発会社、第三者検証会社等、数々のプロダクトの検証業務に従事。
2017年株式会社モンテカンポへ入社し、マネージメント業務の傍ら、自らもテストエンジニアとしテストコンサルやPractiTestの導入サポートなどを担当している。

記事制作:川上サトシ(マーケター、合同会社ぎあはーと代表)