模拟 UdpClient 用于单元测试

发布于 2024-11-25 22:15:10 字数 3401 浏览 0 评论 0原文

我正在开发一个利用 UdpClient 的类,并尝试在此过程中使用 NUnit 和 Moq 来学习/利用 TDD 方法。

到目前为止,我的课程的基本内容如下:

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);
    }
}

.
.
对于 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());
}

.
.
我对此不太满意,因为它使用的是实际的 IP 地址,尽管只是本地主机,并且类中的 UdpClient 正在物理上向其发送数据。因此,据我了解,这不是真正的单元测试。

问题是,我不知道该怎么办。我应该更改我的类并传入一个新的 UdpClient 作为依赖项吗?以某种方式模拟 IP 地址?

有点挣扎,所以我需要在这里停下来看看我是否走在正确的轨道上,然后再继续。任何建议表示赞赏!

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

更新:

好的,我已重构我的代码如下:

创建了一个 IUdpClient 接口:

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

创建了一个适配器类来包装 UdpClient 系统类:

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
}

重构了我的 UdpCommsChannel 类,以要求通过构造函数注入 IUdpClient 的实例:

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

我的单元测试现在看起来像这样

[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());
}

欢迎任何进一步的评论。

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.

A bare-bones part of my class so far is as follows:

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:

[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. 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? Mock the IPAddress somehow?

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.)
.
.

UPDATE:

OK, I have refactored my code as follows:

Created an IUdpClient interface:

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:

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:

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鲸落 2024-12-02 22:15:10

如果您只想测试类的功能,我会创建一个接口 ICommunucator,然后创建一个类 UdpCommunicator,它直接包装所需的 UdpClient 属性和方法,而不需要任何检查和条件。

在您的类中,注入包装器并使用 is 而不是 tf UdpClient。

这样,在测试中您就可以模拟 ISender 并进行测试。

当然,您不会对包装器本身进行测试,但如果它只是普通的呼叫转发,则不需要它。

基本上,您已经朝着正确的方向开始,但是您添加了一些自定义逻辑,无法以这种方式进行测试 - 因此您需要拆分自定义逻辑和通信部分。

即,您的类将变成这样:

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

您的测试将如下所示:

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

mock.Verify....

因此,在几个验证语句中,您可以检查通信器上的 Open 是否被调用,是否传递了正确的数据等。

一般来说 - 您不需要测试系统或第三方类,只有你自己的。

如果您的代码使用这样的类,请使它们可注入。如果这些类没有虚拟方法(即您不能直接模拟它们),或者没有实现某些通用接口(如 SqlConnection 等 - it.implements IDbConnection),那么您将创建一个像上面解释的普通包装器。

除了可测试性的改进之外,这种方法将使将来修改代码变得更加容易 - 即当您需要其他通信方法等时。

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.

In your class, inject the wrapper and use is instead of tf UdpClient.

That way, in the tests you can mock ISender, and test.

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.

I.e., your class becomes like this:

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.

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 (i.e. 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.

Besides of the testability improvements, such an approach will make it much easier to modify your code in the future - i.e. when you need some other method of communication, etc.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文