我有一个用于交换消息的外部库。
在这个库中,我有一个名为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
。
问题
我觉得我应该创建一个匹配Channel
和ChannelWrapper
的额外接口,并使用接口而不是可覆盖的成员进行模拟。
与此同时,我真的没有看到添加新界面的意义。
是否有理由更喜欢模拟接口而不是具有可覆盖成员的类? (我也主要考虑表现)。
答案 0 :(得分:3)
您通常想要模拟接口而不是具体类型的主要原因是,在具体类型的情况下,您的模拟将具有实际实现的一些部分,可能导致不可预测/不期望的行为。 / p>
例如,Channel上的Send方法当前有“// TCP连接的东西”。在里面。如果Channel类实例化某些Web连接并将它们存储为字段,该怎么办?这意味着您的模拟对象现在包含实际的Web连接,即使它们可能永远不会被使用。如果我们谈论数据库连接等,这可能会更严重。
在你的特定例子中可能不是这种情况,但你应该多考虑一下你已经“离开它”,而不是那个规则。这些问题意味着模拟界面通常更清晰;你知道你的模拟对象不包含你在测试中没有放到的任何内容。
我也会看到这个非常相似的问题:Mocking classes that aren't interfaces