从测试范围中排除代码

时间:2009-07-01 16:35:11

标签: unit-testing tdd inversion-of-control mocking

我尽可能使用TDD:

  • 我嘲笑我的界面
  • 我使用IOC,因此可以注入我的模拟ojbects
  • 我确保我的测试运行,覆盖率增加,我很高兴。

...然后

  • 我创建实际执行操作的派生类,例如转到数据库或写入消息队列等。

这是代码覆盖率下降的地方 - 我感到很难过。

然而,我在这些具体课程中大肆宣传[CoverageExclude],并且覆盖范围再次上升。

然而,我感觉很脏,而不是感到难过。我不知何故觉得自己在作弊,尽管不可能对具体课程进行单元测试。

我有兴趣了解您的项目是如何组织的,即您如何在物理上安排可以无法进行测试的代码进行测试的代码。< / p>

我认为也许一个很好的解决方案是将不可测试的具体类型分离到它们自己的程序集中,然后禁止在包含可测试代码的程序集中使用[CoverageExclude]。当在可测试程序集中错误地找到此属性时,这也可以更容易地创建NDepend规则以使构建失败。


编辑:这个问题的本质涉及这样一个事实:你可以测试使用你的模拟接口的东西,但你不能(或不应该!)UNIT测试那些接口的真正实现的对象。这是一个例子:

public void ApplyPatchAndReboot( )
{ 
    _patcher.ApplyPatch( ) ;
    _rebooter.Reboot( ) ;
}

修补程序和重新启动程序在构造函数中注入:

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%的系统行为进行单元测试是件好事。在上面的示例中,修补程序的BEHAVIOR应该重新启动计算机。我们可以verify肯定。 ReallyRebootTheSytemForReal类型不仅仅是行为 - 它有副作用,这意味着它不能进行单元测试。由于它不能进行单元测试,因此会影响测试覆盖百分比。所以,

  • 这些事情会降低单位测试的覆盖率吗?
  • 他们是否应该被隔离到他们自己的集会中,人们期望0%UNIT-TEST覆盖?
  • 这样的具体类型是否应该如此之小(在Cyclomatic Complexity中),单元测试(或其他方式)是多余的

3 个答案:

答案 0 :(得分:4)

你走在正确的轨道上。您可能可以测试的一些具体实现,例如数据访问组件。当然,对关系数据库进行自动化测试是可能的,但也应该将其考虑在自己的库中(使用相应的单元测试库)。

由于您已经在使用依赖注入,因此将这种依赖关系反馈到您的实际应用程序中应该是一块蛋糕。

另一方面,也会有具体的依赖性,这些依赖性基本上是不可测试的(或者可以去测试,因为Fowler曾经开玩笑说)。这种实现应尽可能保持薄。通常,可以设计这样的依赖关系暴露的API,使得所有逻辑都在消费者中发生,并且实际实现的复杂性非常低。

实现这样的具体依赖关系是一个明确的设计决策,当你做出决定时,你同时决定不应该对这样的库进行单元测试,因此不应该测量代码覆盖率。

这样的库被称为Humble Object。它(以及许多其他模式)在优秀的xUnit Test Patterns中描述。

根据经验,如果代码具有 1 的Cyclomatic Complexity,则我接受该代码未经测试。在这种情况下,它或多或少纯粹是声明性的。实际上,只要具有循环复杂度,不可测试的组件就是有序的。你必须自己决定低“低”。

在任何情况下,[CoverageExclude]对我来说都是一种气味(在我阅读你的问题之前我甚至不知道它存在)。

答案 1 :(得分:1)

我不明白你的具体课程是如何不可测试的。这对我来说太可怕了。

如果你有一个写入消息队列的具体类,你应该能够传递一个模拟队列,并测试它的所有方法就好了。如果你的班级要去数据库,那么你应该能够把它交给模拟数据库去。

可能存在可能导致不可测试代码的情况,我不会否认 - 但这应该是例外,而不是规则。所有具体的类工作者对象?有些事情不对。

答案 2 :(得分:1)

扩大womps的答案:我怀疑你正在考虑更多的是“不可测试”而不是真的。在严格的“一次一个单元”单元测试中不可测试而不同时测试任何依赖性?当然。但是,应该可以通过较慢且不经常运行的集成样式测试轻松实现。

您提到访问数据库并将消息写入队列。正如womp所提到的,你可以在单元测试期间为它们提供模拟数据库和模拟队列,并在集成测试中测试实际的具体行为。就个人而言,我没有看到直接测试具体实现作为单元测试的任何问题,至少在他们不是远程(或遗留)时。当然它们运行得有点慢,但是嘿,至少它们会被自动化测试所覆盖。

您是否会将系统投入生产,将消息写入队列并且实际上没有测试过消息是否写入实际的物理/逻辑队列?我不愿意。