条件编译是单元测试的有效模拟/存根策略吗?
在最近关于存根的问题中,许多答案建议使用 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
}
与 Which 相比
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 的构造函数添加了一个新的失败路径(分配可能会失败)。
我在这里什么都不用担心吗? 感谢迄今为止的反馈!
In a recent question on stubbing, many answers suggested C# interfaces or delegates for implementing stubs, but one answer suggested using conditional compilation, retaining static binding in the production code. This answer was modded -2 at the time of reading, so at least 2 people really thought this was a wrong answer. Perhaps misuse of DEBUG was the reason, or perhaps use of fixed value instead of more extensive validation. But I can't help wondering:
Is the use of conditional compilation an inappropriate technique for implementing unit test stubs? Sometimes? Always?
Thanks.
Edit-add: I'd like to add an example as a though experiment:
class Foo {
public Foo() { .. }
private DateTime Now {
get {
#if UNITTEST_Foo
return Stub_DateTime.Now;
#else
return DateTime.Now;
#endif
}
}
// .. rest of Foo members
}
comparing to
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; } }
}
Which allows the outgoing dependency on "DateTime.Now" to be stubbed through a C# interface. However, we've now added a dynamic dispatch call where static would suffice, the object is larger even in the production version, and we've added a new failure path for Foo's constructor (allocation can fail).
Am I worrying about nothing here? Thanks for the feedback so far!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
尝试将生产代码与测试代码分开。 维护不同的文件夹层次结构..不同的解决方案/项目。
除非..您处于遗留 C++ 代码的世界中。 一切都会发生..如果条件块帮助您获得一些可测试的代码并且您会看到好处..无论如何都要这样做。 但尽量不要让它变得比初始状态更混乱。 清晰地注释和划分条件块。 谨慎行事。 这是一种在测试工具下获取遗留代码的有效技术。
Try to keep production code separate from test code. Maintain different folder hierarchies.. different solutions/projects.
Unless.. you're in the world of legacy C++ Code. Here anything goes.. if conditional blocks help you get some of the code testable and you see a benefit.. By all means do it. But try to not let it get messier than the initial state. Clearly comment and demarcate conditional blocks. Proceed with caution. It is a valid technique for getting legacy code under a test harness.
测试代码应该是显而易见的,并且不要与测试代码混合在同一块中。
这与你不应该写的原因几乎相同
Test code should be obvious and not inter-mixed in the same blocks as the tested code.
This is pretty much the same reason you shouldn't write
不,这太可怕了。 它将测试泄漏到您的生产代码中(即使其条件关闭)
非常糟糕。
No this is terrible. It leaks test into your production code (even if its conditioned off)
Bad bad.
我认为这降低了人们审查代码的清晰度。 您不必记住特定代码周围有一个条件标记来理解上下文。
I think it lessens the clarity for people reviewing the code. You shouldn't have to remember that there's a conditional tag around specific code to understand the context.
我想到了这很糟糕的另一个原因:
很多时候你模拟/存根某些东西,你希望它的方法根据你正在测试的内容返回不同的结果。 这要么排除了这一点,要么让它变得非常尴尬。
I thought of another reason this was terrible:
Many times you mock/stub something, you want its methods to return different results depending on what you're testing. This either precludes that or makes it awkward as all heck.
当您在大型代码库中重构可测试性时,它可能会作为一种有用的工具来依靠。 我可以看到您如何使用此类技术来实现较小的更改并避免“大爆炸”重构。 但是,我会担心过于依赖这种技术,并会尽力确保此类技巧不会在代码库中存在太久,否则您可能会面临使应用程序代码变得非常复杂且难以遵循的风险。
It might be useful as a tool to lean on as you refactor to testability in a large code base. I can see how you might use such techniques to enable smaller changes and avoid a "big bang" refactoring. However I would worry about leaning too hard on such a technique and would try to ensure that such tricks didn't live too long in the code base otherwise you risk making the application code very complex and hard to follow.