在进行单元测试时,应该如何处理嵌套的 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 确实可以很好地与其他框架配合使用,所以不要因为使用它而放弃其他选择;它与其他框架结合使用。
我正在为项目中的 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 确实可以很好地与其他框架配合使用,所以不要因为使用它而放弃其他选择;它与其他框架结合使用。