確率的単体テスト。 (カオス駆動ユニットテスト。)

多かれ少なかれ複雑なソフトウェアシステムにはすべてエラーが含まれています(独自のものではない場合、使用されるライブラリによって、または使用されるフレームワークの動作パラダイムに対する不正確な認識のために)。

多くの場合、開発段階でユニットテストを使用してシステムをテストします。



そのため、プログラマーは、制御点と境界値でシステムの動作を制御できます。

多くの場合、境界値の誤った処理が問題につながります。 そして、経験豊富なプログラマーはこれを知っており、単体テストを設計するときにこれを考慮します。



単体テストの利便性は、コードを変更することで、予測可能な結果が得られ、利用可能なスクリプトに従って完全に自動化されたテストを実行して、変更によって引き起こされる問題をすばやく特定できることです。



たとえば、IntelとPPCで動作するコードを作成し、Intelで開発しますが、バイト順を考慮します。 次に、ユニットテストを実行して、出力と標準を比較し、矛盾を見つけます-バイトを変更するのを忘れていた場所があれば、それを修正します-すべてが正常です。



ただし、ユーザーは常にランダム性の要素を持ちます。



経験豊富なプログラマーは、品質テスターの才能を組み合わせて、プログラムがリリースされる前に多くのエラーをキャッチできます。



プログラムが「Hello World!」を出力する以上の場合、非表示のエラーはとにかく残ります。

ロジックのエラーでもあります。



プログラムがコンパイルされ、すべてのWarning'iが除去されます...しかし、時々何かがおかしい...ユーザー(太平洋の島の家に遠く住んでいます-彼に来て感じる方法はありません)。 プログラマーは可能な限りすべてを呼び出してテストしましたが、エラーは見つかりませんでした。 どうする?



どのアプリケーションも、論理ネットワークに接続された相互接続されたCコンポーネントの配列と考えることができます。

各コンポーネントは引数Iを入力として受け入れ、出力としてOを出力します。

ジェネレーターをコンパイルしてランダムな引数Iを受け取り、コンポーネントCの入力に送り、Oの出力を確認し、追加のテストでコンポーネントCの状態の整合性を確認します。



そのため、各コンポーネントをランダムなデータセットでテストします。 このメソッドは、コンポーネントで構成される完全なネットワークまたは選択したサブネットに拡張できます。



このようなストレステストでは、ランダムデータが特定の隠れた境界点を確実に模索し、通常の状況では発明したテストが到達しなかった条件の分岐をテストします。



ますます多くのランダムデータの数千回の反復を実行し、このデータに対してコンポーネントからランダム(有効)操作を選択して要求します。



もちろん、最初の段階では、整合性制御サブシステムを構成する必要がありますが、すべてが時計仕掛けのようになります。

障害が検出されると、確率的テストシステムがコマンドログをファイルに保存するため、ランダムな要因の代わりに、エラーにつながる再現可能な非常に具体的なシナリオが得られます。



エラーを探す前に、このスクリプトを最小長に縮小して、エラーが表示されるようにすることができます。

エラーを排除した後、このスクリプトは失敗することなく動作するはずです。また、将来のテストの貯金箱にもう1つのユニットテストがあります。



実際の例として、辞書を作成するときにエラーをキャッチするために確率的テストシステムを使用できます。

次の操作を区別できます。







テストコードフレームワーク(Objective-Cのフラグメント):



 srand(時間(0));

 NSMutableString * log = [NSMutableString string]; //コマンド用
 int prev = -1;
符号なしi
 #define ST_COUNT 2000

 idモデル= [SomeModelFactory createModelObject];

 for(i = 0 ;! status && i <ST_COUNT; i ++)
 {
     int todo;
    する
     {
         todo = rand()%4;
     }
      while(3 == todo && todo == prev);
     prev = todo;

     if(i + 1 == ST_COUNT)//最後の繰り返し。
         todo = 3; //強制的にint。 確認する

    スイッチ(todo)
      {
         case 0://モデルに新しい単語を追加します
         {
             ...
         }
         case 1://既存の単語を設定
         {
             ...
         }
         case 2://単語を削除する
         {
             ...
         }
         case 3://パイント。 確認する
         {
             if(i + 1 == ST_COUNT || rand()%2)
             {
                 ...
                 status = 3; //失敗した場合、エラーコードを設定します
             }
         }
     }
 }

 if(ステータス)
 {
     [ログwriteToFile:@ "/ tmp / commands.log" atomically:YES encoding:NSUTF8StringEncoding error:NULL];
     exit(ステータス);
 }




引数ジェネレーター:



 char genChar()
 {
     //許可された文字
     static char allowed [] = "ABCDEFGHIJKLMNOPQRSTUVWXUZabcdefghijklmnopqrstuvwxyz1234567890 /";
     return allowed [rand()%(sizeof(allowed)-1)];
 }

 NSString * genWord(int min、int max)
 {
     NSMutableString * res = [NSMutableString string];

     if(最大<最小)
         max = min;

     int toGen = min + rand()%(max-min + 1); 

     int i;
     for(i = 0; i <toGen; i ++)
          [res appendFormat:@ "%c"、genChar()];

    解像度を返す;
 }




たとえば、case-1、case-2、...フォルダーのすべてのエラーログを、/ tmpからissuesフォルダーに転送できます。

今後、ケース番号ごとにチェックを実行するため。



(上記の項目から)特定の操作を選択する確率を調整できます。

したがって、整合性チェックはすべての反復ではなく実行でき、障害が発生した場合は、場所をより正確に識別して、各操作の後にチェックを実行できます。

操作中に、通常のNSMutableDictionaryでシステムの状態を独立して維持し、整合性チェック中に、コントロール内の単語数と作成された辞書が一致すること、すべての単語が検出され、テスト例と同じテキストを持つことを確認します。 同じ単語を取得するために、インデックスとインデックスによって単語を抽出できること。



検証システムに影響を与えるために、引数ジェネレーターの出力を変更することができます-より多くの交差を強制します...そしてそれによって特定のイベントの発生の確率をシフトします。

上記の例では、1〜10文字の単語と1〜100文字の記事を生成できます。

条件を変更して、それぞれ1〜10文字の1〜3文字の記事と記事の単語を生成できます。他のエラーの確率論的なフィールドにいる可能性があります。

また、利用可能な操作を選択する確率に影響を与え、辞書を急激に成長させるか、劇的に体重を減らすこともできます。

方向を変える風のように、ランダムに選択確率の政治を変えることさえできます...



実際、確率的なテスト方法のおかげで、プロジェクトで私たちは、誤動作の目に見えるヒントがなかったテスト済みのエンジンで、5つの隠れた非常に洗練されたエラーを見つけました!



確率的テストにより、エンドユーザーのシミュレーションに一歩近づき、隠れた欠陥の検出に役立ちます。



All Articles