我想知道如何对抽象类和扩展抽象类的类进行单元测试。

我应该通过扩展抽象类、删除抽象方法来测试抽象类,然后测试所有具体方法吗?然后只测试我重写的方法,并在单元测试中测试扩展我的抽象类的对象的抽象方法?

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

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

有帮助吗?

解决方案

编写Mock对象并将其用于测试。它们通常非常非常小(继承自抽象类)而不是更多。然后,在单元测试中,您可以调用要测试的抽象方法。

您应该测试包含某些逻辑的抽象类,就像您拥有的所有其他类一样。

其他提示

有两种使用抽象基类的方法。

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

  2. 您正在使用抽象基类来消除设计中对象内的重复,并且客户端通过他们自己的接口使用具体实现。


解决方案 1 - 策略模式

Option1

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

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

IMotor

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


2 的解决方案

如果您遇到第二种情况,那么您的抽象类正在充当辅助类。

AbstractHelper

看一下它包含的功能。看看是否可以将其中任何一个推送到正在操作的对象上,以最大限度地减少这种重复。如果您还有任何剩余内容,请考虑将其设为一个辅助类,您的具体实现将在其构造函数中采用该辅助类并删除其基类。

Motor Helper

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


作为一条规则

优先选择简单对象的复杂网络,而不是复杂对象的简单网络。

可扩展、可测试代码的关键是小型构建块和独立布线。


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

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

如果您发现您的基类直接实现了一些方法,而其他方法是虚拟的,那么您仍然可以将继承树转换为策略模式,但我也将其视为职责未正确对齐的一个很好的指标,并且可能会需要重构。


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

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

我们的配置文件有标准格式。这个特定的工具有 3 个配置文件,全部采用该格式。我希望每个设置文件都有一个强类型的类,因此,通过依赖项注入,类可以请求它关心的设置。

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

我可以编写一个由 3 个类包装的“SettingsFileParser”,然后委托给基类以公开数据访问方法。我选择不这样做 然而 因为它会产生 3 个具有更多内容的派生类 代表团 其中的代码比其他任何东西都重要。

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

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

那时我不再希望他们的公共接口包含设置访问器方法;所以我将更改此类以封装设置解析器类,而不是从中派生。

因此,抽象类是:这是我目前避免委托代码的一种方法,并且代码中的一个标记可以提醒我稍后更改设计。我可能永远也接触不到它,所以它可能会活很长一段时间......只有代码可以告诉我们。

我发现任何规则都是如此......比如“没有静态方法”或“没有私有方法”。它们表明代码中存在某种气味......这很好。它让你不断寻找你错过的抽象......同时让您继续为客户提供价值。

我想象这样的规则定义了一种景观,可维护的代码存在于山谷中。当您添加新行为时,就像雨落在您的代码上一样。最初你把它放在它落地的任何地方..然后你进行重构,让良好设计的力量推动行为,直到一切最终陷入低谷。

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

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

您通过调用方法测试方法,因此通过实现它来测试抽象类...

如果您的抽象类包含具有业务价值的具体功能,那么我通常会通过创建一个存根抽象数据的测试双重来直接测试它,或者通过使用模拟框架为我做这个。我选择哪一个取决于我是否需要编写抽象方法的特定于测试的实现。

我需要执行此操作的最常见情况是,当我使用模板方法模式,例如当我构建某种可由第三方使用的可扩展框架时。在这种情况下,抽象类定义了我想要测试的算法,因此测试抽象基础比使用特定实现更有意义。

但是,我认为这些测试应该只关注实际业务逻辑的具体实现;你不应该单元测试抽象类的实现细节因为你最终会进行脆弱的测试。

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

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

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

至于测试抽象测试类,请确保自问您正在测试的是什么。有几种方法,你应该找出适用于你的场景的方法。您是否尝试在子类中测试新方法?然后让您的测试仅与该方法交互。您是否正在测试基类中的方法?然后可能只为该类设置一个单独的夹具,并根据需要使用尽可能多的测试单独测试每个方法。

这是我在设置测试抽象类的线束时经常遵循的模式:

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

我在测试中使用的版本:

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);
  }
  /*,,,*/
}

如果在我不期望的情况下调用抽象方法,则测试失败。在安排测试时,我可以使用执行断言,抛出异常,返回不同值等的lambda简单地删除抽象方法。

如果具体方法调用策略不起作用的任何抽象方法,并且您希望分别测试每个子类行为。否则,扩展它并如你所描述的那样对抽象方法进行存根应该没问题,只要抽象类具体方法与子类分离即可。

我想你可能想要测试一个抽象类的基本功能......但是你可能最好通过扩展类而不重写任何方法,并对抽象方法进行最小化的模拟。

使用抽象类的主要动机之一是在应用程序中启用多态 - 即:您可以在运行时替换不同的版本。实际上,这与使用接口非常相似,只是抽象类提供了一些常见的管道,通常称为模板模式

从单元测试的角度来看,有两件事需要考虑:

  1. 您的抽象类与其相关类的交互。使用模拟测试框架是这种情况的理想选择,因为它表明您的抽象类可以很好地与其他人一起使用。

  2. 派生类的功能。如果您具有为派生类编写的自定义逻辑,则应该单独测试这些类。

  3. 编辑:RhinoMocks是一个非常棒的模拟测试框架,可以通过从您的类动态派生来在运行时生成模拟对象。这种方法可以为您节省无数小时的手工编码派生类。

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

 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 ,如下所示:

抽象类 - 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 类来测试抽象方法和非抽象方法:

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"));
    }
}

如果抽象类适合您的实现,请测试(如上所述)派生的具体类。你的假设是正确的。

为了避免将来的混乱,请注意这个具体的测试类不是模拟,而是一个 伪造的.

严格来说,一个 嘲笑 由以下特征定义:

  • 使用模拟来代替正在测试的主题类的每个依赖项。
  • 模拟是接口的伪实现(您可能还记得,作为一般规则,依赖项应声明为接口;可测试性是其主要原因之一)
  • 模拟接口成员的行为(无论方法还是属性)是在测试时提供的(同样是通过使用模拟框架)。这样,您就可以避免正在测试的实现与其依赖项的实现(都应该有自己的离散测试)耦合。
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top