私は 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) 次のようなテスト (ユーザー操作) を作成できる可能性があるため、大きな利益をもたらします。
- 作成
- 作成後、パイプラインを進めます
- 前に戻る (許可されている場合と許可されていない場合はいつですか? 例: 有料注文はおそらく購入承認ステップに戻ることはできません)
- パイプラインの途中で (チェックなど) を追加/削除する
- 同じ C と P の Match を 2 回 (たとえば、PLINQ と同時に) 作成するように依頼した場合、重複が作成されますか? (ユーザーに返されるメッセージは何ですか?)
私が苦労していること:
- FsCheck のテスト データはどのように生成すればよいですか? 正しい方法は、Match を作成するための C と P の個別の可能な組み合わせをすべて定義し、それらをモデルベースのテストの「事前条件」にし、事後条件を Match を作成するかどうかにすることだと思いますが、...
- それは本当に正しいアプローチですか?ランダム化されたプロパティ ベースのテスト ツールとしては決定論的すぎると感じます。このような状況で FsCheck を使用するのは過剰設計ですか? すると、シード値を無視して決定論的なテスト データのストリームを返すデータ ジェネレーターを持っているかのようになります。
- この時点で、FsCheck ジェネレーターは、xUnit.net や AutoPOCO のようなものを使用することと何か違いがありますか?