Как написать тест для выставления возврата задачи из блока использования? - PullRequest
2 голосов
/ 26 июня 2019

Я написал следующий ошибочный асинхронный метод, который проходит свой модульный тест, но не работает в рабочем режиме ( EDIT: в рабочем режиме он выдает ObjectDisposedException):

public class FileUtils {
    public Task<string> ReadAllText(string path)
    {
        using (var stream = ReadStreamAsync(path))
        using (var reader = new StreamReader(stream))
        {
            return reader.ReadToEndAsync();
        }
    }

    private static FileStream ReadStreamAsync(string path)
    {
        return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read,
            4096, FileOptions.Asynchronous);
    }
}

Ошибка заключается в том, что если вы вернете задачу из блока using, код будет работать только в том случае, если вызов ReadToEndAsync выполняется синхронно (что, очевидно, произошло в моем модульном тесте).

Правильный код добавляет async и await следующим образом:

    public async Task<string> ReadAllText(string path)
    {
        using (var stream = ReadStreamAsync(path))
        using (var reader = new StreamReader(stream))
        {
            return await reader.ReadToEndAsync();
        }
    }

У меня такой вопрос: Как мне написать модульный тест, который надежно провалится при неправильной версии кода?

РЕДАКТИРОВАТЬ: Вот текущий (недостаточный) модульный тест, который не выявляет эту проблему:

    [Test]
    public async Task GivenFileUtilsWhenReadAllTextThenGetsText()
    {            
        var fileUtils = new FileUtils(); // the prod code above is in class FileUtils
        var path = @"C:\tmp\foo.txt";
        var expected = "foo";
        File.WriteAllText(path, expected);

        var text = await fileUtils.ReadAllText(path);

        text.Should().Be(expected);
    }

Ответы [ 2 ]

1 голос
/ 27 июня 2019

Как мне написать модульный тест, который надежно провалится для неправильной версии кода?

Сформируйте его с точки зрения требований. Это то, что вы хотите проверить:

"SUT не должен удалять поток немедленно. Он может удалять поток после того, как поток полностью прочитан."

Чтобы сделать это, ваш юнит-тест должен контролировать:

  1. Поток утилизируется, поэтому он может определить, когда происходит удаление.
  2. Когда поток полностью прочитан.

Оба из них могут быть обработаны с использованием пользовательского типа потока.

Для первого требования заглушки ваш пользовательский тип потока может просто иметь свойство bool Disposed, которое устанавливается в true при вызове Dispose.

В соответствии со вторым требованием к заглушке, пользовательский тип потока может быть реализован таким образом, чтобы выполнять асинхронные операции только после получения сигнала. Один тип для "асинхронного сигнала" - TaskCompletionSource<T> - вы можете создать экземпляр внутри пользовательского потока, иметь каждый async метод await его свойство Task, и когда ваш модульный тест готов к завершению потока , это может завершить TaskCompletionSource<T>.

0 голосов
/ 27 июня 2019

Проблема в том, что вы не помещаете в файл достаточно содержимого, чтобы его можно было утилизировать при асинхронном чтении.

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

Чтобы доказать это, я просто добавил больше содержимого

for (int i = 0; i < 10; i++) { expected += expected; } 

перед записью в файл и запустил тест

var fileUtils = new FileUtils(); // the prod code above is in class FileUtils
var path = @"foo.txt";
var expected = "foo";
for (int i = 0; i < 15; i++) {
    expected += expected;
}
File.WriteAllText(path, expected);

var text = await fileUtils.ReadAllText(path);

text.Should().Be(expected);

и, конечно, исключение, вызванное удалением объекта, вызвало неудачный тест.

Вы уже нашли решение кода проблемы, ожидая в блоке using.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...