条件编译是用于单元测试的有效模拟/存根策略吗?
在有关存根的最新问题中,许多答案建议使用Cinterface或者委托来实现存根,但是一个答案建议使用条件编译,并在生产代码中保留静态绑定。在阅读本文时,此答案被修改为-2,因此至少有2个人确实认为这是错误的答案。可能是滥用DEBUG的原因,或者也许使用固定值而不是更广泛的验证。但是我不禁要问:
使用条件编译是否不适合用于实施单元测试存根?有时?总是?
谢谢。
编辑添加:我想通过一个实验添加一个示例:
class Foo { public Foo() { .. } private DateTime Now { get { #if UNITTEST_Foo return Stub_DateTime.Now; #else return DateTime.Now; #endif } } // .. rest of Foo members }
比较
interface IDateTimeStrategy { DateTime Now { get; } } class ProductionDateTimeStrategy : IDateTimeStrategy { public DateTime Now { get { return DateTime.Now; } } } class Foo { public Foo() : Foo(new ProductionDateTimeStrategy()) {} public Foo(IDateTimeStrategy s) { datetimeStrategy = s; .. } private IDateTime_Strategy datetimeStrategy; private DateTime Now { get { return datetimeStrategy.Now; } } }
这允许通过Cinterface存根对" DateTime.Now"的传出依赖性。但是,我们现在添加了一个动态调度调用,其中静态就足够了,即使在生产版本中该对象也更大,并且为Foo的构造函数添加了新的失败路径(分配可能失败)。
我在这里不用担心吗?到目前为止,感谢反馈!
解决方案
我认为这降低了人们查看代码的清晰度。我们不必记住特定代码周围有一个条件标签来理解上下文。
不,这太可怕了。它将测试泄漏到生产代码中(即使条件已关闭)
不好不好
当我们在大型代码库中重构可测试性时,它可能会作为一种依靠的工具。我可以看到我们如何使用此类技术来进行较小的更改并避免"大爆炸"重构。但是,我会担心过分倾向于这种技术,并会尝试确保这些技巧在代码库中的寿命不会太长,否则我们将冒使应用程序代码非常复杂且难以遵循的风险。
测试代码应该是显而易见的,并且不能与测试代码混在一起。
这与我们不应该写的原因几乎相同
if (globals.isTest)
尝试将生产代码与测试代码分开。维护不同的文件夹层次结构..不同的解决方案/项目。
除非..我们处在传统C ++代码的世界中。如果条件块获得了一些可测试的代码,并且我们看到了好处,那么一切都将顺利进行。但是,请不要让它变得比初始状态更混乱。清楚地注释和划分条件块。谨慎行事。这是在测试工具下获取遗留代码的有效技术。
我想到了另一个可怕的原因:
很多时候,我们对某些东西进行模拟/存根,我们希望其方法根据测试内容返回不同的结果。这或者阻止了它,或者使其变得笨拙。