我编写了以下错误的异步方法,该方法通过了其单元测试,但在生产中失败(编辑:在生产中会抛出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>
正确的代码将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);
}
答案 0 :(得分:1)
我该如何编写一个单元测试,以确保由于代码版本不正确而导致失败?
根据要求进行构架。这是您要测试的:
“ SUT不应立即处置该流。它可能在完全读取该流之后处置该流。”
为此,您的单元测试需要控制:
这两种方法都可以使用自定义流类型进行处理。
对于第一个存根需求,您的自定义流类型只能具有一个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
块中等待,您已经找到了解决问题代码的方法。
编写测试以发现您要寻找的东西相当困难,但是代码分析器在编译时发现这些错误的机会更大。