web-dev-qa-db-ja.com

グラフ構造を使用してコードを単体テストするにはどうすればよいですか?

依存関係グラフをナビゲートしている(再帰的な)コードを書いて、依存関係の循環または矛盾を探します。ただし、これを単体テストする方法はわかりません。問題は、主な懸念の1つが、発生する可能性のあるすべての興味深いグラフ構造をコードが処理し、すべてのノードが適切に処理されることを確認することです。

通常、100%のラインまたはブランチのカバレッジは、一部のコードが機能することを確信するのに十分ですが、100%のパスカバレッジであっても疑問が残るように感じます。

したがって、実際のデータで見つかる考えられるすべての順列をコードが処理できるという確信を得るために、テストケースのグラフ構造をどのように選択するのでしょうか。


PS-問題がある場合、グラフのすべてのエッジに「必須」または「不可」というラベルが付けられ、自明な循環はなく、2つのノード間には1つのエッジしかありません。


PPS-この追加の問題ステートメントは、質問の作成者が以下のコメントに最初に投稿したものです。

For all vertices N in forest F, for all vertices M, in F, such that if there are any walks between N and M they all must either use only edges labelled 'conflict' or 'requires'.

18
Sled

1。ランダム化されたテストの生成

グラフを生成するアルゴリズムを記述し、数百(またはそれ以上)のランダムグラフを生成して、それぞれをアルゴリズムに投入します。

興味深いエラーの原因となるグラフのランダムシードを保持し、それらを単体テストとして追加します。

2。難しい部分をハードコードする

あなたが知っているいくつかのグラフ構造はトリッキーであり、すぐにコーディングするか、それらを組み合わせてアルゴリズムにプッシュするコードを書くことができます。

。完全なリストを生成

ただし、「コードが実際のデータにある考えられるすべての順列を処理できる」ことを確認したい場合は、ランダムシードからではなく、すべての順列をたどってこのデータを生成する必要があります。 (これは、地下鉄の鉄道信号システムをテストするときに行われ、テストに時間がかかる莫大な量のケースを提供します。地下鉄の場合、システムは制限されているため、順列の数に上限があります。適用されます)

5
Macke

私たちはすべてのトリッキーなものをカバーしていると考え続け、特定の構造が以前には考えていなかった問題を引き起こすことに気づきました。私たちが考えることができるすべてのトリッキーなテストは、私たちがやっていることです。

それは良いスタートのように思えます。カバレッジベースのテストについてすでに説明したように、 境界値分析等価パーティション化 などのいくつかの古典的な手法を適用しようとしていると思います。良いテストケースを構築するために多くの時間を費やした後、あなた、あなたのチーム、そしてあなたのテスター(もしあれば)がアイデアを使い果たすところにたどり着きます。そして、それがunitテストのパスを残し、できるだけ多くの現実世界のデータでテストを開始する必要があるポイントです。

本番データから非常に多様なグラフを選択する必要があることは明らかです。おそらく、プロセスのその部分だけのために、いくつかの追加のツールまたはプログラムを作成する必要があります。ここでの難しい部分は、おそらくプログラム出力の正確性を検証することです。1万の異なる実世界グラフをプログラムに入れると、プログラムが常に正しい出力を生成するかどうかをどうやって知るのでしょうか。もちろん、手動で確認することはできません。したがって、運が良ければ、依存関係チェックの2番目の非常に単純な実装を作成できる可能性があります。これは、パフォーマンスの期待を満たさない可能性がありますが、元のアルゴリズムよりも簡単に確認できます。また、多くの妥当性チェックをプログラムに直接統合するようにしてください(たとえば、グラフ検索がグラフのすべてのノードとすべてのエッジにヒットする場合は、簿記を作成します)。

最後に、すべてのテストがバグの存在を証明できるだけであり、バグの不在は証明できないことを受け入れることを学びます。

5
Doc Brown

この場合、十分なテストを行うことはできません。実際のデータやファジングさえもありません。 100%のコードカバレッジまたは100%のパスカバレッジでは、再帰関数をテストするには不十分です。

再帰関数は正式な証明に耐えるか(この場合はそれほど難しくありません)、そうではありません。コードがアプリケーション固有のコードと絡みすぎて副作用を排除できない場合は、そこから始めます。

アルゴリズム自体は単純なフラッディングアルゴリズムのように聞こえます。単純な広範な最初の検索に似ており、すべてのノードから実行される訪問済みノードのリストと交差してはならないブラックリストが追加されています。

foreach nodes as node
    foreach nodes as tmp
        tmp.status = unmarked

    tovisit = []
    tovisit.Push(node)
    node.status = required

    while |tovisit| > 0 do
        next = tovisit.pop()
        foreach next.requires as requirement
            if requirement.status = unmarked
                tovisit.Push(requirement)
                requirement.status = required
            else if requirement.status = blacklisted
                return false
        foreach next.collides as collision
            if collision.status = unmarked
                requirement.status = blacklisted
            else if requirement.status = required
                return false
return true

この反復アルゴリズムは、任意のアーティファクトから開始する任意の構造のグラフについて、依存関係が不要であり、同時にブラックリストに登録されるという条件を満たします。これにより、任意のアーティファクトから開始し、開始アーティファクトが常に必要になります。

独自の実装ほど高速である場合とそうでない場合がありますが、すべての場合に終了することを証明できます(外側のループの反復ごとに、各要素はtovisitキューに1回しかプッシュできません)。 、到達可能なグラフ全体(誘導証明)をフラッディングし、各ノードから始めて、アーティファクトを同時に必要とし、ブラックリストに登録する必要があるすべてのケースを検出します。

独自の実装が同じ特性を持っていることを示すことができれば、単体テストを行わずに正確であることを証明できます。キューからのプッシュとポップ、キューの長さのカウント、プロパティの反復などの基本的な方法のみをテストして、副作用がないことを示す必要があります。

EDIT:このアルゴリズムが証明しないことは、グラフにサイクルがないことです。 有向非循環グラフ は十分に研究されたトピックですが、この特性を証明する既製のアルゴリズムを見つけることも簡単です。

ご覧のとおり、ホイールを再発明する必要はまったくありません。

4
Ext3h

「すべての興味深いグラフ構造」や「適切に処理される」などのフレーズを使用しています。これらの構造すべてに対してコードをテストし、コードがグラフを適切に処理するかどうかを判断する方法がない限り、テストカバレッジ分析などのツールしか使用できません。

まず、興味深いグラフ構造をいくつか見つけてテストし、適切な処理を決定して、コードがそれを実行することを確認することをお勧めします。次に、これらのグラフを次のように摂動し始めることができます。a)ルールに違反する壊れたグラフ、またはb)問題のあるそれほど面白くないグラフ。コードがそれらを適切に処理できないかどうかを確認します。

3
BobDalgleish

トポロジカルソート を実行して、成功するかどうかを確認できます。そうでない場合は、少なくとも1つのサイクルがあります。

3
Harry Pehkonen

この種のテストが難しいアルゴリズムになると、テストに基づいてアルゴリズムを構築するTDDに行きます。

要するにTDD、

  • テストを書く
  • それが失敗しているのを見てください
  • コードを変更する
  • すべてのテストに合格していることを確認してください
  • リファクタリング

サイクルを繰り返します

この特定の状況では、

  1. 最初のテストは、アルゴリズムがサイクルを返さない単一ノードグラフです。
  2. 2つ目は、アルゴリズムがサイクルを返さない、サイクルのない3ノードグラフです。
  3. 次に、アルゴリズムがサイクルを返さないサイクルを持つ3つのノードグラフを使用します。
  4. これで、可能性に応じて、もう少し複雑なサイクルに対してテストできます

このメソッドの1つの重要な側面は、可能なステップ(可能なシナリオを単純なテストに分割する場合)のテストを常に追加する必要があり、すべての可能なシナリオをカバーすると、通常、アルゴリズムが自動的に進化することです。

最後に、1つ以上の複雑な統合テストを追加して、予期しない問題(グラフが非常に大きく、再帰を使用する場合のスタックオーバーフローエラー/パフォーマンスエラーなど)がないかどうかを確認する必要があります。

2

最初に述べられ、マッケの返答の下のコメントによって更新された問題の私の理解には、次のものが含まれます。 2)2つのノードが1つのエッジで接続されている場合、他のタイプまたは逆であっても、それらを別のエッジで接続してはなりません。 3)異なるタイプのエッジを混合することにより2つのノード間のパスを構築できる場合、それは無視される状況ではなくエラーです。 4)1つのタイプのエッジを使用して2つのノード間にパスがある場合、他のタイプのエッジを使用してそれらの間に別のパスがない可能性があります。 5)単一のEdgeタイプまたは混合Edgeタイプのいずれかのサイクルは許可されていません(アプリケーションでの推測から、競合のみのサイクルがエラーであるかどうかはわかりませんが、そうでない場合は、この条件を削除できます)。

さらに、使用されているデータ構造は、これらの要件の違反の表現を妨げないものと想定します(たとえば、条件2に違反するグラフは、ノードペアから(タイプ、方向)へのマップで、ノードペアが常に存在する場合は表現できません。最小の番号のノードが最初になります。)特定のエラーを表現できない場合は、検討するケースの数を減らします。

ここで考慮できるグラフは実際には3つあります。1つのEdgeタイプのみの2つと、2つのタイプのそれぞれの1つの和集合によって形成される混合グラフです。これを使用して、いくつかのノードまでのすべてのグラフを体系的に生成できます。最初に、ノードの2つの順序付けられたペア(有向グラフであるため順序付けられたペア)の間に1つ以下のエッジを持つNノードのすべての可能なグラフを生成します。次に、これらのグラフのすべての可能なペアを取得します。1つは依存関係を表し、もう1つは競合を表します。各ペアの結合を形成します。

データ構造が条件2の違反を表現できない場合、依存関係グラフのスペース内に収まる可能性のあるすべての競合グラフを作成するか、その逆を行うことで、考慮すべきケースを大幅に減らすことができます。それ以外の場合は、ユニオンの形成中に条件2の違反を検出できます。

最初のノードから結合されたグラフの幅優先トラバーサルで、到達可能なすべてのノードへのすべてのパスのセットを構築できます。そうすることで、すべての条件の違反をチェックできます(サイクル検出の場合、 Tarjanのアルゴリズム を使用します。)

他のノードからのパスは他の場合には最初のノードからのパスとして表示されるため、グラフが切断されている場合でも、最初のノードからのパスのみを考慮する必要があります。

混合エッジパスがエラーではなく単に無視できる場合(条件3)、依存関係グラフと競合グラフを個別に検討し、ノードの1つに到達可能かどうかが他のノードに到達できないことを確認するだけで十分です。

N-1ノードのグラフの調査で見つかったパスを覚えている場合は、Nノードのグラフを生成および評価するための開始点としてそれらを使用できます。

これはノード間に同じタイプの複数のエッジを生成しませんが、それを行うように拡張できます。ただし、これによりケースの数が大幅に増えるので、テスト対象のコードでこれを表現できないようにしたり、失敗したりして、すべてのケースを事前に除外しておくとよいでしょう。

このようなOracleを書くための鍵は、たとえそれが非効率的であることを意味するとしても、それをできる限り単純にして、信頼性を確立できるようにすることです(理想的には、テストによってバックアップされたその正確さのための引数を通じて)。

テストケースを生成する手段を用意し、作成したOracleを信頼して善と悪を正確に分離したら、これを使用してターゲットコードの自動テストを実行できます。それが実行可能でない場合、次善の策は、特徴的なケースの結果をくまなく調べることです。 Oracleは、検出したエラーを分類し、各タイプのパスの数と長さ、両方のタイプのパスの先頭にノードがあるかどうかなど、受け入れられたケースに関する情報を提供します。これまでに見たことのないケースを探すのに役立ちます。

2
sdenham