2011-07-22 121 views
6

我正在研究一个使用UdpClient的类,并尝试在过程中使用NUnit和Moq学习/使用TDD方法。模拟UdpClient进行单元测试

我班的裸机部分至今如下:

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



对于发送方法,我在此刻以下的单元测试:

[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。) 。

UPDATE:

好吧,我有我的重构代码如下:

创建一个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 CLAS需要通过构造函数注入的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()); 
} 

欢迎任何进一步的意见。

回答

5

如果您只想测试您的类的功能,我会创建一个接口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.... 

所以,在几个验证报表您可以检查是否打开的传播者被调用时,如果有合适的数据传递等

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

如果您的代码使用这样的类,使它们可注射。如果这些类不具有虚拟方法(即不能直接模拟它们),或者不实现一些通用接口(如SqlConnection等 - 它实现了IDbConnection),那么您将创建一个如上所述的纯封装。

另外的可测性的改进,这样的做法将使其更容易修改你的代码在将来 - 也就是当你需要沟通的一些其他方法,等您的回复

+0

感谢。你的意思是说“你可以模拟ICommunicator ...”吗? – Andy

+0

请参阅编辑 –

+0

是的,现在更有意义,谢谢。 – Andy

相关问题