【发布时间】:2023-03-16 13:29:01
【问题描述】:
我尽可能使用 TDD:
- 我模拟了我的界面
- 我使用 IOC,因此可以注入我的模拟对象
- 我确保我的测试能够运行并且覆盖范围增加,我很高兴。
那么……
- 我创建派生类来实际做一些事情,例如访问数据库或写入消息队列等。
这是代码覆盖率降低的地方 - 我感到难过。
然后,我在这些具体的类上大肆传播[CoverageExclude],覆盖率再次上升。
但是,我没有感到悲伤,而是感到肮脏。尽管无法对具体类进行单元测试,但我还是觉得自己在作弊。
我有兴趣了解您的项目是如何组织的,即您如何实际安排可以测试的代码与无法测试的代码。
我认为也许一个不错的解决方案是将不可测试的具体类型分离到它们自己的程序集中,然后禁止在包含可测试代码的程序集中使用[CoverageExclude]。当在可测试程序集中错误地找到此属性时,这也可以更轻松地创建 NDepend 规则以使构建失败。
编辑:这个问题的本质涉及这样一个事实,即您可以测试使用模拟接口的东西,但您不能(或不应该!)对作为这些接口的真实实现的对象进行单元测试.这是一个例子:
public void ApplyPatchAndReboot( )
{
_patcher.ApplyPatch( ) ;
_rebooter.Reboot( ) ;
}
patcher 和 rebooter 被注入到构造函数中:
public SystemUpdater(IApplyPatches patcher, IRebootTheSystem rebooter)...
单元测试如下:
public void should_reboot_the_system( )
{
... new SystemUpdater(mockedPatcher, mockedRebooter);
update.ApplyPatchAndReboot( );
}
这很好 - 我的 UNIT-TEST 覆盖率为 100%。我现在写:
public class ReallyRebootTheSystemForReal : IRebootTheSystem
{
... call some API to really (REALLY!) reboot
}
我的 UNIT-TEST 覆盖率下降,无法对新课程进行 UNIT-TEST。当然,我会添加一个功能测试并在我有 20 分钟的空闲时间时运行它(!)。
所以,我想我的问题归结为一个事实,即拥有接近 100% 的 UNIT-TEST 覆盖率是件好事。换句话说,能够对接近 100% 的系统行为进行单元测试真是太好了。在上面的例子中,修补程序的行为应该重新启动机器。我们肯定可以verify。 ReallyRebootTheSytemForReal 类型不仅仅是行为——它有副作用,这意味着它不能进行单元测试。由于它不能进行单元测试,因此会影响测试覆盖率。所以,
- 这些东西是否会降低单元测试覆盖率?
- 是否应该将它们隔离到人们期望 0% 的 UNIT-TEST 覆盖率的自己的程序集中?
- 这样的具体类型是否应该如此小(在循环复杂度中)以至于单元测试(或其他)是多余的
【问题讨论】:
-
“无法测试”是什么意思??障碍是什么?
-
诸如触及不可触及区域的接口的特定实现之类的东西,例如消息队列、数据库、文件系统等。例如,可以模拟 IWriteToAQueue 之类的接口,并且所有期望 IWriteToAQueue 的位都可以使用模拟进行测试。但是 - 仅写入 MSMQ 的名为“WriteToMsmq”的具体类型无法进行单元测试。
标签: unit-testing tdd inversion-of-control mocking