如何测试异步的 void 方法

4
请考虑下面展示的代码。通过调用GetBrands,Brands属性将被赋予适当的数据。
public class BrandsViewModel : ViewModelBase
{
    private IEnumerable<Brand> _brands;
    public IEnumerable<Brand> Brands
    {
        get { return _brands; }
        set { SetProperty(ref _brands, value); }
    }

    public async void GetBrands()
    {
        // ......

        Brands = await _dataHelper.GetFavoriteBrands();

        // ......
    }
}

但是,如果我按照下面所示的方式进行测试,则测试失败。我该如何等待GetBrands方法内部的异步调用?

[TestMethod]
public void AllBrandsTest()
{
    BrandsViewModel viewModel = new BrandsViewModel();
    viewModel.GetBrands();
    Assert.IsTrue(viewModel.Brands.Any());
}

3
请阅读/观看此内容:http://blogs.msdn.com/b/lucian/archive/2013/02/18/talk-the-new-async-design-patterns.aspx基本上它说:除了事件处理程序外,不要使用async void - Daniel Hilgarth
@DanielHilgarth 谢谢你,Daniel。我重构了我的代码,现在它就像魔术般地运行。 - Shinbo
2个回答

9
简单的答案是:不要将其设为async void。实际上,除非必须作为事件处理程序才能工作,否则永远不要将某些内容设置为async void。您想要进行测试(以及可能是真正的代码)的东西,async void所失去的恰恰是您需要的东西。

相反,将其设为async Task方法,现在您可以等待完成(带有超时)/添加继续,并检查它是否成功退出或出现异常。

这只是一个单词的更改:

public async Task GetBrands()
{
    // ......

    Brands = await _dataHelper.GetFavoriteBrands();

    // ......
}

然后在测试中:

[TestMethod]
public async Task AllBrandsTest()
{
    BrandsViewModel viewModel = new BrandsViewModel();
    var task = viewModel.GetBrands();
    Assert.IsTrue(task.Wait(YOUR_TIMEOUT), "failed to load in time");
    Assert.IsTrue(viewModel.Brands.Any(), "no brands");
}

如果你在测试中没有使用await,为什么要将测试标记为async - svick
@svick 这是一个问题,需要问原帖作者,他也没有在其中使用 await :) - Marc Gravell
@svick 我的错。我已经纠正了我的测试代码。如果方法中没有await,就不需要保持async。 - Shinbo

1
你的模型(一个 DTO)正在填充自身(数据访问)。这对于一个类来说太多了。通常当你问自己“我该如何测试这个”时,就是重构的时候了。创建一个单独的数据访问类:
BrandsViewModel viewModel = new BrandsViewModel();
var brandAccess = new BrandsDataAccess();
viewModel.Brands = await brandAccess.GetAllBrands();
Assert.IsTrue(viewModel.Brands.Any());

现在,您可以测试 BrandsDataAccess.GetAllBrands()

async 去哪里了?我的意思是:原始代码是 async,你的不是,所以我不认为它是一个好的替代品。 - svick
@svick,重要的是原则,而不是实现方式。请查看编辑。 - CodeCaster
@CodeCaster,您能否进一步解释一下您的观点?就单一职责原则而言,我认为在同一类中填充其自身属性是可以接受的。不是吗? - Shinbo
@Shinbo 不,数据传输对象(也就是视图模型)不应该关心数据访问。 - CodeCaster
@CodeCaster 感谢您澄清。如果DTO包含数据访问逻辑,会有什么副作用? - Shinbo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接