我该对私有/受保护方法进行单元测试吗?

28

这其实是与语言无关的。但我会以Python为例,给你提供一些背景。

我有一个父类:

class Mammal(object):
    def __init__(self):
        """ do some work """

    def eat(self, food):
        """Eat the food"""
        way_to_eat = self._eating_method()
        self._consume(food)

    def _eating_method(self):
        """Template method"""

    def _consume(self, food):
        """Template method"""

这里eat是唯一的公共方法,而_consume_eating_method实际上是受保护的方法,将由子类实现。

当你只编写了Mammal类时,你会测试什么?

显然测试所有4个方法。

现在让我们介绍一个子类。

class Tiger(Mammal):    
    def _eating_method(self):
        """Template method"""

    def _consume(self, food):
        """Template method"""

看这个类,它只有2个受保护的方法。

我应该测试Tiger的所有4个方法(包括继承的2个方法)还是只测试引入变更的部分(仅覆盖2个重写的方法)?

理想情况是什么?


那要看情况。通常,eat方法会测试每种可能的_eating_method_consume结果。因此只需要测试更改过的方法即可。 - Daniel
3个回答

48

从理论角度来看,您只需要测试可实例化类的公共方法(在标准OOP语言中)。测试内部行为是没有意义的,因为您想知道的只是“给定输入产生什么输出”(对于特定的方法或整个类)。尽可能尊重这一点,因为它迫使您对类的封装性和提供的接口进行一些问题的提问,这可能对您的架构具有决定性作用。

从实用角度来看,有时您可以拥有一些抽象的辅助类,没有实现具体子类或者一个抽象类占据了90%以上的其子类,如果不插入受保护的方法,则很难测试输出。在这种情况下,您可以模拟一个子类。

在您简单的示例中,我建议您只测试Tiger类(以及公共方法eat)。

对于那些考虑TDD的人,请注意。在TDD中,在编写类Tiger之前,您不应该已经开始编写类Mammal,因为Mammal应该是重构阶段的结果。因此,您肯定不会对Mammal进行任何特定的测试。


2
是的,你可以测试这个方法(唯一公开的方法)。 - Gnucki

5
我处理此事的方法是:
  • 创建一个最小化可测试的Mammal子类,它提供了两个受保护方法的最小实现,这些方法允许您对公共方法的行为进行单元测试。
  • 为每个子类编写单独的单元测试,再次测试Mammal上的公共方法,但是要断言特定于该子类的行为。

这样可以用最少的测试获得必要的测试覆盖率。

另一种替代方法是仅测试子类,并在其中一个子类单元测试中还断言特定于Mammal的功能。这避免了创建特定测试子类的需要,但是有两个缺点:

  • 您不再孤立地测试Mammal,因此Mammal特定代码的测试容易因子类的问题而失败。
  • 其他人可能不太明显地知道在哪里以及如何测试Mammal的属性。

1
当进行测试时,应该关注您的代码的外部行为,而不是实现细节。我不知道上下文,所以我只会做出一些巨大的假设。
你真的关心(可能是抽象的)哺乳动物超类的行为吗?重用通过继承的关系很重要吗?如果您决定将继承关系替换为基于组合的策略,会怎样?
我会专注于测试您真正关心的类的行为:“老虎是否按预期进食?”而不是测试仅用于代码重用的某个抽象超类。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接