测试“代理”;好的TDD还是代码味道?

时间:2011-01-05 18:33:16

标签: unit-testing tdd

当我为某些类型的对象编写测试时,例如Forms或UserControls之类的UI元素,我经常发现自己正在改变我的TDD模式;而不是先测试,我定义并布置窗体的控件,提供“骨架”,然后开始编写行为测试(数据绑定/“解除绑定”,显示模式行为等)。在这样做的过程中,我发现自己与非公共成员打交道。我也遇到了与其他行为方法相同的问题;在担心其他方法及其行为的使用之前,我可能想要专注于并在另一个方法调用的私有帮助器中运行逻辑。

对我来说,让一切公开(有时候是虚拟的)只是为了能够对所有东西进行单元测试是一种气味;我不希望其他对象能够调用帮助程序,或直接访问文本框;但我需要知道帮助器完成它的工作,文本框在表单加载时获取它的值。

我前段时间提到的解决方案是为测试中的实际对象创建一个“测试代理”。代理派生于被测对象,并不隐藏或覆盖任何行为,但它确实提供了内部可见的getter,setter和/或方法,可以调用被测对象的非公共成员,允许我告诉对象执行某些操作然后我可以查看结果,而不要求测试也依赖于对象内的正确集成,或者只是为了测试目的而在生产代码中公开该方法或其他一些感兴趣的成员。

我看到的优点:

  • 会员的知名度不取决于您是否需要进行单元测试。
  • 更好地控制测试中对象的功能,可以进行更灵活,可扩展的测试。

我看到的缺点:

  • 课程数量增加,仅为测试目的而开发额外级别。
  • 必须注意不要以某种方式最终在生产代码中使用测试代理(使构造函数或整个类内部通常都能解决问题)
  • 在某种程度上,不是“纯”单元测试,而是依赖于代理与被测对象之间的集成。

问题是,这是构建单元测试的有效方法,还是我必须这样做的事实表明代码或测试策略存在问题?

3 个答案:

答案 0 :(得分:2)

我对这种模式的第一反应是你不再强调TDD中的'D'。您的代码已经过测试,但这些测试并没有推动您的设计,因此您最终得到的代码与您先编写测试时的结构不同。具有比必要的更难以访问的私有状态的结构。一般来说,我认为如果你不能使用公共接口测试你的类的行为,那么你要么编写一个没有意义的测试(测试实现细节),要么你的公共接口设计不合理。

但是,如果您正在使用视图类,这会变得有点复杂,因为您通过视图有“公共”输入和输出,您想要测试但不一定使用此视图组件对代码进行公开。在这种情况下,我认为让测试访问该用户界面是有意义的;通过将那些通常的私有属性暴露给测试(您的代理是一个选项而其他可能根据您使用的语言可用)或者通过编写某种形式的功能测试来驱动UI(同样可用的工具取决于您的环境) )。

答案 1 :(得分:1)

我想说这表明您的测试策略或代码存在问题。原因是因为你的违规封装会将你的测试耦合到实现而不是接口。这将增加您的整体工作,因为重构(例如)可能不再是免费的。

话虽如此,我认为有很好的理由违反封装,并且它们围绕着具有副作用的功能(这通常是UI编程的情况)。在许多情况下,您需要确保按特定顺序调用函数或根本调用函数。我认为有一些方法可以减少你违反封装的程度。

如果我正在编写副作用测试,我通常会将它们分成自己的测试。我还将根据我的要求(即,顺序,时间,被叫或不是,参数等)来存根/模拟副作用函数并断言它们被调用。这使我无法了解实现细节,但仍允许我声明正确调用了特定函数。

在某些语言中,模拟对象或方法可能很困难。在这些情况下,我将使用依赖注入来传递具有副作用的对象或函数。这样在测试时我可以通过我的模拟验证。

答案 2 :(得分:1)