我正在尝试将范式转变为 FsCheck 和基于随机属性的测试。我有复杂的业务工作流程,其中包含的测试用例比我可能列举的要多,而且业务逻辑是一个不断变化的目标,其中添加了新功能。
背景:匹配是企业资源计划(ERP)系统中非常常见的抽象。订单履行、供应链物流等
示例:给定一个 C 和一个 P,确定两者是否匹配。在任何给定的时间点,一些 P永远无法匹配,而一些 C永远无法匹配。每个人都有一个状态,表明他们是否可以考虑参加比赛。
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,一些检查必须为真,对于匹配的 P,一些检查必须为真,并且 C 的一些检查必须与 P 的检查进行交叉检查。这就是我怀疑基于模型的地方使用 FsCheck 进行测试将带来巨大的收益,因为 (a) 它是添加到产品中的新功能的示例 (b) 我可以编写测试(用户交互),例如:
- 创造
- 创建后,通过管道向前移动
- 向后移动(何时允许与不允许?例如:付费订单可能无法返回采购批准步骤)
- 在管道中间添加/删除东西(如检查)
- 如果我要求为相同的 C 和 P 创建两次匹配(例如,与 PLINQ 同时),我会创建重复项吗?(什么消息会返回给用户?)
我正在努力解决的事情:
- 我应该如何为 FsCheck 生成测试数据?我认为正确的方法是定义 Cs 和 Ps 的所有离散可能组合来创建匹配,并将它们作为我基于模型的测试的“前提条件”,后置条件是是否应该创建匹配,但是...
- 这真的是正确的方法吗?对于基于随机属性的测试工具来说,这感觉太确定了。在这种情况下甚至使用 FsCheck 是否过度设计?然后,就好像我有一个忽略种子值并返回确定性测试数据流的数据生成器。
- 在这一点上,FsCheck 生成器与仅使用 xUnit.net 和 AutoPOCO 之类的东西有什么不同吗?