NSubstitute создание события на поддельном подклассе - PullRequest
0 голосов
/ 17 февраля 2019

У меня есть обертка вокруг класса из внешней библиотеки (в данном случае это WebSocketSharp), класс обертки реагирует на определенные события, например, когда было установлено соединение и т. Д.

Для проверки этого класса обертки IЯ издевался над классом WebSocket и выполнял Raise.EventWith с этим классом, и я ожидаю, что класс Wrapper будет выполнять его без обработки.

Код выглядит примерно так:

public class WebsocketClient {
    public event EventHandler Connected;

    public WebSocket Connection { get;set; }

    public void ConnectAsync() {
        Connection.OnOpen += Connection_OnOpen;
        Connection.ConnectAsync();
    }

    private void Connection_OnOpen(object sender, System.EventArgs e) {
        Connected?.Invoke(this, new EventArgs());
    }
}

Тест, который я хотел написать, таков:

public void ConnectedTest() {
    var objClient = new WebsocketClient();
    var raised = false;
    objClient.Connected += delegate (object sender, EventArgs e) {
        raised = true;
    }; 
    var objWebSocket = Substitute.For<WebSocketSharp.WebSocket>("wss://localhost:443");
    objClient.Connection = objWebSocket;
    objClient.ConnectAsync();
    objClient.Connection.OnOpen += Raise.EventWith(new object(), new EventArgs());

    objWebSocket.Received().OnOpen += Arg.Any<EventHandler>();
    Assert.IsTrue(raised);
}

Очевидно, что все немного упростили, поскольку есть еще несколько вещей, которые нужно проверить, это просто чтобы понять идею.

Здесь, в тесте, я хочу проверить две вещи: когда вызывается ConnectAsync, к событию OnOpen добавляется обработчик событий, а когда срабатывает OnOpen, я получаю событие из класса, который тестирую.

Что я знаю, ответ будет таков, что это «плохой дизайн», но это не очень мне помогает, как бы вы решили это ?, Мне нужно обернуть класс WebSocket, этоэто не мое, так что не говорите об этом.

Единственная вещь в этом сценарии, о которой я могу думать, это расширение класса WebSocket вместо создания оболочки, но я уверен, что есть другие тесты, которые мне нужно сделать, гдерасширение не является опцией, и для этого потребуется написание оболочки, например, при использовании файловой системыwatcher или таймера, где обработчик событий является частным и все еще хочет проверить, что происходит, когда запускается четное число.

Комупродемонстрировать, как это, например, будет проверено?(ничего не запускалось, это просто чтобы показать идею)

public class FilesDeleted {
    private FileSystemWatcher _objWatcher;
    private List<string> _lstPaths;

    public event EventHandler ItemsDeleted;

    public FilesDeleted(string pPath) {
        _lstPaths = new List<string>();
        _objWatcher = new FileSystemWatcher(pPath);
    }

    public void Start() {
        _objWatcher.Deleted += _objWatcher_Deleted;
        _objWatcher.EnableRaisingEvents = true;
    }

    private void _objWatcher_Deleted(object sender, FileSystemEventArgs e) {
        _lstPaths.Add(e.FullPath); 
        if(_lstPaths.Count > 10) {
            ItemsDeleted?.Invoke(this, new EventArgs());
        }
    }
}

В тесте вы хотели бы убедиться, что после 10 событий удаления файлов из файловой системы вы получаете событие "ItemsDeleted" из этогоclass.

Думаю, пример FileSystemWatcher показывает, с чем я бьюсь больше всего

1 Ответ

0 голосов
/ 17 февраля 2019

Ваше утверждение о плохом дизайне является верным.Проблема здесь в том, что вы тесно связаны с проблемами реализации третьей части, которые не контролируются вами, что затрудняет изолированное тестирование кода.

Создайте абстракцию для желаемой функциональности

public interface IWebSocket {
    event EventHandler OnOpen;
    void ConnectAsync();

    //... other members
}

иметь эту абстракцию в явном виде в целевом классе

public class WebsocketClient {
    private readonly IWebSocket connection;

    public WebsocketClient(IWebSocket connection) {
        this.connection = connection;
    }

    public event EventHandler Connected = delegate { };

    public void ConnectAsync() {
        connection.OnOpen += Connection_OnOpen;
        connection.ConnectAsync();
    }

    private void Connection_OnOpen(object sender, System.EventArgs e) {
        Connected.Invoke(this, new EventArgs());
    }
}

Обратите внимание, что целевому классу больше не нужно выставлять / раскрывать проблемы реализации.

В рабочем коде реализация абстрагированноговеб-сокет обернет фактическую стороннюю зависимость.Это то, что будет внедрено в зависимые классы во время выполнения.

public class DefaultWebSocketWrapper : IWebSocket {
    private WebSocket webSocket;

    public DefaultWebSocketWrapper() {
        webSocket = new WebSocket("wss://localhost:443");
    }

    public event EventHandler OnOpen {
        add {
            webSocket.OnOpen += value;
        }
        remove {
            webSocket.OnOpen -= value;
        }
    }

    public void ConnectAsync() {
        webSocket.ConnectAsync();
    }

    //... other members
}

Этот класс не нуждается в проверке, поскольку он является просто оболочкой для внешнего кода, который вы не можете контролировать.Таким образом, тестирование будет пустой тратой времени.

Конечным результатом является то, что теперь ваш код отделен от внешних зависимостей и может тестироваться изолированно без влияния на эффекты,

[TestClass]
public class WebSocketTests {
    [Test]
    public void ConnectedTest() {
        //Arrange
        var webSocketMock = Substitute.For<IWebSocket>();

        var subject = new WebsocketClient(webSocketMock);
        bool raised = false;
        subject.Connected += delegate(object sender, EventArgs e) {
            raised = true;
        };
        subject.ConnectAsync();

        //Act
        webSocketMock.OnOpen += Raise.Event();

        //Assert
        Assert.IsTrue(raised);
    }
}

.Выше безопасно тестирует WebsocketClient, не беспокоясь о внешних сторонних зависимостях, так как вы контролируете весь используемый код.

Такой же подход можно использовать с FileSystemWatcher, как описано в вашем вопросе.

...