C# 如何使具有依赖项的类在没有膨胀的情况下可以进行单元测试?

C# 如何使具有依赖项的类在没有膨胀的情况下可以进行单元测试?,c#,unit-testing,dependency-injection,tdd,C#,Unit Testing,Dependency Injection,Tdd,所以我有一个类(库的一部分),它促进了双向TCP通信。其中一部分是接受传入连接 我的实现包括使用TCPListener对象和多线程方法来接受连接。为了使其可测试,我决定创建一个“INetworkListener”接口,它只包含一个事件“onclientacepted”。通过这种方式,我可以使用依赖项注入来模拟TCPListener的假版本,并避免多线程单元测试 问题是,我需要一种使用伪INetworkListener测试类的方法,但我希望我的用户不可以选择使用此接口的其他实现 下面是一些精简的示

所以我有一个类(库的一部分),它促进了双向TCP通信。其中一部分是接受传入连接

我的实现包括使用TCPListener对象和多线程方法来接受连接。为了使其可测试,我决定创建一个“INetworkListener”接口,它只包含一个事件“onclientacepted”。通过这种方式,我可以使用依赖项注入来模拟TCPListener的假版本,并避免多线程单元测试

问题是,我需要一种使用伪INetworkListener测试类的方法,但我希望我的用户可以选择使用此接口的其他实现

下面是一些精简的示例代码:

class TcpMessenger
{
    // Various properties

    private INetworkListener _tcpListener;

    public TcpMessenger(int port, string friendlyName) // This is the ONLY constructor I want available to users
    {
        ServerPort = port;
        FriendlyName = friendlyName;
        _isRunning = false;
        _tcpListener = new ConcreteExample(port); // This prevents unit testing because it opens threads and such
    }

    public TcpMessenger(int port, string friendlyName, INetworkListener listener) // I need this to test
    {
        ServerPort = port;
        FriendlyName = friendlyName;
        _isRunning = false;
        _tcpListener = listener; // No dependency here :)
    }
}
为什么我不能离开两个构造函数?

我的图书馆就像一个门面。它使TCP通信更容易,但没有添加太多功能。因此,我的目标受众永远不需要注入这种依赖性。如果我不想让他们这么做,那么正确的设计告诉我要执行它

为什么不将其作为面向公众的API进行测试?集成测试

实际上,我是在我的库的前一个版本中这样做的,这个版本非常耦合。结果很糟糕。我做了十几个测试,涉及多个线程,更糟糕的是,涉及实际的套接字。单元测试不应该依赖于这样的外部因素

您真的需要对此进行测试吗?

对。该库具有高级功能,例如异常处理、错误报告、连接问题的故障保护以及维护双向连接的算法(意味着每个节点都是客户端和服务器)。这个功能可以在不打开实际插槽的情况下进行测试,所以我绝对希望这样

我还希望单元测试是公开的。这意味着任何人都可以获取源代码,运行它们,并看到所有绿色的复选标记

结论性问题:


如何独立测试两个类,同时在真实场景中强制依赖关系?

您可以使用
InternalsVisibleTo将构造函数设置为内部,并将内部暴露给测试:

[assembly: InternalsVisibleTo("YourNamespace.YourTests")]

请参阅:

希望我的用户不能选择使用此接口的其他实现。
为什么?如果他们做了另一个实现,那就这样吧。但是如果你仍然想,你可以在你的界面上使用属性
InternalsVisibleTo
,这样它只对你的程序集可见。它与我设定的设计目标不太协调。老实说,这是非常低的赌注。这更像是一个概念的证明。即使我选择保持原样,也可能有另一种情况更为严重。如果API的使用者故意编写代码并实现接口来做其他与其余代码不兼容的事情,那么这就是他们的问题。老实说,我不会为此担心的。