单元测试是一种高度抽象的方法
本文关键字:一种 高度 抽象的 方法 单元测试 | 更新日期: 2023-09-27 18:10:18
在高级单元测试和模拟对象的价值
中讨论了类似的主题。然而,我想描述一个具体的情况,并询问您关于我应该如何编写单元测试的意见。
我正在开发一个普通的三层应用程序,它使用实体框架。在EF上面,我有两个图层:
- Repositories:它们直接访问EF ObjectContext并完成所有的CRUD工作(实际上,这些类是用T4模板生成的)。所有Repository类都有一个合适的接口。
- manager :它们实现更高级的业务逻辑,它们不直接访问ObjectContext,而是使用适当的Repository。管理人员不知道具体的repository实现,只知道接口(我在单元测试中使用依赖注入和模拟)。
不作进一步描述,下面是我想为其编写单元测试的类:
public class PersonManager
{
private IPersonRepository personRepository; // This is injected.
// Constructor for injection is here.
public void ComplexMethod()
{
// High level business logic
bool result = this.SimpleMethod1();
if(result)
this.SimpleMethod2(1);
else
this.SimpleMethod2(2);
}
public bool SimpleMethod1()
{
// Doing some low-level work with the repository.
}
public void SimpleMethod2(int param)
{
// Doing some low-level work with the repository.
}
}
通过用PersonRepository
的模拟实例化PersonManager
,可以很容易地对SimpleMethod1
和SimpleMethod2
进行单元测试。
但是我找不到任何方便的方法来单元测试ComplexMethod
。
ComplexMethod
中的方法调用使用this
引用,而是通过接口访问PersonManager
本身,并将其替换为mock ?提前感谢您的建议
纪尧姆的回答很好(+1),但我想给出一个额外的观察。我在你发布的代码中看到的是人们试图弄清楚(或反对)TDD的一个非常常见的问题的基础,这是:
"我如何/为什么要测试ComplexMethod(),因为它依赖于SimpleMethod1()和SimpleMethod2(),这已经测试过了,并且有自己的行为,我必须在ComplexMethod()的测试中考虑?"为了完全测试ComplexMethod(),我基本上必须复制SimpleMethod1()和SimpleMethod2()的所有测试,这是愚蠢的。"
之后,他们通常会发现部分模拟。使用部分模拟,您可以模拟SimpleMethod1()和SimpleMethod2(),然后使用正常的模拟机制测试ComplexMethod()。"听起来不错,"他们想,"这将完美地解决我的问题!"一个好的模拟框架应该强烈反对以这种方式使用部分模拟,因为现实情况是:
你的测试告诉你一个设计问题。
具体地说,它们告诉您,您在一个类中混合了关注点和/或抽象级别。它们告诉你SimpleMethod1()和SimpleMethod2()应该被提取到这个类所依赖的另一个类中。无论我多少次看到这种场景,无论开发人员如何激烈地争论,最终测试都被证明是正确的。
我不明白是什么问题。您可以在模拟存储库的同时测试您的复杂方法,没有问题。
您将需要两个单元测试,每个单元测试将使用与您在SimpleMethod1测试中相同的期望和执行顺序(我假设您已经为SimpleMethod1进行了两个单元测试,一个用于返回"true",一个用于返回"false"),并且您对测试SimpleMethod2具有相同的期望,分别具有固定参数1或2。
当然,在你的测试类中会有一些"重复",但这不是问题。
还请注意,您的SimpleMethod2测试不应该对传递的参数做任何假设:在"现实生活"中,您只能将1或2作为参数(这就是您的ComplexMethod单元测试将具有的),但您的SimpleMethod2单元测试应该测试它,无论参数是什么:任何int。
最后,如果ComplexMethod是调用SimpleMethod1和/或SimpleMethod2的唯一方法,你应该考虑使它们私有,并且只对ComplexMethod进行单元测试。
明白了吗?