【发布时间】:2015-09-20 16:46:57
【问题描述】:
我正在尝试将范式转变为 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 之类的工具有何不同?
【问题讨论】:
标签: c# fscheck property-based-testing