如何使用Verify()获取特定的预期结果?

时间:2018-01-17 10:32:11

标签: c# unit-testing autofixture

上下文

我正在使用AutoFixtue的GuardClauseAssertion来验证测试中的方法是否抛出了预期的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...
}

问题

在我的特定情况下,预期的行为不是抛出ArgumentNullException,而是返回空IEnumerable<T>,所以我想验证此结果。

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

2 个答案:

答案 0 :(得分:1)

上述MyMethod之类的方法不是GuardClauseAssertion最初旨在涵盖的方法。

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

Nulls是一个错误

sir Tony Hoare一样,我认为空引用是一个错误。他们是C#的一部分,所以我们必须处理他们的潜在存在,但我不认为它应该改变null不是有效价值的基本原则。调用带有null参数的方法是错误的。

当客户端开发人员查看类似

的方法签名时
public IEnumerable<Foo> Sut(Bar bar, Baz baz, Qux qux)

在那个级别,没有足够的信息使他或她能够推断出null是否被允许用于某个特定的论点。在这种特定情况下,您可能已经知道了。他们都被允许null,但客户开发人员(同事或将来自己)可能并不知道所有方法。

考虑另一种方法:

public Foo CounterExample(Bar bar, Corge corge, Garply garply)

CounterExample来呼叫null bar是否可以?怎么样null corge?如果bargarplynull,可能会发现它没问题,但如果corgenull,那么它就可以了抛出ArgumentNullException

使用抽象设计

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

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

解决该问题的一种方法是完全删除问题。您可以通过全面的泛化声明在代码库中null永远不是可接受的或有效的值,而不是让客户端开发人员想知道哪些参数可以为null?

因此,只要方法收到null参数,它就会抛出ArgumentNullException,而GuardClauseAssertion旨在验证。

Postel的法律

那么Postel's law怎么样?它不是说我们接受的是自由主义吗?如果我们可以通过返回空null p1来正确处理IEnumerable<T>,那么我们不应该这样做吗?

是的,如果这确实是合适的设计,那么这就是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)

这从单独的方法签名中可以清楚地看出,所有参数都是可选的,这使得客户端开发人员不必阅读相关方法的实现细节。

这可能会为实施工作增加更多工作,但稍后会节省工作。由于大多数代码的读取次数超过了它的编写范围,因此优化了关键路径(即读取)。

在这种特殊情况下,由于所有四个参数都是可选的,因此可以使用相当多的组合。在这种情况下,我考虑将设计重构为Fluent Builder,但在我这样做之前,如果我真的需要一个方法的那么多参数,我就会认真地重新考虑。 / p>

答案 1 :(得分:0)

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

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