2011-07-22 18 views
6

Estoy trabajando en una clase que utiliza un UdpClient, y tratando de aprender/utilizar un enfoque TDD usando NUnit y Moq en el proceso.Mock UdpClient para prueba de unidad

Una parte escueto de mi clase hasta el momento es el siguiente:

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

.
.
Para el método de envío, que tienen la siguiente prueba de la unidad en la actualidad

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

.
.
No estoy muy contento con eso porque está usando una dirección IP real, aunque solo sea el localhost, y el UdpClient dentro de la clase está físicamente enviándole datos. Por lo tanto, esta no es una verdadera prueba unitaria por lo que yo entiendo.

El problema es que no entiendo exactamente qué hacer al respecto. ¿Debo cambiar mi clase y pasar un nuevo UdpClient como una dependencia tal vez? ¿Se burla de la IPAddress de alguna manera?

Luchando un poco, así que tengo que parar aquí para ver si estoy en el camino correcto antes de continuar. Cualquier consejo apreciado!

(utilizando NUnit 2.5.7, Moq 4.0 y C# WinForms) .
.

ACTUALIZACIÓN:

OK, he rediseñado mi código de la siguiente manera:

Creado una interfaz IUdpClient:

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

.

creado una clase adaptador para envolver la clase sistema de 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 
} 

.

refactorizado mis clas UdpCommsChannel para requerir una instancia de un IUdpClient inyectado por el constructor:

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

.

Mi prueba de la unidad ahora se ve así:

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

.

Cualquier comentario adicional es bienvenido.

Respuesta

5

Si desea probar solo la funcionalidad de su clase, iría creando una interfaz ICommunucator, luego crearía una clase UdpCommunicator, que directamente envuelve las propiedades y los métodos de UdpClient necesarios, sin controles ni condiciones.

En su clase, inyecte el envoltorio y use es en lugar de tf UdpClient.

De esta forma, en las pruebas puede simular ISender y probar.

Por supuesto, no tendrá pruebas para el envoltorio en sí, pero si se trata simplemente de un desvío de llamadas, no necesita esto.

Básicamente, ha comenzado en la dirección correcta, pero ha agregado una lógica personalizada, que no se puede probar de esa manera, por lo que debe dividir la lógica personalizada y la parte de comunicación.

Es decir, la clase se convierte de esta manera:

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

Y la prueba se verá así:

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

mock.Verify.... 

Así, en algunas declaraciones Verify puede comprobar si está abierta en el comunicador se llama, si se pasaron los datos correctos, etc.

En general, no necesita probar clases del sistema o de terceros, solo las suyas.

Si su código utiliza tales clases, hágalo inyectable. Si estas clases no tienen métodos virtuales (es decir, no se pueden burlar directamente), o no implementan una interfaz común (como SqlConnection, etc. does - it. Implementa IDbConnection), entonces crea un contenedor simple como se explicó anteriormente.

Además de las mejoras en la capacidad de prueba, este enfoque hará que sea mucho más fácil de modificar el código en el futuro - es decir, cuando se necesita algún otro método de comunicación, etc.

+0

Gracias por su respuesta. ¿Querías decir "... puedes burlarte de ICommunicator ..."? – Andy

+0

pls, ver la edición –

+0

Sí, eso tiene más sentido ahora, gracias. – Andy