我想知道如何单元测试抽象类,以及扩展抽象类的类。

我是否应该通过扩展抽象类来测试它,去掉抽象方法,然后测试所有的具体方法?然后只测试我重写的方法,并在单元测试中测试扩展抽象类的对象的抽象方法?

我是否应该有一个抽象测试用例,可以用来测试抽象类的方法,并在测试用例中为扩展抽象类的对象扩展该类?

注意,我的抽象类有一些具体的方法。


当前回答

为了专门对抽象类进行单元测试,您应该出于测试目的派生它,test base.method()的结果和继承时预期的行为。

你通过调用一个方法来测试它,所以通过实现一个抽象类来测试它……

其他回答

我反对“抽象”测试。我认为测试是一个具体的概念,没有抽象的概念。如果您有公共元素,请将它们放在辅助方法或类中供所有人使用。

至于测试抽象测试类,一定要问自己要测试的是什么。有几种方法,您应该找出适合您的场景的方法。您是否试图在子类中测试一个新方法?然后让您的测试只与该方法交互。测试基类中的方法吗?然后可能只针对该类有一个单独的fixture,并使用尽可能多的测试单独测试每个方法。

抽象基类有两种使用方式。

您正在专门化抽象对象,但所有客户端都将通过其基接口使用派生类。 您正在使用抽象基类来排除设计中对象中的重复,而客户端通过自己的接口使用具体实现。


1策略模式的解决方案

如果您有第一种情况,那么您实际上有一个由派生类正在实现的抽象类中的虚方法定义的接口。

您应该考虑使其成为一个真实的接口,将抽象类更改为具体类,并在其构造函数中获取该接口的实例。然后派生类就成为这个新接口的实现。

这意味着您现在可以使用新接口的模拟实例测试以前的抽象类,并通过现在的公共接口测试每个新实现。一切都是简单且可测试的。


解决方案2

如果您有第二种情况,那么您的抽象类将作为辅助类工作。

看看它包含的功能。看看是否可以将其中的任何一部分推到被操纵的对象上,以最小化复制。如果您还有剩余的东西,考虑将其作为一个helper类,您的具体实现使用它的构造函数并删除它们的基类。

这再次导致了简单且易于测试的具体类。


一般来说

喜欢简单对象的复杂网络,而不是复杂对象的简单网络。

可扩展可测试代码的关键是小的构建块和独立的连接。


更新:如何处理两者的混合?

有可能有一个基类同时扮演这两个角色……即:它有一个公共接口,并有受保护的helper方法。如果是这种情况,那么您可以将辅助方法分解为一个类(场景2),并将继承树转换为策略模式。

如果您发现一些方法是基类直接实现的,而另一些方法是虚的,那么您仍然可以将继承树转换为策略模式,但我也认为这是一个很好的指标,说明职责没有正确对齐,可能需要重构。


更新2:抽象类作为垫脚石(2014/06/12)

前几天我遇到了一个使用抽象的情况,所以我想探讨一下为什么。

我们的配置文件有一个标准格式。这个特殊的工具有3个配置文件都是这种格式。我希望每个设置文件都有一个强类型类,这样通过依赖注入,类就可以请求它所关心的设置。

我通过一个抽象基类来实现这一点,它知道如何解析设置文件格式和派生类,这些派生类公开了这些相同的方法,但封装了设置文件的位置。

我本可以编写一个由3个类包装的“SettingsFileParser”,然后委托给基类以公开数据访问方法。我选择不这样做,因为这将导致3个派生类,其中的委托代码比其他任何类都多。

However... as this code evolves and the consumers of each of these settings classes become clearer. Each settings users will ask for some settings and transform them in some way (as settings are text they may wrap them in objects of convert them to numbers etc.). As this happens I will start to extract this logic into data manipulation methods and push them back onto the strongly typed settings classes. This will lead to a higher level interface for each set of settings, that is eventually no longer aware it's dealing with 'settings'.

此时,强类型设置类将不再需要公开底层“设置”实现的“getter”方法。

在这一点上,我将不再希望他们的公共接口包括设置访问器方法;所以我将改变这个类封装一个设置解析器类,而不是从它派生。

因此,对于我来说,抽象类是一种暂时避免委托代码的方法,也是代码中的一个标记,用来提醒我稍后更改设计。我可能永远也见不到它了,所以它可能会活很久……只有密码能告诉我们。

我发现这适用于任何规则……比如“没有静态方法”或者“没有私有方法”。它们表明代码中有一种气味……这很好。它让你不断寻找你错过的抽象概念……同时让你继续为你的客户提供价值。

我想象像这样的规则定义了一个景观,可维护的代码生活在山谷中。当你添加新的行为时,它就像雨水降落在你的代码上。一开始你把它放在它落地的地方。然后你重构,让优秀设计的力量推动行为,直到一切都在山谷中结束。

如果具体方法调用任何抽象方法,该策略将不起作用,您将希望分别测试每个子类的行为。否则,只要抽象类的具体方法与子类解耦,就可以像您所描述的那样扩展它并存根抽象方法。

我为抽象类和接口所做的工作如下:我编写一个测试,将对象作为具体对象使用。但是在测试中没有设置类型为X的变量(X是抽象类)。这个测试类并没有添加到测试套件中,而是它的子类,它们有一个setup-方法,将变量设置为x的具体实现。这样我就不会重复测试代码。如果需要,未使用测试的子类可以添加更多的测试方法。

one way is to write an abstract test case that corresponds to your abstract class, then write concrete test cases that subclass your abstract test case. do this for each concrete subclass of your original abstract class (i.e. your test case hierarchy mirrors your class hierarchy). see Test an interface in the junit recipies book: http://safari.informit.com/9781932394238/ch02lev1sec6. https://www.manning.com/books/junit-recipes or https://www.amazon.com/JUnit-Recipes-Practical-Methods-Programmer/dp/1932394230 if you don't have a safari account.

也可以在xUnit模式中看到Testcase超类:http://xunitpatterns.com/Testcase%20Superclass.html