我有以前有大量方法的类,所以我将此方法的工作细分为“帮助”方法。
这些辅助方法被声明private
为强制封装 - 但是我想对大型公共方法进行单元测试。对辅助方法进行单元测试是否也好,就好像其中一个方法失败了,调用它的公共方法也会失败,这样我们就可以确定它失败的原因?
此外,为了使用模拟对象测试这些,我需要将它们的可见性从私有更改为受保护,这是可取的吗?
我有以前有大量方法的类,所以我将此方法的工作细分为“帮助”方法。
这些辅助方法被声明private
为强制封装 - 但是我想对大型公共方法进行单元测试。对辅助方法进行单元测试是否也好,就好像其中一个方法失败了,调用它的公共方法也会失败,这样我们就可以确定它失败的原因?
此外,为了使用模拟对象测试这些,我需要将它们的可见性从私有更改为受保护,这是可取的吗?
一种方法是省略private
并将测试放在同一个包中。然后测试可以调用内部方法,但没有其他人(=包外)可以。
此外,失败的内部方法应该会产生错误消息,从而可以轻松解决问题。当您将代码投入生产时,您会看到比测试更少的内容,并且您将承受很大的压力来快速解决问题。因此,在这里花一分钟将为您节省一小时后您的老板坐在您的脖子上。
这闻起来像你有错误的问题。你所描述的类似于创建一个子“单元测试”,这让我相信你的单元测试毕竟是在测试一个单元。
这不是对您正在尝试做的事情的批评:从“我们今天所处的位置”到“其他明显更好的地方”是一个成功的举措。但是,建议您退后一步来评估您的位置——了解您当前的情况与某些柏拉图式的理想有何不同可能有助于展示新的可能性。
这里有很多关于确定辅助方法范围的建议。另一种可能性是审查实现以确定是否存在潜伏在当前实现中的帮助程序类。创建一个新类和一套测试来练习它总是可以接受的。
请注意,这种方法使您免受重构:您可以在不更改测试套件的情况下更改实现(因为即使帮助对象不再是生产实现的一部分,帮助对象的单元测试也会继续通过),并且您会得到对实现及其测试的干净打包(用例:您认为 bozo-sort 是错误的实现,不应再使用。如果 bozo-sort 实现是孤立的,那么您只需删除它及其测试。但是当 bozo-sort 实现的测试与所有其他测试纠缠在一起时,就会涉及更多的思考)。
回顾一下为什么要对代码进行单元测试也可能会有所帮助。如果原因之一是“使重构安全”,那么您不想编写将您锁定在实现中的测试。
如果你的类真的那么大,那么听起来你应该打破辅助对象,而不仅仅是辅助方法(尽管提取方法通常是一个步骤)。一旦你这样做了,你的旧类变得更简单,更容易测试(也许使用模拟,也许不是),你可以直接在新的支持类上测试方法。
我的偏好是通过对象的公共 API 进行测试。如果这太难了,则暗示该对象应该被分解。
如果您想测试Helper方法,您可以将它们从私有更改,但您可能会考虑这一点。
您不应该对实现的私有细节进行单元测试,主要是因为它可能会由于重构和“破坏”您的测试而发生变化。
我对这里的一些答案感到非常震惊。
本质上有些人在说“不要测试私有代码,因为这违反了 TDD 范式”
测试该死的代码。做任何你需要做的事情,以确保它完全按照它应该的方式工作。
就个人而言,我会将方法设置为受保护或默认,编写测试,运行测试,并在成功后恢复为私有。此时,我会注释掉相关的测试,并在它们上面留下一个指令块:
/** 很抱歉,但我继承了一个烂摊子... * 如果您需要测试这些方法,请在源代码中公开它们并取消注释以下 * 行 */
但绝对不要让严格遵守开发方法妨碍改进代码。
这是我想说继续打破规则的情况之一。
如果您是从头开始设计该类,您肯定不希望对辅助方法进行自己的单元测试,但是……由于您正在重构现有的类,因此可以修改规则以确保您不这样做不要破坏任何东西。
使它们受到保护可以让您自己测试助手,以确保当您从大毛球方法中提取逻辑时它们仍然具有您期望的行为,并允许您将它们存根并返回固定响应,以便您可以确保您正在重构的大方法对于辅助方法的某些结果的行为符合预期。
但此时你还没有完成。拆分方法并不能真正解决问题的根源。现在您已经分解了方法和一组(有点不正统的)测试,可以准确地向您展示所有逻辑的作用,您可以重新检查整个类并尝试找出该方法的原因一开始就这么大。很可能你的整个班级也需要被分解成具有离散职责的更小的单元,这样在不弯曲任何规则的情况下更容易测试。
你基本上有2个选择:
将辅助方法的范围从私有增加到默认。然后您可以测试这些方法(假设测试类与测试对象在同一个包中)。这提高了类的可测试性,但你牺牲了一些封装
让一切保持原样。这将阻止您编写非常细粒度的测试,但不需要您牺牲任何封装。
就个人而言,我会选择(2),因为你不应该真的需要测试私有方法。该类应该通过它的公共接口进行测试(这反过来又会调用私有方法。测试私有方法可能会导致脆弱的测试,即当只有类的内部行为发生变化时测试会失败。
还有第三种选择(我不愿提及):使用反射(或其他一些巫术)在测试类中调用私有方法。这具有(1)的缺点以及反射代码固有的缺点(例如绕过类型检查并且难以阅读)
正如 Don 和 Dror 所说,将方法公开以便您可以为它们创建单元测试会破坏封装。然后,您将自己绑定到特定的实现。通过将它们公开,您向世界声明这些方法是已发布接口的一部分,因此它们的规范是锁定的。
就个人而言,我会寻求更实用的解决方案:将它们保密,不要编写单元测试。如果您遇到公共方法失败的情况,并且您无法弄清楚原因,但您认为这可能是您的私有方法之一的问题,那么暂时将它们公开,编写单元测试,调试,当您'完成,再次将它们设为私有并注释掉单元测试。
您可以通过编写单元测试来对这些辅助方法进行单元测试,这些单元测试会执行您的代码的这些部分。
你在这个类中定义了一个公共 API 是有原因的,对吧?测试一下。如果它有效,则该类有效。
使用代码覆盖工具来帮助您了解该类的足够部分是否正在测试,如果没有,请编写更多单元测试来练习公共 API 并触及那些没有被覆盖的角落。