【问题标题】:Test "proxies"; good TDD or a code smell?测试“代理”;好的 TDD 还是代码味道?
【发布时间】:2011-01-05 18:33:16
【问题描述】:

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

对我来说,将所有内容都公开(有时是虚拟的)只是为了能够对所有内容进行单元测试是一种味道;我不希望其他对象能够调用助手或直接访问文本框;但我需要知道帮助程序完成了它的工作,并且在表单加载时文本框得到了它的值。

我前段时间得出的解决方案是为实际被测对象创建一个“测试代理”。代理派生自被测对象,不会隐藏或覆盖任何行为,但它确实提供内部可见的 getter、setter 和/或方法来调用被测对象的非公共成员,从而使我能够告诉对象执行某些操作,然后我可以查看结果,而无需测试也依赖于对象内的适当集成,或者仅出于测试目的在生产代码中公开方法或其他一些感兴趣的成员。

我看到的优点:

  • 成员的可见性并不取决于您是否要进行单元测试。
  • 更好地控制您可以在测试中对对象执行哪些操作,从而实现更灵活和可扩展的测试。

我看到的缺点:

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

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

【问题讨论】:

  • “私人助手”是一个方法,还是一个类?如果它是一个类,那么它的方法应该是公开的,因此可以从外部测试——这很好。如果它是一个方法,考虑把它变成一个类,从而减少到前面的情况。
  • 好建议,但并不总是有效。作为一个更具体的例子,我为表单选择的一个常见模式是实现一个 Bind() 方法,该方法的工作是获取一些数据对象并填充包含在表单或嵌套控件上的控件。这种方法需要操作其他非公共成员(嵌套控件),它只对为其编写的一个表单具有价值,并且它是正确初始化表单的一系列步骤,所以我不希望它被调用直接通过生产代码。
  • 我错过了什么吗?为了代理您的课程,您是否不需要保护其私有成员?所以你的测试策略迫使你让你的成员比你想要的更容易访问?
  • @David - 是的,这是另一个缺点,但在将消费者指向他们应该使用和不应该使用的方法方面,protected 比 public 更好。另一种方法是将我想要测试的所有内容都公开,这意味着几乎所有内容都公开,当消费者在他们认为应该插入的任何地方插入时,这会造成维护噩梦。

标签: unit-testing tdd


【解决方案1】:

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

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

【讨论】:

  • +!因为“您的公共界面设计不佳。”
  • 我最喜欢这个答案,因为它“可能很糟糕,但有时可以接受”,这似乎是我的情况;这在我的测试中绝对是罕见的,但有时没有其他好的选择。
【解决方案2】:

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

话虽如此,我认为违反封装是有充分理由的,它们围绕着具有副作用的函数(UI 编程经常出现这种情况)。在许多情况下,您需要确保以特定顺序调用函数或完全调用它们。我认为有一些方法可以减轻您违反封装的程度。

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

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

【讨论】:

    【解决方案3】:

    MSTest 使用此方法来测试私有方法。 Visual Studio 将所有测试放在一个单独的测试项目中并创建一个“访问器”类。该类是所有私有成员都公开的子类。由于这个子类在测试项目中,它在被测程序集中不可用。我认为这是测试私有方法的可行模式,如果您不使用 Visual Studio 和 MSTest,可以在 TDD 环境中手动实现。

    【讨论】:

    • +1 参考 MS Accessor 作为模式。但是请注意,OP 和您在谈论单元测试,而不是 TDD
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-31
    • 1970-01-01
    • 2011-07-10
    • 2010-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多