C# 如何在模拟不可用时保持单元测试干燥';不行?

C# 如何在模拟不可用时保持单元测试干燥';不行?,c#,.net,unit-testing,tdd,mocking,C#,.net,Unit Testing,Tdd,Mocking,编辑: 似乎通过尝试为我自己的问题提供一些解决方案,我模糊了整个问题。所以我对这个问题稍加修改 假设我有这个班: public class ProtocolMessage : IMessage { public IHeader GetProtocolHeader(string name) { // Do some logic here including returning null // and throw exception in some

编辑:

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

假设我有这个班:

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或异常的情况。因此,主要的问题是:我是否应该编写真正执行
GetProtocolHeader
的测试(一些测试将被复制,因为它们将测试与
GetProtocolHeader
本身相同的代码)或者我应该使用@adrift和@Eric Nicholson所描述的模拟方法吗?在这里,我不会运行real
GetProtoclHeader
,而只是将模拟配置为在调用此方法时返回header、null或exception?

我经常使用部分模拟(在Rhino中)或等效方法(如Fakeitesy中的CallsBaseMethod)模拟我正在测试的实际类。然后,您可以使GetProtocolHeader虚拟并模拟对它的调用。你可能会说它违反了单一责任原则,但这显然仍然是非常内聚的代码

或者,您可以使用如下方法

internal static string GetProtocolHeaderValue(string name, IHeader header )
并独立测试该处理。公共GetProtocolHeaderValue方法不会有任何/许多测试


编辑:在这个特定的情况下,我还将考虑将GETValueAd()作为IHeader的扩展方法添加。这将非常容易阅读,并且您仍然可以执行空检查。

我可能遗漏了一些内容,但是根据列出的代码,您似乎不需要担心它是否被调用

存在两种可能性:

  • GetProtocolHeader()
    方法需要是公共的,在这种情况下,您可以编写一组测试来告诉您它是否按预期工作
  • 这是一个实现细节,不需要公开,除非您希望能够直接对其进行测试,但在这种情况下,您真正关心的是一组测试,这些测试告诉您
    GetProtocolHeaderValue()
    是否按需工作

  • 在这两种情况下,您都在测试公开的功能,而在一天结束时,这才是最重要的。如果它是一个依赖项,那么是的,您可能会担心它是否被调用,但如果它不是唯一的依赖项,那么它肯定是一个实现细节,并且不相关?

    在调用
    GetProtocolHeaderValue
    时,您是否确实需要知道它是否被调用
    GetProtocolHeader

    当然,知道它从正确的头中获得正确的值就足够了。它实际上是如何得到它的与单元测试无关

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

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

    只要您以这样的方式精心设计您的测试和测试数据,以确保重复的头不会掩盖错误,那么一切都应该很好

    编辑更新的问题:
  • 如果
    GetProtocolHeader
    能够快速、可靠地工作并且是幂等的,那么我仍然认为没有必要对它进行模拟。(国际海事组织)这三个方面的不足是嘲弄的主要原因

    如果(正如我从问题标题中怀疑的那样),您希望模拟它的原因是设置适当状态以返回实际值所需的序言太冗长,并且您不希望在两个测试中重复它,为什么不在设置阶段进行呢

  • 良好的单元测试所扮演的角色之一是文档

    如果有人希望知道如何使用您的类,他们可以检查测试,并可能复制和修改测试代码以满足他们的目的。如果真正的习惯用法被创建和注入的模拟所掩盖,那么这就变得很困难

  • 模拟可以掩盖潜在的bug

    假设
    GetProtocolHeader
    在name为空时抛出异常。相应地创建一个模拟,并确保
    GetProtocolHeaderValue
    适当地处理该异常。稍后,您决定
    GetProtocolHeader
    应该为空名称返回
    null
    。如果忘记更新mock,
    GetProtocolHeaderValue(“”)
    现在在现实生活中的行为将与测试套件不同

  • 如果mock没有设置那么详细,那么mock可能是一个优势,但是首先要充分考虑以上几点

    尽管您给出了
    GetProtocolHeaderValue
    需要测试的三个不同的
    GetProtocolHeaderValue
    响应(header、null或异常),但我认为第一个响应可能是“一系列头”。(例如,我应该做什么
    public string GetProtocolHeaderValue(string name)
    {
       IHeader header = GetProtocolHeader(name);
       return RetrieveHeaderValue(IHeader header);
    }
    
    public string GetProtocolHeaderValue(string name, IHeaderParser parser)
    {
        IHeader header = parser.GetProtocolHeader(name);
        return parser.HeaderValue(IHeader header);
    }