C# 如何对特定的预期结果使用Verify()?

C# 如何对特定的预期结果使用Verify()?,c#,unit-testing,autofixture,C#,Unit Testing,Autofixture,上下文 我正在使用AutoFixtue的guardClauseSertion验证被测方法是否在抛出预期的ArgumentNullException(如果任何方法调用参数为null): var fixture = new Fixture(); var assertion = new GuardClauseAssertion(fixture); var myMethodInfo = ... assertion.Verify(myMethodInfo); 测试中的方法如下所示: IEnumerabl

上下文

我正在使用AutoFixtue的
guardClauseSertion
验证被测方法是否在抛出预期的ArgumentNullException(如果任何方法调用参数为null):

var fixture = new Fixture();
var assertion = new GuardClauseAssertion(fixture);
var myMethodInfo = ...
assertion.Verify(myMethodInfo);
测试中的方法如下所示:

IEnumerable<T> MyMethod<T>(IList<T> p1, IList<T> p2, Func<T> p3, string p4)
{
     if (p1 == null || p2 == null...)
     {
          yield break;
     }

     // Return non-empty IEnumerable here...
}
IEnumerable MyMethod(IList p1、IList p2、Func p3、字符串p4)
{
如果(p1==null | | p2==null…)
{
屈服断裂;
}
//在此处返回非空IEnumerable。。。
}
问题

在我的特殊情况下,预期的行为不是抛出
ArgumentNullException
,而是返回空的
IEnumerable
,因此我想根据这个结果进行验证


如何自定义现有的验证默认行为以实现此目标?

GuardClauseSertion检查方法参数是否为null。正如您所提到的,您的特定案例不适合该行为,因此没有必要使用
GuardClauseAssertion
。只需创建一个涵盖您的特殊行为的特殊测试


同时,如果使用程序集-或,则可能需要从验证列表中排除特定方法。AFAIR,AutoFixture不提供任何API;您应该只使用LINQ。

像上面的
MyMethod
这样的方法不是
GuardClauseAssertion
最初设计的目的

最初,AutoFixture是作为一个助手库开发的,用于进行测试驱动开发(TDD),其目的是获取有关对象设计和实现细节的快速反馈。因为它是一个固执己见的库,AutoFixture倾向于增强TDD反馈。当使用AutoFixture测试SUT变得很尴尬时,这可能表明存在更深层次的设计问题。 我倾向于认为这里的情况就是这样

空值是一个错误 比如,我认为空引用是一个错误。它们是C#的一部分,因此我们必须处理它们的潜在存在,但我认为这不应该改变
null
不是有效值的基本原则。使用空参数调用方法是错误的

当客户机开发人员看到方法签名时,如

public IEnumerable<Foo> Sut(Bar bar, Baz baz, Qux qux)
使用
空条调用
反例
可以吗?一件空的紧身衣怎么样?如果
bar
garply
null
,结果可能没问题,但是如果
corge
null
,它将抛出一个
ArgumentNullException

抽象设计 当一个完整的代码库/API是这样设计的(即没有一致性)时,客户机开发人员获得诸如哪些参数可以为null之类问题答案的唯一方法是什么?是阅读实现代码

这减慢了开发速度,因为每个人都不能依赖抽象,而只能在实现细节级别上工作。它还使代码库变得脆弱,因为客户端开发人员最终编写的代码取决于实现细节。更改实现,客户端代码将中断

解决这个问题的一个方法是完全消除这个问题。与让客户机开发人员怀疑哪些参数可以为null不同,您可以通过全面概括,声明在代码库中,
null
永远不是一个可接受或有效的值

因此,每当一个方法收到一个
null
参数时,它都应该抛出一个
ArgumentNullException
,这就是
guardClauseSertion
设计用来验证的

波斯特尔定律 那么呢?这不是说我们接受的东西要自由吗?如果我们可以通过返回一个空的
IEnumerable
来正确处理
null p1
,我们不应该这样做吗

是的,如果这真的是合适的设计,那么这就是Postel定律所说的,但我认为它应该在设计中浮出水面。如果参数是可选的,则应使用方法重载对此进行建模,而不是通过允许
null
参数:

IEnumerable<T> MyMethod<T>()
IEnumerable<T> MyMethod<T>(IList<T> p1)
IEnumerable<T> MyMethod<T>(IList<T> p2)
IEnumerable<T> MyMethod<T>(IList<T> p3)
IEnumerable<T> MyMethod<T>(IList<T> p4)
IEnumerable<T> MyMethod<T>(IList<T> p1, IList<T> p2)
// etc...
IEnumerable<T> MyMethod<T>(IList<T> p1, IList<T> p2, Func<T> p3)
IEnumerable<T> MyMethod<T>(IList<T> p1, IList<T> p2, Func<T> p3, string p4)
IEnumerable MyMethod()
IEnumerable MyMethod(IList p1)
IEnumerable MyMethod(IList p2)
IEnumerable MyMethod(IList p3)
IEnumerable MyMethod(IList p4)
IEnumerable MyMethod(IList p1,IList p2)
//等等。。。
IEnumerable MyMethod(IList p1、IList p2、Func p3)
IEnumerable MyMethod(IList p1、IList p2、Func p3、字符串p4)
仅从方法签名就可以清楚地看出,所有参数都是可选的,这使客户机开发人员不必通读相关方法的实现细节

这可能会为实现工作增加一些工作,但会节省以后的工作。由于大多数代码的读取量大于写入量,因此这将优化关键路径(即读取)


在这种特殊情况下,由于所有四个参数都是可选的,因此有很多组合可用。在这种情况下,我会考虑将设计重构成一个流畅的生成器,但是在我这样做之前,我会认真考虑如果我真的需要对一个方法的很多参数。也许会帮助你实现你想要实现的目标。@JustinasMarozas非常感谢。在那个测试中,delegatingbehaviorexpection是helper测试类,但无论如何,我可以从代码开始?请分享一个演示问题的例子——在本例中是您希望测试的方法。好吧,我认为这是一个“习惯用法”,所以请不要这么快放弃,值得给一个机会,不要重新发明轮子,重新使用AutoFixture中的现有基础设施,然后写一行断言,而不是一堆复制粘贴的测试。都是真的。但是,您的问题得到了非常详细的回答:“空参数是否被视为错误?”,这不是问题所在。我们知道答案。是的
IEnumerable<T> MyMethod<T>()
IEnumerable<T> MyMethod<T>(IList<T> p1)
IEnumerable<T> MyMethod<T>(IList<T> p2)
IEnumerable<T> MyMethod<T>(IList<T> p3)
IEnumerable<T> MyMethod<T>(IList<T> p4)
IEnumerable<T> MyMethod<T>(IList<T> p1, IList<T> p2)
// etc...
IEnumerable<T> MyMethod<T>(IList<T> p1, IList<T> p2, Func<T> p3)
IEnumerable<T> MyMethod<T>(IList<T> p1, IList<T> p2, Func<T> p3, string p4)