如何编写测试以暴露从using块返回的任务?

时间:2019-06-26 20:57:49

标签: c# unit-testing async-await

我编写了以下错误的异步方法,该方法通过了其单元测试,但在生产中失败(编辑:在生产中会抛出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调用恰好同步执行(这显然发生在我的单元测试中)时,代码才起作用。 / p>

正确的代码将asyncawait添加如下:

    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 个答案:

答案 0 :(得分:1)

  

我该如何编写一个单元测试,以确保由于代码版本不正确而导致失败?

根据要求进行构架。这是您要测试的:

“ SUT不应立即处置该流。它可能在完全读取该流之后处置该流。”

为此,您的单元测试需要控制:

  1. 正在处理的流,因此它可以检测何时发生处理。
  2. 当流被完全读取时。

这两种方法都可以使用自定义流类型进行处理。

对于第一个存根需求,您的自定义流类型只能具有一个bool Disposed属性,该属性在调用true时设置为Dispose

对于第二个存根需求,可以将您的自定义流类型实现为仅在接收到信号之后完成异步操作。一种“异步信号”是TaskCompletionSource<T>-您可以在自定义流中创建实例,让每个async方法await拥有其Task属性,以及在您的设备上测试已准备就绪,可以完成流,它可以完成TaskCompletionSource<T>

答案 1 :(得分:0)

问题在于,您没有在文件中放入足够的内容以使在异步读取时将其丢弃。

因此,并非您的代码是同步执行的。它完成任务的速度太快,无法在生产中引起问题。

为了证明这一点,我刚刚添加了更多内容

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块中等待,您已经找到了解决问题代码的方法。

编写测试以发现您要寻找的东西相当困难,但是代码分析器在编译时发现这些错误的机会更大。