如何unit testing抽象类:扩展与存根(stub)?

我想知道如何unit testing抽象类和扩展抽象类的类。

我是否应该通过扩展来testing抽象类,抽出抽象方法,然后testing所有的具体方法? 那么只testing我重写的方法,并testing抽象类的unit testing抽象方法的对象,扩展我的抽象类?

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

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

编写一个模拟对象,并将其用于testing。 它们通常非常非常小(从抽象类inheritance),而不是更多。然后,在你的unit testing中,你可以调用你想testing的抽象方法。

您应该testing包含所有其他类的逻辑的抽象类。

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

  1. 您正在专注于您的抽象对象,但所有客户端都将通过其基本接口使用派生类。

  2. 您正在使用抽象基类去分析devise中对象的重复,客户端通过自己的接口使用具体的实现。


1 – 战略模式的解决scheme

选项1

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

你应该考虑把这个接口做成一个真正的接口,把你的抽象类变成具体的,并且在它的构造函数中接受这个接口的一个实例。 您的派生类然后成为这个新的接口的实现。

IMOTOR

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


解决scheme2

如果你有第二种情况,那么你的抽象类就是一个辅助类。

AbstractHelper

看看它包含的function。 看看是否有任何可以推到正在操作的对象,以尽量减less这种重复。 如果还剩下什么东西的话,可以看看把它变成一个助手类,你的具体实现需要在构造函数中去掉它们的基类。

汽车帮手

这又导致具体的类是简单的,易于testing。


作为一个规则

通过简单的复杂对象networking来支持简单对象的复杂networking。

可扩展可testing代码的关键是小型构build块和独立的接线。


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

有可能有一个基类执行这两个angular色…即:它有一个公共接口,并有保护辅助方法。 如果是这种情况,则可以将辅助方法分解成一个类(scheme2),并将inheritance树转换为策略模式。

如果你发现你有一些方法你的基类直接实现,其他是虚拟的,那么你仍然可以将inheritance树转换成一个策略模式,但我也认为这是一个很好的指示,责任是不正确alignment,并可能需要重构。


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

我有一个情况,那天我用抽象的,所以我想探讨为什么。

我们有我们的configuration文件的标准格式。 这个特定的工具有3个configuration文件,全部以这种格式。 我想为每个设置文件强types的类,所以通过dependency injection,一个类可以要求它关心的设置。

我通过拥有一个抽象基类实现了这一点,该基类知道如何parsing暴露这些相同方法的设置文件格式和派生类,但封装了设置文件的位置。

我可以写一个“SettingsFileParser”这3个类包装,然后委托给基类来公开数据访问方法。 我select不这样做,因为这会导致派生代码更多的3个派生类。

但是…随着代码的发展,每个设置类的消费者都变得更加清晰。 每个设置用户都会要求一些设置,并以某种方式转换它们(因为设置是文本,可以将它们包装在转换成数字的对象中)。 当发生这种情况时,我将开始将这个逻辑提取到数据操作方法中,并将它们推回到强types设置类中。 这将导致每一组设置的更高级别的界面,最终不再意识到它正在处理“设置”。

在这一点上,强types设置类将不再需要暴露底层“设置”实现的“getter”方法。

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

因此,抽象类是:我目前避免委派代码的一种方式,以及代码中的一个标记,以提醒我稍后更改devise。 我可能永远都不会这样做,所以它可能会活得很好……只有代码才能说出来。

我发现这是真实的任何规则…像“没有静态方法”或“没有私人方法”。 他们表明了代码中的气味…这很好。 它使您不断寻找您错过的抽象概念,并让您在同时为客户提供价值。

我想像这样的规则定义一个景观,在那里可维护的代码居住在山谷里。 当你添加新的行为时,就像在你的代码上着陆一样。 最初,你把它放在任何地方..然后你重构,让优秀的devise力量推动行为,直到所有的结果在山谷。

我为抽象类和接口所做的工作如下:我写一个testing,它使用对象,因为它是具体的。 但是X的variables(X是抽象类)在testing中没有设置。 这个testing类不会被添加到testing套件中,而是它的子类,它具有一个设置方法,将variables设置为具体的X实现。这样我就不会复制testing代码。 未使用的testing的子类可以添加更多的testing方法,如果需要的话。

为了在抽象类上专门进行unit testing,您应该派生出来用于testing目的,testingbase.method()结果和inheritance时的预期行为。

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

如果你的抽象类包含具有商业价值的具体function,那么我通常会直接通过创build一个抽象数据的testingdouble来testing它,或者使用一个模拟框架为我做这件事。 我select哪一个取决于我是否需要编写抽象方法的特定于testing的实现。

我需要这样做的最常见的情况是当我使用模板方法模式 ,比如当我构build某种可扩展的框架,将被第三方使用。 在这种情况下,抽象类定义了我想要testing的algorithm,因此testing抽象基准比具体实现更有意义。

但是,我认为这些testing应该只关注实际业务逻辑具体实现 ,这一点很重要。 你不应该对抽象类的实现细节进行unit testing,因为你会以脆弱的testing结束。

一种方法是编写与抽象类相对应的抽象testing用例,然后写出抽象testing用例的子类的具体testing用例。 为原始抽象类的每个具体子类(即您的testing用例层次结构镜像您的类层次结构)执行此操作。 请参阅testingjunit收件簿中的界面: http ://safari.informit.com/9781932394238/ch02lev1sec6。

另请参阅xUnit模式中的Testcase Superclass: http : //xunitpatterns.com/Testcase%20Superclass.html

我会反对“抽象”testing。 我认为一个testing是一个具体的想法,并没有抽象。 如果你有共同的元素,把它们放在辅助方法或类中供大家使用。

至于testing一个抽象的testing类,确保你问自己你在testing什么。 有几种方法,你应该找出你的scheme中有什么作用。 你想在你的子类中testing一个新的方法吗? 然后让你的testing只与该方法交互。 你在testing基类中的方法吗? 然后可能有一个单独的夹具只为该类,并testing每个方法单独与尽可能多的testing。

这是我在设置testing抽象类的线束时通常遵循的模式:

public abstract class MyBase{ /*...*/ public abstract void VoidMethod(object param1); public abstract object MethodWithReturn(object param1); /*,,,*/ } 

而我在testing中使用的版本:

 public class MyBaseHarness : MyBase{ /*...*/ public Action<object> VoidMethodFunction; public override void VoidMethod(object param1){ VoidMethodFunction(param1); } public Func<object, object> MethodWithReturnFunction; public override object MethodWithReturn(object param1){ return MethodWihtReturnFunction(param1); } /*,,,*/ } 

如果抽象方法在我不期望的时候被调用,testing失败。 在安排testing的时候,我可以很容易地用抽象方法来执行断言,抛出exception,返回不同的值等。

如果具体方法调用任何抽象方法,那么这个策略将不起作用,并且您想要分别testing每个子类的行为。 否则,扩展它并像上面描述的那样存根抽象方法应该没问题,再次提供抽象类具体方法与子类分离。

我想你可能想要testing一个抽象类的基本function……但是,如果不重写任何方法,扩展这个类可能是最好的,并且为抽象方法做最小的努力嘲笑。

使用抽象类的主要动机之一是在应用程序中启用多态性 – 即:可以在运行时replace不同的版本。 事实上,除了抽象类提供了一些常用的pipe道,通常被称为模板模式 ,这与使用接口非常相似。

从unit testing的angular度来看,有两点需要考虑:

  1. 你的抽象类与它相关的类的相互作用 。 使用模拟testing框架对于这个场景是理想的,因为它表明了你的抽象类与其他类相似。

  2. 派生类的function 。 如果您有为自己的派生类编写的自定义逻辑,则应该单独testing这些类。

编辑:RhinoMocks是一个很棒的模拟testing框架,可以在运行时通过dynamic派生类来生成模拟对象。 这种方法可以为您节省无数小时的手工编码派生类。

首先,如果抽象类包含一些具体的方法,我认为你应该考虑这个例子

  public abstract class A { public boolean method 1 { // concrete method which we have to test. } } class B extends class A { @override public boolean method 1 { // override same method as above. } } class Test_A { private static B b; // reference object of the class B @Before public void init() { b = new B (); } @Test public void Test_method 1 { b.method 1; // use some assertion statements. } } 

在@ patrick-desjardins答案之后,我实现了抽象,它的实现类和@Test一起如下:

Abstarct类 – ABC.java

 import java.util.ArrayList; import java.util.List; public abstract class ABC { abstract String sayHello(); public List<String> getList() { final List<String> defaultList = new ArrayList<>(); defaultList.add("abstract class"); return defaultList; } } 

由于抽象类不能被实例化,但它们可以被子类化 ,具体类DEF.java如下:

 public class DEF extends ABC { @Override public String sayHello() { return "Hello!"; } } 

@Test类来testing抽象以及非抽象的方法:

 import org.junit.Before; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.contains; import java.util.Collection; import java.util.List; import static org.hamcrest.Matchers.equalTo; import org.junit.Test; public class DEFTest { private DEF def; @Before public void setup() { def = new DEF(); } @Test public void add(){ String result = def.sayHello(); assertThat(result, is(equalTo("Hello!"))); } @Test public void getList(){ List<String> result = def.getList(); assertThat((Collection<String>) result, is(not(empty()))); assertThat(result, contains("abstract class")); } }