Mock UdpClient для модульного тестирования - PullRequest
6 голосов
/ 22 июля 2011

Я работаю над классом, который использует UdpClient, и пытаюсь изучить / использовать подход TDD, используя NUnit и Moq в этом процессе.

На данный момент часть моего класса выглядит так::

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

.

Любые дальнейшие комментарии приветствуются.

1 Ответ

5 голосов
/ 22 июля 2011

Если вы хотите протестировать только функциональность вашего класса, я бы пошел создать интерфейс ICommunucator, а затем создать класс UdpCommunicator, который напрямую оборачивает необходимые свойства и методы UdpClient, без каких-либо проверок и условий.

В вашем классе вставьте оболочку и используйте вместо 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....

Итак, в нескольких утверждениях Verify вы можете проверить, вызывается ли Open на коммуникаторе, были ли переданы правильные данные и т. Д.

В общем случае - вам не нужнодля тестирования системных или сторонних классов, только ваших собственных.

Если ваш код использует такие классы, сделайте их инъекционными.Если эти классы не имеют виртуальных методов (то есть вы не можете имитировать их напрямую), или не реализует какой-либо общий интерфейс (например, SqlConnection и т. Д. - реализует IDbConnection), то вы создаете простую оболочку, как описано выше.

Помимо улучшений тестируемости, такой подход значительно упростит изменение вашего кода в будущем - например, когда вам понадобится какой-то другой метод связи и т. Д.

...