有没有理由喜欢模拟一个接口而不是一个具有可覆盖成员的类?

时间:2014-01-17 10:20:30

标签: c# unit-testing mocking

我有一个用于交换消息的外部库。 在这个库中,我有一个名为Channel的对象。

这是反编译后的结果:

public class Channel 
{
    public State CurrentState { get { /*Only for code compiling, the value depend on the TCP Connection state.*/return State.ERROR; } }
    public bool Send(string message)
    {
        //Some stuff with TCP connection.
        return true;
    }   

    public enum State
    {
      DISCONNECTED,
      CONNECTED,
      ERROR
    }
}

现在在我的代码中,我在类中使用此Channel来发送消息,该类看起来像这样:

public class ClientConnection
{
    private Channel MyChannel;

    public ClientConnection(Channel channel)
    {
        MyChannel = channel;
    }

    public bool Send(string message)
    {
        bool result = false;
        if(MyChannel.CurrentState == Channel.State.CONNECTED)
        {
            result = MyChannel.Send(message);
        }
        return result;
    }
}

所以我的目标是测试它,验证调用方法send,并检查参数是否与我的输入匹配。 这里的问题是没有接口,并且该方法不是虚拟的,因此无法直接进行模拟。

我做了什么 我创建了一个带有可覆盖属性和方法的包装器,如下所示:

public class ChannelWrapper
{
    private readonly Channel channel;
    public ChannelWrapper(Channel channel)
    {
        this.channel = channel;
    }
    public virtual Channel.State CurrentState { get { return channel.CurrentState; } }
    public virtual bool Send(string message)
    {
        return channel.Send(message);
    }
}

并在构造函数和属性中将ClientConnection类型Channel更改为ChannelWrapper

问题 我觉得我应该创建一个匹配ChannelChannelWrapper的额外接口,并使用接口而不是可覆盖的成员进行模拟。 与此同时,我真的没有看到添加新界面的意义。

是否有理由更喜欢模拟接口而不是具有可覆盖成员的类? (我也主要考虑表现)。

1 个答案:

答案 0 :(得分:3)

您通常想要模拟接口而不是具体类型的主要原因是,在具体类型的情况下,您的模拟将具有实际实现的一些部分,可能导致不可预测/不期望的行为。 / p>

例如,Channel上的Send方法当前有“// TCP连接的东西”。在里面。如果Channel类实例化某些Web连接并将它们存储为字段,该怎么办?这意味着您的模拟对象现在包含实际的Web连接,即使它们可能永远不会被使用。如果我们谈论数据库连接等,这可能会更严重。

在你的特定例子中可能不是这种情况,但你应该多考虑一下你已经“离开它”,而不是那个规则。这些问题意味着模拟界面通常更清晰;你知道你的模拟对象不包含你在测试中没有放到的任何内容。

我也会看到这个非常相似的问题:Mocking classes that aren't interfaces