C# · 12月 22, 2021

c# – 当模拟不起作用时如何保持我的单元测试DRY?

编辑:

似乎通过尝试为我自己的问题提供一些解决方案,我模糊了整个问题.所以我正在修改这个问题.

假设我有这个课程:

public class ProtocolMessage : IMessage{ public IHeader GetProtocolHeader(string name) { // Do some logic here including returning null // and throw exception in some cases return header; } public string GetProtocolHeaderValue(string name) { IHeader header = GetProtocolHeader(name); // Do some logic here including returning null // and throw exception in some cases return value; }}

实际上,在这些方法中发生的事情并不重要.重要的是我有多个单元测试来覆盖GetProtocolHeader方法,涵盖所有情况(返回正确的标头,null或异常),现在我正在为GetProtocolHeaderValue编写单元测试.

如果GetProtocolHeaderValue依赖于外部依赖,我将能够模拟它并注入它(我正在使用Moq NUnit).然后我的单元测试将测试期望外部依赖被调用并返回预期值.外部依赖将通过自己的单元测试进行测试,我会完成但是如何在这个示例中正确进行,其中方法不是外部依赖?

澄清问题:

我相信我的GetProtocolHeaderValue测试套件必须测试GetProtocolHeader返回header,null或exception的情况.所以主要的问题是:我应该编写测试,其中GetProtocolHeader将被真正执行(一些测试将被复制,因为他们将测试相同的代码作为GetProtocolHeader本身的测试)或者我应该使用@adrift和@Eric Nicholson所描述的模拟方法不会运行真正的GetProtoclHeader但只是配置mock以在调用此方法时返回header,null或exception?

解决方法 在调用GetProtocolHeaderValue时,您是否真的需要知道它是否称为GetProtocolHeader?

当然,知道它从正确的标题中获取正确的值就足够了.实际如何获得它与单元测试无关.

您正在测试功能单元,GetProtocolHeaderValue的功能单元是它是否返回给定标题名称的预期值.

确实,您可能希望防止不适当的缓存或交叉污染或从不同的标头中获取值,但我认为测试它已调用GetProtocolHeader是最好的方法.你可以推断它以某种方式从它返回标题的预期值的事实中获取了正确的标题.

只要您确保测试和测试数据的方式确保重复的标题不会掩盖错误,那么一切都应该很好.

编辑更新的问题:

>如果GetProtocolHeader快速,可靠并且是幂等的,那么我仍然认为没有必要嘲笑它.这三个方面中的任何一个方面的缺陷是(IMO)嘲弄的主要原因.

如果(我怀疑问题标题),你想要模仿它的原因是设置一个适当的状态以返回一个实际值所需的序言太冗长了,你宁愿不在两个测试中重复它,为什么不在设置阶段这样做呢?
>良好的单元测试所执行的角色之一是文档.

如果有人想知道如何使用您的课程,他们可以检查测试,并可能复制和更改测试代码以符合他们的目的.如果使用的真实习语被模拟的创建和注入所掩盖,则这变得困难.
>模拟可以掩盖潜在的错误.

假设名称为空,GetProtocolHeader会抛出异常.您可以相应地创建一个模拟,并确保GetProtocolHeaderValue适当地处理该异常.稍后,您决定GetProtocolHeader应为空名称返回null.如果您忘记更新模拟,GetProtocolHeaderValue(“”)现在在现实生活中与测试套件的行为会有所不同.
>如果模拟比设置更简洁,则模拟可能会带来优势,但首先要考虑上述要点.

虽然你给出了GetProtocolHeaderValue需要测试的三个不同的GetProtocolHeader响应(header,null或exception),但我想第一个可能是“一系列标题”. (例如,它对存在的标题有什么作用,但是空?它如何处理前导和尾随空格?非ASCII字符怎么样?数字?).如果所有这些的设置非常详细,那么模拟可能会更好.