21

大規模でかなり複雑なステート マシンを継承しました。31 の可能な状態があり、すべてが本当に必要です (大きなビジネス プロセス)。次の入力があります。

  • Enum: 現在の状態 (つまり 0 -> 30)
  • 列挙型: ソース (現在 2 つのエントリのみ)
  • ブール値: リクエスト
  • ブール値: タイプ
  • Enum: ステータス (3 つの状態)
  • Enum: 処理 (3 つの状態)
  • ブール値: 完了

各状態は異なるため、個別の状態マシンに分割することは現実的ではないようです。最も一般的な入力のテストを作成しました。入力ごとに 1 つのテストがあり、State を除くすべての入力は一定です。

[Subject("Application Process States")]
public class When_state_is_meeting2Requested : AppProcessBase
{
    Establish context = () =>
    {
        //Setup....
    };

    Because of = () => process.Load(jas, vac);

    It Current_node_should_be_meeting2Requested = () => process.CurrentNode.ShouldBeOfType<meetingRequestedNode>();
    It Can_move_to_clientDeclined = () => Check(process, process.clientDeclined);
    It Can_move_to_meeting1Arranged = () => Check(process, process.meeting1Arranged);
    It Can_move_to_meeting2Arranged = () => Check(process, process.meeting2Arranged);
    It Can_move_to_Reject = () => Check(process, process.Reject);
    It Cannot_move_to_any_other_state = () => AllOthersFalse(process);
}

各状態と一連の入力に対して出力がどうあるべきかを完全に確信している人はいません。そのためのテストを書き始めました。ただし、4320テスト (30 * 2 * 2 * 2 * 3 * 3 * 2) のようなものを記述する必要があります。

ステート マシンをテストするための提案はありますか?


編集:私はすべての提案で遊んでおり、最も効果的なものを見つけたら答えをマークします。

4

9 に答える 9

6

問題はわかりますが、ロジックを分割してみることは間違いありません。

私の目の大きな問題領域は次のとおりです。

  • 31 の可能な状態があります。
  • 次の入力があります。
    • Enum: 現在の状態 (つまり 0 -> 30)
    • 列挙型: ソース (現在 2 つのエントリのみ)
    • ブール値: リクエスト
    • ブール値: タイプ
    • Enum: ステータス (3 つの状態)
    • Enum: 処理 (3 つの状態)
    • ブール値: 完了

あまりにも多くのことが起こっています。入力によってコードのテストが難しくなっています。これをより管理しやすい領域に分割するのは面倒だとおっしゃいましたが、これほど多くのロジックを外出先でテストするのは、それほど苦痛ではないにしても同様です。あなたの場合、各単体テストはあまりにも多くのことをカバーしています。

大規模なメソッドのテストについて私が尋ねたこの質問は、本質的に似ています。私のユニットが単に大きすぎることがわかりました。それでも多くのテストが必要になりますが、それらはより小さくなり、管理しやすくなり、カバーする領域が少なくなります。ただし、これは良いことです。

レガシ コードのテスト

ペックスをチェックしてください。あなたはこのコードを継承したと主張しているので、これは実際にはテスト駆動開発ではありません。単体テストで各側面をカバーしたいだけです。今後の作業は検証されるため、これは良いことです。個人的にはまだ Pex を適切に使用していませんが、ビデオを見て驚きました。基本的に、入力に基づいて単体テストを生成します。この場合、それは有限状態マシン自体になります。十分に考えていないテストケースが生成されます。確かにこれは TDD ではありませんが、このシナリオではレガシー コードをテストするのが理想的です。

テスト カバレッジを取得したら、既存の機能を壊さないように、リファクタリングを開始したり、優れたテスト カバレッジの安全性を確保して新しい機能を追加したりできます。

于 2010-04-22T13:23:00.100 に答える
4

オールペアテスト

テストする組み合わせの量を制限し、最も重要な組み合わせがカバーされていることを合理的に保証するには、すべてのペアのテストを確認する必要があります。

すべてのペアのテストの背後にある理由は次のとおりです。プログラムの最も単純なバグは、通常、単一の入力パラメーターによってトリガーされます。バグの次の最も単純なカテゴリは、パラメータのペア間の相互作用に依存するバグで構成され、すべてのペアのテストで検出できます。13つ以上のパラメータ間の相互作用を伴うバグは、次第に一般的ではなくなります2が、同時に、次第に高価になります。可能なすべての入力の徹底的なテストを限界として持つ徹底的なテストによって見つけること。

また、追加情報とツールとしてのall-pairとpictの両方へのリンクについては、ここで以前の回答(恥知らずなプラグ)を参照してください。

Pictモデルファイルの例

与えられたモデルは、入力パラメーターのすべてのペアをカバーする93のテストケースを生成します。

#
# This is a PICT  model for testing a complex state machine at work 
#

CurrentState  :0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30
Source        :1,2
Request       :True, False
Type          :True, False
Status        :State1, State2, State3
Handling      :State1, State2, State3
Completed     :True,False

#
# One can add constraints to the model to exclude impossible 
# combinations if needed.
#
# For example:
# IF [Completed]="True" THEN CurrentState>15;
#

#
# This is the PICT output of "pict ComplexStateMachine.pict /s /r1"
#
# Combinations:    515
# Generated tests: 93
于 2010-04-30T06:56:12.433 に答える
3

SpecExplorerまたはNModelを使用します。

于 2010-04-23T09:25:37.700 に答える
3

私は医療機器の有限状態マシンを構築しました。FSM は、私が定義した XML フォーマットを介して構成可能でした。

ステート マシンを定義するには、ステート マップを使用したデジタル回路設計の経験に頼る必要があります。

私がターンパイク遷移マップと呼んでいるものを使用する必要があります。米国東海岸では、ほとんどの高速道路はターンパイクと呼ばれています。ターンパイク当局は、ターンパイク通行料の価格設定マップを発行します。料金所に 50 の出口がある場合、料金マップには 50 行 x 50 列のテーブルがあり、行と列の両方で出口を網羅的に一覧表示します。20 番出口と 30 番出口の通行料金を調べるには、20 行目と 30 列目の交点を探すだけです。

30 ステートのステート マシンの場合、ターンパイク トランジション マップは 30 x 30 のマトリックスになり、可能な 30 ステートすべてが行と列ごとにリストされます。行を CURRENT 状態に、列を NEXT 状態に決定しましょう。

交差する各セルには、現在の状態 (行) から次の状態 (列) への遷移の「価格」がリストされます。ただし、単一の $ 値の代わりに、セルは Inputs テーブルの行を参照します。これは遷移 ID と呼ぶことができます。

私が開発した医療機器 FSM には、String、enum、int などの入力がありました。入力テーブルには、これらの入力刺激が列ごとにリストされていました。

入力テーブルを作成するには、考えられるすべての入力の組み合わせをリストする簡単なルーチンを作成します。しかし、テーブルは巨大になります。あなたの場合、テーブルには4320行があり、したがって4320の遷移IDがあります。ただし、プログラムでテーブルを生成したため、面倒なテーブルではありません。私の場合、単純な JSP を作成して、トランジション入力テーブル (およびターンパイク テーブル) をブラウザーに一覧表示するか、csv としてダウンロードして MS Excel に表示しました。

これら 2 つのテーブルの作成には 2 つの方向性があります。

  1. ターンパイク テーブルを構築する設計方向。すべての可能なトランジションを作成し、到達不可能なトランジションをグレー表示します。次に、遷移 ID として行番号を使用して、到達可能な遷移ごとにのみ予想されるすべての入力の入力テーブルを作成します。各遷移 ID は、ターンパイク遷移マップのそれぞれのセルに転写されます。ただし、FSM はスパース マトリックスであるため、ターンパイク トランジション マップのセルですべてのトランジション ID が使用されるわけではありません。また、同じ遷移条件が複数の状態変化のペアに適用される可能性があるため、遷移 ID を何度も使用できます。

  2. テストの方向は逆で、入力テーブルを作成します。徹底的な遷移テストの一般的なルーチンを作成する必要があります。
    このルーチンは、最初に遷移シーケンス テーブルを読み取り、ステート マシンをエントリポイント状態にして、テスト サイクルを開始します。各 CURRENT 状態で、4320 のすべての遷移 ID を実行する準備ができています。ターンパイク遷移マップの CURRENT 状態の各行には、有効な NEXT 状態の限られた数の列があります。

未使用の遷移 ID が CURRENT 状態に影響を与えないように、ルーチンが Inputs テーブルから読み取る 4320 行すべての入力を循環させたい場合があります。すべての効果的なトランジション ID が有効なトランジションであることをテストします。

しかし、それはできません。有効な遷移がポンピングされると、マシンの状態が NEXT 状態に変更され、以前の CURRENT 状態の残りの遷移 ID のテストを完了できなくなるためです。マシンの状態が変化したら、もう一度遷移 ID 0 からテストを開始する必要があります。

遷移パスは、周期的または不可逆的であるか、パスに沿って周期的セクションと不可逆セクションの組み合わせを持つことができます。

テスト ルーチン内では、その状態に送り込まれた最後の遷移 ID を記憶するために、各状態のレジスタが必要です。テストが有効な遷移 ID に到達するたびに、その遷移 ID がそのレジスターに残されます。そのため、サイクルを完了して既に通過した状態に戻ると、レジスタに格納されているものよりも大きい次の遷移 ID で反復を開始します。

あなたのルーチンは、移行パスの不可逆的なセクションを処理する必要があります。マシンが最終状態になった場合、エントリ ポイントの状態からテストを再開し、次の移行 ID から 4320 の入力を繰り返します。州のために。このようにして、マシンのすべての可能な遷移パスを徹底的に発見することができます.

幸いなことに、FSM は有効な遷移のスパース マトリックスです。これは、徹底的なテストでは、遷移 ID の数 x 可能な状態の 2 乗の数の完全な組み合わせを消費しないためです。ただし、従来の FSM を扱っている場合、視覚的または温度状態をテスト システムにフィードバックすることができず、各状態を視覚的に監視する必要がある場合、問題が発生します。それは醜いでしょうが、効果的なトランジションのみを視覚的に行う機器の追加テストに 2 週間を費やしました.

FSM で単純なリセットと遷移 ID の適用でエントリポイントに到達できる場合は、(テスト ルーチンがマシンを目的のエントリポイントに移動するために読み取るエントリ ポイントの状態ごとに) 遷移シーケンス テーブルは必要ない場合があります。エントリーポイント状態に。しかし、頻繁に状態ネットワークの中に入ってそこからテストを開始する必要があるため、ルーチンで遷移シーケンス テーブルを読み取ることができると便利です。

遷移マップと状態マップの使用法について理解しておく必要があります。これは、マシンのすべての可能性のある文書化されていない状態を検出し、ユーザーが実際にそれらをグレー表示したい場合 (遷移が無効になり、状態が到達不能になった場合) にインタビューするのに非常に役立つためです。

私が持っていた利点は、それが新しい機器であり、xml ファイルを読み取るようにステート マシン コントローラーを設計する選択肢があったことです。未使用のトランジション ID は本当に効果がないことを確認できました。

有限状態マシン コントローラーの Java リストについては、http://code.google.com/p/synthfuljava/source/browse/#svn/trunk/xml/org/synthfulを参照してください。テストルーチンは含まれていません。

于 2010-04-23T11:58:05.757 に答える
3

このようなFSMをテストする簡単な方法は、機械学習技術を使用したり、ブルートフォースを使用したりして、非常に衒学的で証明を採用することなしには考えられません。

ブルート フォース: 4320 のすべてのテスト ケースを宣言的な方法で生成するものを作成します。ほとんどの場合、データは正しくありません。これを CSV ファイルに入れてから、NUnits パラメータ テストなどを使用してすべてのテスト ケースをロードすることをお勧めします。現在、これらのテスト ケースのほとんどは失敗するため、宣言ファイルを手動で更新して正しいものにし、テスト ケースのサンプルをランダムに取得して修正する必要があります。

機械学習手法: ベクトル マシンまたは MDA アルゴリズム/ヒューリスティックを使用して、上記のサンプルから学習を試み、ML プログラムに FSM を教えることができます。次に、すべての 4320 入力に対してアルゴリズムを実行し、2 つの値が一致しない場所を確認します。

于 2010-04-22T14:00:02.533 に答える
3

関数 sum(int a, int b) を「完全に」テストするには、いくつのテストが必要だと思いますか? C#では、18446744056529682436のテストのようなものになります...あなたの場合よりもはるかに悪いです。

次のことをお勧めします。

  1. ほとんどの可能性のある状況、境界条件をテストします。
  2. SUT のいくつかの重要な部分を個別にテストします。
  3. QA または本番環境でバグが見つかった場合は、テスト ケースを追加します。

この特定のケースでは、システムがある状態から別の状態に切り替わる方法をテストするのが最善の方法です。DSL を作成してステート マシンをテストし、それを使用して最も頻繁に使用されるケースを実装します。例えば:

Start
  .UploadDocument("name1")
  .SendDocumentOnReviewTo("user1")
  .Review()
  .RejectWithComment("Not enough details")
  .AssertIsCompleted()

フローの簡単なテストを作成する例は次のとおりです: http://slmoloch.blogspot.com/2009/12/design-of-selenium-tests-for-aspnet_09.html

于 2010-04-23T05:12:29.890 に答える
1

Model Based Testingの調査を検討してください。このような状況でのテスト生成に役立つツールがいくつかあります。私は通常MBTをお勧めします。

于 2010-04-23T12:38:38.660 に答える
1

要件に基づいてテストします。Completed が true のときに特定の状態が特定の別の状態に移動する必要がある場合は、他の入力のすべての組み合わせを自動的に循環するテストを作成して (これは for ループのカップルにすぎないはずです)、他の入力が正しいことを証明します。正しく無視されます。最終的には、遷移アークごとに 1 つのテストを行う必要があります。これは、4000 ではなく、100 または 150 程度のテストになると見積もっています。

于 2010-04-23T05:22:11.633 に答える
0

カバレッジ テストを伴う総当りは、非常に始まりのようです。

于 2010-04-22T16:12:56.073 に答える