我应该对子类还是抽象父类进行单元测试?


12

我有一个简单的实现,如Effective Java的第18项(此处扩展了讨论)。它是一个抽象类,提供2个公共方法methodA()和methodB(),这些方法调用子类方法来“填补我无法以抽象方式定义的空白”。

我首先通过创建一个具体的类并为其编写单元测试来开发它。当第二个类到来时,我能够提取常见的行为,在第二个类中实现“缺失的差距”,并且已经准备就绪(当然,单元测试是为第二个子类创建的)。

时光流逝,现在我有4个子类,每个子类实现3个受保护的短方法,这些方法非常具体地针对其具体实现,而骨架实现则完成所有常规工作。

我的问题是,当我创建一个新的实现时,我会重新编写测试:

  • 子类是否调用给定的必需方法?
  • 给定的方法是否具有给定的注释?
  • 从方法A中可以获得预期的结果吗?
  • 从方法B中可以获得预期的结果吗?

虽然看到了这种方法的优点:

  • 它通过测试记录了子类需求
  • 如果重构有问题,它可能会很快失败
  • 对子类进行整体测试,并通过其公共API进行测试(“ methodA()是否起作用?”而不是“此受保护的方法起作用吗?”)

我的问题是对新子类的测试基本上是理所当然的:-这些测试在所有子类中都相同,它们只是更改方法的返回类型(最基本的实现是通用的)和属性I检查测试的断言部分。-通常,子类没有针对其的测试

我喜欢测试集中于子类本身的结果并防止其重构的方式,但是实施测试只是“手动工作”,这一事实使我觉得我做错了。

测试类层次结构时,此问题常见吗?我该如何避免呢?

ps:我考虑过测试骨架类,但是创建一个抽象类的模拟对象以进行测试也很奇怪。而且我认为,对抽象类进行的任何重构会改变子类所不期望的行为,因此不会很快被注意到。我的胆量告诉我,“整体”测试子类是这里的首选方法,但是请随时告诉我我错了:)

ps2:我已经在Google上搜索了,发现了许多与此主题有关的问题。其中之一是这样的一个具有很大的答案从奈杰尔·索恩,我的情况下,将他的“1号。” 那太好了,但是我现在不能重构,所以我不得不忍受这个问题。如果我可以重构,我会将每个子类都作为一种策略。可以,但是我仍然会测试“主类”并测试策略,但是我不会注意到任何重构会破坏主类和策略之间的集成。

ps3:我找到了一些答案,说测试抽象类“可以接受”。我同意这是可以接受的,但是我想知道在这种情况下哪种方法更可取(我仍然从单元测试开始)


2
编写一个抽象测试类,并让您的具体测试继承自该类,以反映业务代码的结构。测试应该测试行为而不是测试单元的实现,但这并不意味着您不能利用对系统的内部了解来更有效地实现该目标。
Kilian Foth,2015年

我读的地方,不应该用在测试中继承,我找源头仔细阅读
巴赫

4
@KilianFoth您确定此建议吗?这听起来像是脆性单元测试的秘诀,对设计变更高度敏感,因此不太容易维护。
Konrad Morawski

Answers:


5

我看不到如何为抽象基类编写测试。您无法实例化它,因此无法对其进行测试。您可以仅出于测试目的而创建“虚拟”具体子类-不知道将使用多少。YMMV。

每次创建新的实现(类)时,都应该编写用于测试该新实现的测试。由于部分功能(您的“空白”)是在每个子类中实现的,因此这是绝对必要的。每个新实现的每个继承方法的输出可能(可能应该)不同。


是的,它们是不同的(类型和内容是不同的)。从技术上讲,我将能够模拟此类或仅出于测试目的使用简单的实现并使用空实现。我不喜欢这种方法。但是,我不需要“认为”在新的实现中通过测试的事实让我感到很奇怪,也使我对自己对这个测试的信心有疑问(当然,如果我故意改变基类,测试失败,这是我可以信任的证明)
JSBach 2015年

4

通常,您将创建一个基类来实施接口。您要测试该界面,而不是下面的详细信息。因此,您应该创建测试,以分别实例化每个类,但只能通过基类的方法进行测试。

这种方法的优点在于,因为您已经测试了接口,所以现在可以在接口下面进行重构。您可能会发现子类中的代码应位于父类中,反之亦然。现在,您的测试将证明您在移动代码时没有破坏行为。

通常,您应该测试代码的预期用途。这意味着避免测试私有方法,并且通常意味着被测单元可能比您最初想象的要大一些-也许是一组类而不是单个类。您的目标应该是隔离更改,以便在重构给定范围时很少需要更改测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.