我正在尝试为一个类编写单元测试,该类实例化其中的其他类,但正在努力解决如何以可测试的方式实例化这些类。我知道依赖注入(inject),但这有些不同,因为实例化不会在构造函数中发生。
这个问题确实不是特定于 MVVM 和 C#,但这就是我的示例将使用的内容。请注意,我已经简化了它,它不会按原样编译 - 目标是显示一种模式。
class ItemListViewModel
{
ItemListViewModel(IService service)
{
this.service.ItemAdded += this.OnItemAdded;
}
List<IItemViewModel> Items { get; }
OnItemAdded(IItemModel addedItem)
{
var viewModel = new ItemViewModel(addedItem);
this.Items.Add(viewModel);
}
}
class ItemViewModel : IItemViewModel
{
ItemViewModel(IItem) {}
}
从上面可以看出,有一个来自模型层的事件。 ViewModel 监听该事件,并作为响应添加一个新的子 ViewModel。这符合我所了解的标准面向对象编程实践以及 MVVM 模式,对我来说感觉是一个非常干净的实现。
当我想对这个 ViewModel 进行单元测试时,问题就出现了。虽然我可以使用依赖项注入(inject)轻松模拟服务,但我无法模拟通过事件添加的项目。这引出了我的主要问题:根据 ItemViewModel 的真实版本而不是模拟版本编写单元测试可以吗?
我的直觉:这不行,因为我现在本质上测试的不仅仅是 ItemListViewModel,特别是如果 ItemListViewModel 在内部调用任何项目上的任何方法。我应该在测试期间让 ItemListViewModel 依赖于模拟 IItemViewModels。
我考虑了一些如何做到这一点的策略:
- 让 ItemListViewModel 的所属类监听事件并添加模拟出的项目。但这只是解决了问题,因为现在拥有的类无法完全模拟出。
- 将 ItemViewModel 工厂传递给 ItemListViewModel 并使用它而不是新的。这肯定适用于模拟,因为它将事物移动到基于依赖项注入(inject)......但这是否意味着我需要一个工厂对于我想在应用程序中模拟的每个类(class)?这感觉不对,而且维护起来会很痛苦。
- 重构我的模型以及它与 ViewModel 的通信方式。也许我使用的事件模式不适合测试;尽管我不知道如何解决需要在需要测试的代码中最终构造 ItemViewModel 的问题。
另外,我在网上搜索过《Clean Code》这本书,但确实没有涉及到这一点。一切都在谈论依赖注入(inject),但这并不能明确解决这个问题。
最佳答案
While I can easily mock out the service using dependency injection, I'm unable to mock out items added through the event.
Misko Hevery 写了有关此模式的文章:How to Think About the New Operator
If you mix application logic with graph construction (the new operator) unit-testing becomes impossible for anything but the leaf nodes in your application.
因此,如果我们查看您的问题代码:
OnItemAdded(IItemModel addedItem)
{
var viewModel = new ItemViewModel(addedItem);
this.Items.Add(viewModel);
}
然后我们可以考虑的一项更改是用更间接的方法替换对 ItemViewModel::new
的直接调用
var viewModel = factory.itemViewModel(addedItem);
其中factory
提供了创建ItemViewModel的功能,并且设计允许我们提供替代品。
ItemListViewModel(IService service, Factory factory)
{
this.service.ItemAdded += this.OnItemAdded;
this.factory = factory;
}
完成此操作后,您可以(在适当的时候)使用 Factory 来提供项目 View 模型的一些更简单的实现。
什么时候这很重要?需要注意的一件事是,您询问的是 ItemViewModel,但不是询问List。这是为什么?
几个答案:列表是稳定的;我们根本不担心 List 本身的行为会发生变化,从而导致 ItemListViewModel 的行为发生可观察到的变化。如果测试稍后报告问题,那么毫无疑问我们在代码中引入了错误。
此外,this.List
(大概)是孤立的。我们不必担心我们的测试结果会不稳定,因为其他一些代码正在同时运行。换句话说,测试不易受到共享可变状态引起的问题的影响。
如果这些属性也适用于 ItemViewModel,那么在代码中添加一堆仪式来创建这两个实现之间的分离实际上并不会让您的设计变得“更好”。
关于c# - 实例化其他类的单元测试类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60330954/