简体   繁体   English

模拟 UdpClient 进行单元测试

[英]Mock UdpClient for unit testing

I am working on a class that utilises a UdpClient, and attempting to learn/utilise a TDD approach using NUnit and Moq in the process.我正在研究使用 UdpClient 的 class,并尝试在此过程中使用 NUnit 和 Moq 学习/利用 TDD 方法。

A bare-bones part of my class so far is as follows:到目前为止,我的 class 的基本部分如下:

public UdpCommsChannel(IPAddress address, int port)
{
    this._udpClient = new UdpClient();
    this._address = address;
    this._port = port;
    this._endPoint = new IPEndPoint(address, port);
}

public override void Open()
{
    if (this._disposed) throw new ObjectDisposedException(GetType().FullName);

    try
    {
        this._udpClient.Connect(this._endPoint);
    }
    catch (SocketException ex)
    {
        Debug.WriteLine(ex.Message);
    }
}

public override void Send(IPacket packet)
{
    if (this._disposed) throw new ObjectDisposedException(GetType().FullName);

    byte[] data = packet.GetBytes();
    int num = data.Length;

    try
    {
        int sent = this._udpClient.Send(data, num);
        Debug.WriteLine("sent : " + sent);
    }
    catch (SocketException ex)
    {
        Debug.WriteLine(ex.Message);
    }
}

. .
. .
For the Send method, I have the following unit test at the moment:对于 Send 方法,我目前有以下单元测试:

[Test]
public void DataIsSent()
{
    const int port = 9600;

    var mock = new Mock<IPacket>(MockBehavior.Strict);
    mock.Setup(p => p.GetBytes()).Returns(new byte[] { }).Verifiable();

    using (UdpCommsChannel udp = new UdpCommsChannel(IPAddress.Loopback, port))
    {
        udp.Open();
        udp.Send(mock.Object);
    }

    mock.Verify(p => p.GetBytes(), Times.Once());
}

. .
. .
I'm not that happy with that because it is using an actual IP address, albeit only the localhost, and the UdpClient inside the class is physically sending data to it.我对此并不满意,因为它使用的是实际的 IP 地址,尽管只有 localhost,并且 class 内的 UdpClient 正在向它物理发送数据。 Therefore, this is not a true unit test as far as I understand it.因此,据我所知,这不是一个真正的单元测试。

Problem is, I can't get my head around exactly what to do about it.问题是,我不知道该怎么做。 Should I change my class and pass in a new UdpClient as a dependency perhaps?我是否应该更改我的 class 并将新的 UdpClient 作为依赖项传入? Mock the IPAddress somehow?以某种方式模拟 IPAddress?

Struggling a bit, so I need to stop here to see if I am on the right track before carrying on.有点挣扎,所以我需要在这里停下来看看我是否走在正确的轨道上,然后再继续。 Any advice appreciated!任何建议表示赞赏!

(Using NUnit 2.5.7, Moq 4.0 and C# WinForms.). (使用 NUnit 2.5.7、Moq 4.0 和 C# WinForms。)。
. .

UPDATE:更新:

OK, I have refactored my code as follows:好的,我已经重构了我的代码如下:

Created an IUdpClient interface:创建了一个 IUdpClient 接口:

public interface IUdpClient
{
    void Connect(IPEndPoint endpoint);
    int Send(byte[] data, int num);
    void Close();
}

. .

Created an adapter class to wrap the UdpClient system class:创建了一个适配器 class 来包装 UdpClient 系统 class:

public class UdpClientAdapter : IUdpClient
{
    private UdpClient _client;

    public UdpClientAdapter()
    {
        this._client = new UdpClient();
    }

    #region IUdpClient Members

    public void Connect(IPEndPoint endpoint)
    {
        this._client.Connect(endpoint);
    }

    public int Send(byte[] data, int num)
    {
        return this._client.Send(data, num);
    }

    public void Close()
    {
        this._client.Close();
    }

    #endregion
}

. .

Refactored my UdpCommsChannel clas to require an instance of an IUdpClient injected via the constructor:重构我的 UdpCommsChannel 类以要求通过构造函数注入的 IUdpClient 实例:

public UdpCommsChannel(IUdpClient client, IPEndPoint endpoint)
{
    this._udpClient = client;
    this._endPoint = endpoint;
}

. .

My unit test now looks like this:我的单元测试现在看起来像这样:

[Test]
public void DataIsSent()
{
    var mockClient = new Mock<IUdpClient>();
    mockClient.Setup(c => c.Send(It.IsAny<byte[]>(), It.IsAny<int>())).Returns(It.IsAny<int>());

    var mockPacket = new Mock<IPacket>(MockBehavior.Strict);
    mockPacket.Setup(p => p.GetBytes()).Returns(new byte[] { }).Verifiable();

    using (UdpCommsChannel udp = new UdpCommsChannel(mockClient.Object, It.IsAny<IPEndPoint>()))
    {
        udp.Open();
        udp.Send(mockPacket.Object);
    }

    mockPacket.Verify(p => p.GetBytes(), Times.Once());
}

. .

Any further comments welcome.欢迎任何进一步的评论。

If you want to test only the functionality of your class, the I would go creating an interface ICommunucator, then create a class UdpCommunicator, which directly wraps UdpClient properties and methods needed, without any checks and conditions.如果你只想测试你的 class 的功能,我会 go 创建一个接口 ICommunucator,然后创建一个 class UdpCommunicator 和所需的属性,无需任何检查客户端包装和方法。

In your class, inject the wrapper and use is instead of tf UdpClient.在您的 class 中,注入包装器并使用 is 而不是 tf UdpClient。

That way, in the tests you can mock ISender, and test.这样,您可以在测试中模拟 ISender 并进行测试。

Of course, you will not have tests for the wrapper itself, but if it's just a plain call forwarding, you do not need this.当然,你不会对包装器本身进行测试,但如果它只是一个普通的呼叫转移,你就不需要这个。

Basically, you have started in the right direction, but you have added some custom logic, which can not be tested that way - so you need to split the custom logic and the comm part.基本上,您已经朝着正确的方向开始,但是您添加了一些自定义逻辑,无法以这种方式进行测试-因此您需要拆分自定义逻辑和 comm 部分。

Ie, your class becomes like this:即,你的 class 变成这样:

public MyClass(ICommunicator comm)
{
     public void Method1(someparam)
     {
         //do some work with ICommunicator
     }
     ....
}

And your test will look like:您的测试将如下所示:

var mockComm = Mock.GetMock<ICommunicator>();
mockComm.Setup ....
var myTestObj = new MyClass(mock.Object);
MyClass.Method1(something);

mock.Verify....

So, in few Verify statements you can check if Open on the communicator is called, if the right data was passed, etc.因此,在几个验证语句中,您可以检查是否调用了通信器上的 Open、是否传递了正确的数据等。

In general - you do not need to test system or third-party classes, only your own.一般来说 - 你不需要测试系统或第三方类,只需要你自己的。

If your code uses such a classes, make them injectable.如果您的代码使用这样的类,请使它们可注入。 If these classes do not have virtual methods (ie you can not mock them directly), or do not implement some common interface (like SqlConnection, etc. does - it. implements IDbConnection), then you create a plain wrapper like explained above.如果这些类没有虚拟方法(即您不能直接模拟它们),或者没有实现一些通用接口(如 SqlConnection 等 - 它实现了 IDbConnection),那么您将创建一个如上所述的普通包装器。

Besides of the testability improvements, such an approach will make it much easier to modify your code in the future - ie when you need some other method of communication, etc.除了可测试性改进之外,这种方法将使将来修改代码变得更加容易 - 即当您需要其他一些通信方法等时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM