Как moq NetworkStream в unit test?

Я использую Moq и NUnit в качестве среды unit test.

Я написал метод, которому в качестве параметра задан объект NetworkStream:

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

Теперь я рассматриваю повторную запись моих модульных тестов для этого метода, поскольку ранее написанные тесты полагаются на реальные объекты NetworkStream и не очень приятно обрабатывать.

Как я могу высмеять NetworkStream? Я использую Moq, как упоминалось ранее. Это вообще возможно? Если нет, то как я могу решить эту проблему?

С нетерпением ждем ваших отзывов!

Вот предыдущее решение:

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

UPDATE:

Я снова переписал свой класс. Единичное тестирование с использованием предыдущего решения прошло отлично, но пример приложения реального мира показал мне, почему я не могу использовать (в противном случае великое) предложение передать объект Stream в мой метод.

Во-первых, мое приложение использует постоянное TCP-соединение. Если вы используете Stream.Read (что возможно), и нет данных для его получения, это заблокирует выполнение. Если вы укажете таймаут, исключение будет выбрано, если данные не будут получены. Такое поведение неприемлемо для (довольно простого) приложения, которое мне нужно. Мне просто нужно без излишеств, постоянное соединение TCP. Поэтому наличие свойства NetworkStream.DataAvailable имеет первостепенное значение для моей реализации.

Текущее решение :

В итоге я написал интерфейс и оболочку для NetworkStream. Я также закончил передачу массива байтов для временного буфера приема в этот метод. Модульное тестирование теперь работает довольно хорошо.

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

И вот unit test, который я использую:

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

Ответы

Ответ 1

Вы не можете издеваться над NetworkStream с moq, поскольку это не абстрактный класс или интерфейс. Однако вы можете создать абстракцию поверх нее и изменить свой метод, чтобы принять экземпляр этой абстракции. Это может быть примерно так:

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

Теперь вы создаете класс, реализующий интерфейс:

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

}

Теперь вы можете изменить свою подпись метода для использования экземпляра IMyNetworkStream и использовать Moq для создания макета IMyNetworkStream.

Ответ 2

Как указано в комментариях - возможно ли изменить тип на Stream, чтобы вы, во время тестирования, могли передать MemoryStream вместо этого?

Ответ 3

Поместите NetworkStream за простой интерфейс (только с необходимыми вызовами) и издевайтесь над этим.