条件编译是单元测试的有效模拟/存根策略吗?
-
01-07-2019 - |
题
在最近关于存根的问题中,许多答案建议使用 C# 接口或委托来实现存根,但是 一个答案 建议使用条件编译,在生产代码中保留静态绑定。这个答案在阅读时被修改为-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; } }
}
这允许通过 C# 接口对“DateTime.Now”的传出依赖进行存根。然而,我们现在添加了一个动态调度调用,其中静态就足够了,即使在生产版本中对象也更大,并且我们为 Foo 的构造函数添加了一个新的失败路径(分配可能会失败)。
我在这里什么都不用担心吗?感谢迄今为止的反馈!
解决方案
尝试将生产代码与测试代码分开。维护不同的文件夹层次结构..不同的解决方案/项目。
除非..您正处于遗留 C++ 代码的世界中。这里什么都可以..if 条件块可以帮助您获得一些可测试的代码,并且您会看到好处..无论如何都要这样做。但尽量不要让它变得比初始状态更混乱。清晰地注释和划分条件块。谨慎行事。这是一种在测试工具下获取遗留代码的有效技术。
其他提示
我认为这降低了人们审查代码的清晰度。您不必记住特定代码周围有一个条件标记来理解上下文。
不,这太可怕了。它将测试泄漏到您的生产代码中(即使其条件关闭)
糟糕糟糕。
测试代码应该是显而易见的,并且不要与测试代码混合在同一块中。
这与你不应该写的原因几乎相同
if (globals.isTest)
我想到了另一个可怕的原因:
很多时候,您模拟/存根某些东西,您希望它的方法根据您正在测试的内容返回不同的结果。这要么排除了这一点,要么让它变得非常尴尬。
当您在大型代码库中重构可测试性时,它可能会作为一种有用的工具来依靠。我可以看到您如何使用此类技术来实现较小的更改并避免“大爆炸”重构。然而,我会担心过于依赖这种技术,并会尽力确保此类技巧不会在代码库中存在太久,否则您可能会面临使应用程序代码变得非常复杂且难以遵循的风险。