0

私は FsCheck とランダム プロパティ ベースのテストへのパラダイム シフトを試みています。列挙しきれないほど多くのテスト ケースを含む複雑なビジネス ワークフローがあり、ビジネス ロジックは新しい機能が追加されて移動するターゲットです。

背景: マッチメイキングは、エンタープライズ リソース プランニング (ERP) システムで非常に一般的な抽象概念です。注文処理、サプライ チェーン ロジスティクスなど

例: C と P が与えられた場合、2 つが一致するかどうかを判断します。任意の時点で、一部の P は決してMatch-able ではなく、一部の C は決してMatch-able ではありません。それぞれに、試合の対象と見なされるかどうかを示すステータスがあります。

public enum ObjectType {
  C = 0,
  P = 1
}

public enum CheckType {
  CertA = 0,
  CertB = 1
}
public class Check {
  public CheckType CheckType {get; set;}
  public ObjectType ObjectType {get; set;}
  /* If ObjectType == CrossReferenceObjectType, then it is assumed to be self-referential and there is no "matching" required. */
  public ObjectType CrossReferenceObjectType {get; set;}
  public int ObjectId {get; set;}
  public MatchStatus MustBeMetToAdvanceToStatus {get; set;}
  public bool IsMet {get; set;}
}

public class CStatus {
  public int Id {get; set;}
  public string Name {get; set;}
  public bool IsMatchable {get; set;}
}

public class C {
  public int Id {get; set;}
  public string FirstName {get; set;}
  public string LastName {get; set;}
  public virtual CStatus Status {get;set;}
  public virtual IEnumerable<Check> Checks {get; set;}
  C() {
    this.Checks = new HashSet<Check>();
  }
}

public class PStatus {
  public int Id {get; set;}
  public string Name {get; set;}
  public bool IsMatchable {get; set;}
}

public class P {
  public int Id {get; set;}
  public string Title {get; set;}
  public virtual PStatus Status { get; set;}
  public virtual IEnumerable<Check> Checks {get; set;}
  P() {
    this.Checks = new HashSet<Check>();
  }
}

public enum MatchStatus {
  Initial = 0,
  Step2 = 1,
  Step3 = 2,
  Final = 3,
  Rejected = 4
}

public class Match {
  public int Id {get; set;}
  public MatchStatus Status {get; set;}
  public virtual C C {get; set;}
  public virtual P P {get; set;}
}

public class MatchCreationRequest {
  public C C {get; set;}
  public P P {get; set;}
}

public class MatchAdvanceRequest {
  public Match Match {get; set;}
  public MatchStatus StatusToAdvanceTo {get; set;}
}

public class Result<TIn, TOut> {
  public bool Successful {get; set;}
  public List<string> Messages {get; set;}
  public TIn InValue {get; set;}
  public TOut OutValue {get; set;}
  public static Result<TIn, TOut> Failed<TIn>(TIn value, string message)
  {
    return Result<TIn, TOut>() {
      InValue = value,
      Messages = new List<string>() { message },
      OutValue = null,
      Successful = false
    };
  }
  public Result<TIn, TOut> Succeeded<TIn, TOut>(TIn input, TOut output, string message)
  {
    return Result<TIn, TOut>() {
      InValue = input,
      Messages = new List<string>() { message },
      OutValue = output,
      Successful = true
    };
  }
}

public class MatchService {
   public Result<MatchCreationRequest> CreateMatch(MatchCreationRequest request) {
     if (!request.C.Status.IsMatchable) {
       return Result<MatchCreationRequest, Match>.Failed(request, "C is not matchable because of its status.");
     }
     else if (!request.P.Status.IsMatchable) {
       return Result<MatchCreationRequest, Match>.Failed(request, "P is not matchable because of its status.");
     }
     else if (request.C.Checks.Any(ccs => cs.ObjectType == ObjectType.C && !ccs.IsMet) {
       return Result<MatchCreationRequest, Match>.Failed(request, "C is not matchable because its own Checks are not met.");
     } else if (request.P.Checks.Any(pcs => pcs.ObjectType == ObjectType.P && !pcs.IsMet) {
       return Result<MatchCreationRequest, Match>.Failed(request, "P is not matchable because its own Checks are not met.");
     }
     else if (request.P.Checks.Any(pcs => pcs.ObjectType == ObjectType.C && C.Checks.Any(ccs => !ccs.IsMet && ccs.CheckType == pcs.CheckType))) {
       return Result<MatchCreationRequest, Match>.Failed(request, "P's Checks are not satisfied by C's Checks.");
     }
     else {
       var newMatch = new Match() { C = c, P = p, Status = MatchStatus.Initial }
       return Result<MatchCreationRequest, Match>.Succeeded(request, newMatch, "C and P passed all Checks.");
     }
   }
}

ボーナス: 単純な「ブロック マッチ」ステータスを超えて、C と P にはそれぞれ一連のチェックがあります。CがMatch-edであるためにいくつかのチェックが真でなければならず、PがMatch-edであるためにいくつかのチェックが真である必要があり、CのいくつかのチェックがPのチェックに対してクロスチェックされなければなりません。 FsCheck を使用したテストは、(a) 製品に追加された新機能の例であり、(b) 次のようなテスト (ユーザー操作) を作成できる可能性があるため、大きな利益をもたらします。

  1. 作成
  2. 作成後、パイプラインを進めます
  3. 前に戻る (許可されている場合と許可されていない場合はいつですか? 例: 有料注文はおそらく購入承認ステップに戻ることはできません)
  4. パイプラインの途中で (チェックなど) を追加/削除する
  5. 同じ C と P の Match を 2 回 (たとえば、PLINQ と同時に) 作成するように依頼した場合、重複が作成されますか? (ユーザーに返されるメッセージは何ですか?)

私が苦労していること:

  1. FsCheck のテスト データはどのように生成すればよいですか? 正しい方法は、Match を作成するための C と P の個別の可能な組み合わせをすべて定義し、それらをモデルベースのテストの「事前条件」にし、事後条件を Match を作成するかどうかにすることだと思います、...
  2. それは本当に正しいアプローチですか?ランダム化されたプロパティ ベースのテスト ツールとしては決定論的すぎると感じます。このような状況で FsCheck を使用するのは過剰設計ですか? すると、シード値を無視して決定論的なテスト データのストリームを返すデータ ジェネレーターを持っているかのようになります。
  3. この時点で、FsCheck ジェネレーターは、xUnit.net や AutoPOCO のようなものを使用することと何か違いがありますか?
4

1 に答える 1

3

決定論的な (網羅的な) テスト データを生成したい場合、FsCheck はあまり適していません。基本的な仮定の 1 つは、状態空間が大きすぎて実現できないというものです。そのため、ランダムですが、ガイド付き生成はより多くのバグを見つけることができます (これを証明するのは困難ですが、この仮定を裏付ける研究がいくつかあります。すべての状況でそれが最善のアプローチであると言っているわけではありません)。

あなたが書いたことから、CreateMatchメソッドはプロパティをテストしたいものだと思います。その場合は、MatchCreationRequest. ジェネレーターは構成するので、これはあなたの場合かなり長くなります (それらはすべて可変型であるため、リフレクションベースの自動ジェネレーターはありません) が、簡単でもあります - それは常に同じパターンです:

var genCStatus = from id in Arb.Generate<int>()
                 from name in Arb.Generate<string>()
                 from isMatchable in Arb.Generate<bool>()
                 select new CStatus { Id = id, Name = name, IsMatchable = isMatchable };

var genC = from status in genCStatus
           ...
           select new C { ... }

これらを取得したら、テストするプロパティを記述することは比較的簡単ですが、この例では、少なくとも実装自体よりも大幅に単純ではありません。

一例は次のとおりです。

//check that if C or P are not matchable, the result is failed.
Prop.ForAll(genC.ToArbitrary(), genP.ToArbitrary(), (c, p) => {
    var result = MatchService.CreateMatch(new MatchCreationRequest(c, p));
    if (!c.IsMatchable || !p.IsMatchable) { Assert.IsFalse(result.Succesful); }
}).QuickCheckThrowOnFailure();
于 2015-09-20T08:46:57.257 に答える