2010-02-01 4 views
15

J'utilise Moq & NUnit comme un cadre de test unitaire.Comment moq un NetworkStream dans un test unitaire?

J'ai écrit une méthode qui est donnée d'un objet NetworkStream comme paramètre:

public static void ReadDataIntoBuffer(NetworkStream networkStream, Queue dataBuffer) 
{ 
    if ((networkStream != null) && (dataBuffer != null)) 
    { 
    while (networkStream.DataAvailable) 
    { 
     byte[] tempBuffer = new byte[512]; 

     // read the data from the network stream into the temporary buffer 
     Int32 numberOfBytesRead = networkStream.Read(tempBuffer, 0, 512); 

     // move all data into the main buffer 
     for (Int32 i = 0; i < numberOfBytesRead; i++) 
     { 
      dataBuffer.Enqueue(tempBuffer[i]); 
     } 
    } 
    } 
    else 
    { 
    if (networkStream != null) 
    { 
     throw new ArgumentNullException("networkStream"); 
    } 

    if (dataBuffer != null) 
    { 
     throw new ArgumentNullException("dataBuffer"); 
    } 
    } 
} 

Maintenant, je suis à la recherche récrire mes tests unitaires pour cette méthode puisque les tests déjà écrits reposent sur NetworkStream réel objets et ne sont pas très agréable à manipuler.

Comment puis-je simuler le NetworkStream? J'utilise Moq comme mentionné précédemment. Est-ce possible? Sinon, comment pourrais-je contourner ce problème?

Dans l'attente de vos commentaires!

Voici la solution précédente:

public static void ReadDataIntoBuffer(Stream dataStream, Queue dataBuffer) 
{ 
    if ((networkStream != null) && (dataBuffer != null)) 
    { 
    byte[] tempBuffer = new byte[512]; 
    Int32 numberOfBytesRead = 0; 

    // read the data from the network stream into the temporary buffer 
    while ((numberOfBytesRead = dataStream.Read(tempBuffer, 0, 512) > 0) 
    { 
     // move all data into the main buffer 
     for (Int32 i = 0; i < numberOfBytesRead; i++) 
     { 
      dataBuffer.Enqueue(tempBuffer[i]); 
     } 
    } 
    } 
    else ... 
} 

MISE À JOUR:

J'ai réécrites ma classe une fois de plus. Les tests unitaires utilisant la solution précédente se sont bien passés mais l'exemple d'application réel m'a montré pourquoi il n'est pas possible pour moi d'utiliser la suggestion (sinon géniale) de passer un objet Stream dans ma méthode.

Tout d'abord, mon application repose sur une connexion TCP constante. Si vous utilisez Stream.Read (ce qui est possible) et qu'il n'y a pas de données à recevoir, cela bloque l'exécution. Si vous spécifiez un délai d'expiration, une exception sera levée si aucune donnée n'est reçue. Ce genre de comportement n'est pas acceptable pour l'application (plutôt simple) dont j'ai besoin. J'ai juste besoin d'une connexion TCP constante et sans fioritures. Par conséquent, avoir la propriété NetworkStream.DataAvailable est primordiale pour mon implémentation.

La solution actuelle :

Je fini par écrire une interface et une enveloppe à NetworkStream. J'ai également fini par passer le tableau d'octets pour le tampon de réception temporaire dans la méthode. L'unité de test fonctionne maintenant plutôt bien.

public static void ReadDataIntoBuffer(INetworkStream networkStream, Queue dataBuffer, byte[] tempRXBuffer) 
{ 
    if ((networkStream != null) && (dataBuffer != null) && (tempRXBuffer != null)) 
    { 
     // read the data from the network stream into the temporary buffer 
     while(networkStream.DataAvailable) 
     { 
      Int32 numberOfBytesRead = networkStream.Read(tempRXBuffer, 0, tempRXBuffer.Length); 

      // move all data into the main buffer 
      for (Int32 i = 0; i < numberOfBytesRead; i++) 
      { 
       dataBuffer.Enqueue(tempRXBuffer[i]); 
      } 
     } 
    } 
    else ... 
} 

Et voici le test unitaire que j'utilise:

public void TestReadDataIntoBuffer() 
{ 
    var networkStreamMock = new Mock<INetworkStream>(); 
    StringBuilder sb = new StringBuilder(); 

    sb.Append(_testMessageConstant1); 
    sb.Append(_testMessageConstant2); 
    sb.Append(_testMessageConstant3); 
    sb.Append(_testMessageConstant4); 
    sb.Append(_testMessageConstant5); 


    // ARRANGE 
    byte[] tempRXBuffer = Encoding.UTF8.GetBytes(sb.ToString()); 

    // return true so that the call to Read() is made 
    networkStreamMock.Setup(x => x.DataAvailable).Returns(true); 

    networkStreamMock.Setup(x => x.Read(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>())).Callback(() => 
     { 
      // after the call to Read() re-setup the property so that we 
      // we exit the data reading loop again 
      networkStreamMock.Setup(x => x.DataAvailable).Returns(false); 

     }).Returns(tempRXBuffer.Length); 

    Queue resultQueue = new Queue(); 

    // ACT 
    ReadDataIntoBuffer(networkStreamMock.Object, resultQueue, tempRXBuffer); 

    // ASSERT 
    Assert.AreEqual(Encoding.UTF8.GetBytes(sb.ToString()), resultQueue.ToArray()); 
} 
+2

Pouvez-vous changer le 'NetworkStream' dans un' Stream'? Cela pourrait rendre les choses plus faciles. –

+0

Je suppose que je pourrais le faire. Pouvez-vous expliquer comment cela faciliterait les choses? Est-ce que Moq peut se moquer d'un objet Stream? –

+0

Oui, Moq peut se moquer d'un flux car il s'agit d'une classe abstraite, cependant 'Stream' ne contient pas la propriété' DataAvailable', donc je vous suggère d'utiliser l'approche que j'ai décrite ci-dessous. –

Répondre

15

Vous ne pouvez pas simuler le NetworkStream avec moq puisqu'il ne s'agit pas d'une classe abstraite ou d'une interface. Vous pouvez cependant créer une abstraction et changer votre méthode pour accepter une instance de cette abstraction.Il pourrait être quelque chose comme ceci:

public interface IMyNetworkStream 
{ 
    int Read([In, Out] byte[] buffer, int offset, int size); 
    bool DataAvailable {get;} 
} 

Maintenant, vous créez une classe qui implémente l'interface:

public class MyNetworkStream : IMyNetworkStream 
{ 
    private NetworkStream stream; 

    public MyNetworkStream(NetworkStream ns) 
    { 
     if(ns == null) throw new ArgumentNullException("ns"); 
     this.stream = ns; 
    } 

    public bool DataAvailable 
    { 
     get 
     { 
      return this.stream.DataAvailable; 
     } 
    } 

    public int Read([In, Out] byte[] buffer, int offset, int size) 
    { 
     return this.stream.Read(buffer, offset, size); 
    } 

} 

Maintenant, vous pouvez modifier votre signature de la méthode à utiliser une instance de IMyNetworkStream et utiliser Moq pour créer un simulacre de IMyNetworkStream.

+0

Merci d'avoir élaboré! Je pense que je peux éviter d'avoir à utiliser la propriété DataAvailable de NetworkStream en testant le résultat de l'appel Read(). Ou pouvez-vous voir une raison pour laquelle cela ne devrait pas fonctionner? –

+0

Non, cela fonctionnerait probablement aussi. Cependant, l'implémentation de 'DataAvailable' est un peu différente. Vous pouvez regarder l'implémentation dans le réflecteur en cas de doute. –

+0

Je vais tester NetworkStream.DataAvailable avant d'appeler ma méthode ReadDataIntoBuffer(). De cette façon, je peux passer juste l'objet Stream. Merci pour vos commentaires! –

1

Mettre le NetworkStream derrière une interface simple (avec seulement les appels dont vous avez besoin) et Mock.

+0

C'est une proposition valide. Je me demandais si je pouvais être un peu plus paresseux et omettre de créer une interface que je pourrais mocker ... –

6

Comme suggéré dans les commentaires - est-il possible que vous changiez le type en Stream, de sorte que vous puissiez, au cours du test, passer un MemoryStream à la place?

+0

Eh bien c'est une bonne suggestion! Cependant, je ne serais plus capable d'utiliser NetworkStream.DataAvailable comme dans mon exemple de code. Je suppose que je pourrais remplacer cette mise en œuvre de la lecture des données réelles en quelque sorte. –

Questions connexes