>我嘲笑我的界面
>我使用IOC,因此可以注入我的模拟ojbects
>我确保我的测试运行,覆盖率增加,我很高兴.
然后…
>我创建实际执行操作的派生类,例如转到数据库或写入消息队列等.
这是代码覆盖率下降的地方 – 我感到很难过.
但随后,我在这些具体课程中大量传播[CoverageExclude],并且覆盖范围再次上升.
但后来感觉不舒服,我觉得很脏.我不知何故觉得我在作弊,尽管不可能对具体课程进行单元测试.
我有兴趣了解您的项目是如何组织的,即您如何物理安排可以针对无法测试的代码进行测试的代码.
我想也许一个很好的解决方案是将不可测试的具体类型分离到它们自己的程序集中,然后禁止在包含可测试代码的程序集中使用[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应该重新启动计算机.我们可以肯定地验证. ReallyRebootTheSytemForReal类型不仅仅是行为 – 它有副作用,这意味着它不能进行单元测试.由于它不能进行单元测试,因此会影响测试覆盖百分比.所以,
>这些事情是否会降低单位测试覆盖率?
>他们是否应该被隔离到他们自己的集会中,人们期望0%UNIT-TEST覆盖?
>如果这样的具体类型如此之小(在Cyclomatic Complexity中),单元测试(或其他)是多余的
由于您已经在使用依赖注入,因此将这种依赖关系反馈到您的实际应用程序中应该是件小事.
另一方面,也会有具体的依赖性,这些依赖性基本上是不可测试的(或者可以解决,正如Fowler曾经开玩笑的那样).这种实现应尽可能保持薄.通常,可以设计这样的依赖关系暴露的API,使得所有逻辑都在消费者中发生,并且实际实现的复杂性非常低.
实现这样的具体依赖关系是一个明确的设计决策,当您做出决定时,您同时决定不应对这样的库进行单元测试,因此不应测量代码覆盖率.
这样的库被称为Humble Object.它(和许多其他模式)在优秀的xUnit Test Patterns中描述.
根据经验,我接受如果代码的循环复杂度为1,则代码未经测试.在这种情况下,它或多或少纯粹是声明性的.实际上,只要具有低的Cyclomatic Complexity,不可测试的组件就是有序的.你必须自己决定低’低’.
在任何情况下,[CoverageExclude]对我来说都是一种气味(在我阅读你的问题之前我甚至都不知道它存在).