在进行单元测试时,应该如何处理嵌套的 ViewModel?

What should you do about nested ViewModels when you are unit testing?

我正在为项目中的 ViewModel 创建一些单元测试。我并没有真正遇到问题,因为它们中的大多数都非常简单,但是 运行 当我在我的另一个 ViewModel 中有一个新的(未完成的)ViewModel 时遇到了问题。

public class OrderViewModel : ViewModelBase
{
    public OrderViewModel(IDataService dataService, int orderId)
    {
        \ ...

        Payments = new ObservableCollection<PaymentViewModel>();
    }

    public ObservableCollection<PaymentViewModel> Payments { get; private set; }

    public OrderStatus Status { ... } //INPC

    public void AddPayment()
    {
        var vm = new PaymentViewModel();

        payments.Add(vm);

        // TODO: Subscribe to PaymentViewModel.OnPropertyChanged so that
        // if the payment is valid, we update the Status to ready.
    }
}

我想创建一个单元测试,这样如果我的 PaymentViewModel 的任何 IsValid 属性 发生变化并且所有这些都是真的 Status 应该是OrderStatus.Ready。我可以实现 class,但让我担心的是,如果问题出在 PaymentViewModel.

,我的单元测试将会中断

我不确定这是否可以,但感觉我不应该担心 PaymentViewModel 是否正常运行以进行我的单元测试 OrderViewModel 正确。

public void GivenPaymentIsValidChangesAndAllPaymentsAreValid_ThenStatusIsReady()
{
    var vm = new OrderViewModel();

    vm.AddPayment();
    vm.AddPayment();

    foreach (var payment in vm.Payments)
    {
        Assert.AreNotEqual(vm.Status, OrderStatus.Ready);

        MakePaymentValid(payment);
    }

    // Now all payments should be valid, so the order status should be ready.
    Assert.AreEqual(vm.Status, OrderStatus.Ready);
}

问题是,我如何编写 MakePaymentValid 才能确保 PaymentViewModel 的行为不会对我的单元测试产生负面影响?运行?因为如果是这样,那么我的单元测试将失败,因为另一段代码不起作用,而不是我的代码。或者,如果 PaymentViewModel 也是错误的,它应该失败吗?我只是因为我不认为如果 PaymentViewModel 有错误,我对 OrderViewModel 的测试应该失败。

我意识到我总是可以像使用 IDataService 那样创建一个界面,但在我看来,让每个 ViewModel 都有一个界面并注入一些界面似乎有点矫枉过正怎么样?

谈到单元测试时,您绝对应该将测试与任何外部依赖项分开。请记住,这并不意味着您必须传入某个接口;您将 运行 遇到使用特定 class 的情况,无论 class 是否在您的控制范围内。

想象一下,您依赖的不是您的示例,而是 DateTime.Now。有些人会争辩说将它抽象成某种可以工作的接口 IDateTimeService。或者,您可以利用 Microsoft Fakes:Shims & Stubs。

Microsoft Fakes 将允许您创建 Shim* 实例。关于这个主题有很多内容需要讨论,但 Microsoft 提供的图片说明了 Fakes 的使用超出了 class 您无法控制的范围(它也包括您控制范围内的组件)。

注意您正在测试的组件 (OrderViewModel) 应如何与 System.dll(即 DateTime.Now)、其他组件(PaymentViewModel)和外部项目隔离同样(如果您依赖数据库或 Web 服务)。 Shim 用于伪造 classes,而 Stub 用于伪造(模拟)接口。


添加 Fakes 程序集后,只需使用 ShimPaymentViewModel class 即可提供您期望的行为。如果出于某种原因,真正的 PaymentViewModel class 行为不当并且您的应用程序崩溃,您至少可以确信问题不是由于 OrderViewModel。当然,要避免这种情况,您应该为 PaymentViewModel 包含一些单元测试,以确保无论其他 class 正在使用它或他们如何使用它,它都能正常运行。

TL;DR;

是的,在使用 Microsoft Fakes 进行测试时完全隔离您的组件。哦,Microsoft Fakes 确实可以很好地与其他框架配合使用,所以不要因为使用它而放弃其他选择;它与其他框架结合使用。