【问题标题】:How to test abstract class in Java with JUnit?如何使用 JUnit 测试 Java 中的抽象类?
【发布时间】:2011-11-26 00:49:32
【问题描述】:

我是使用 JUnit 进行 Java 测试的新手。我必须使用 Java,我想使用单元测试。

我的问题是:我有一个带有一些抽象方法的抽象类。但是有些方法不是抽象的。如何用 JUnit 测试这个类?示例代码(很简单):

abstract class Car {

    public Car(int speed, int fuel) {
        this.speed = speed;
        this.fuel = fuel;
    }

    private int speed;
    private int fuel;

    abstract void drive();

    public int getSpeed() {
        return this.speed;
    }

    public int getFuel() {
        return this.fuel;
    }
}

我想测试getSpeed()getFuel() 函数。

与此问题类似的问题是here,但它没有使用 JUnit。

在JUnit FAQ部分,我找到了this link,但是我不明白作者想用这个例子说什么。这行代码是什么意思?

public abstract Source getSource() ;

【问题讨论】:

  • 请参阅stackoverflow.com/questions/1087339/… 了解使用 Mockito 的两种解决方案。
  • 学习另一个测试框架有什么好处吗? Mockito 只是对 jUnit 的扩展,还是完全不同的项目?
  • Mockito 不会取代 JUnit。与其他模拟框架一样,它用于单元测试框架的补充,并帮助您创建模拟对象以在测试用例中使用。

标签: java junit abstract-class


【解决方案1】:

如果您没有该类的具体实现并且方法不是static,那么测试它们有什么意义?如果您有一个具体类,那么您将测试这些方法作为具体类的公共 API 的一部分。

我知道你在想什么“我不想一遍又一遍地测试这些方法,这就是我创建抽象类的原因”,但我对此的反驳是单元测试的目的是让开发人员能够进行更改、运行测试并分析结果。这些更改的一部分可能包括覆盖您的抽象类的方法,包括protectedpublic,这可能会导致基本的行为变化。根据这些更改的性质,它可能会以意想不到的、可能是负面的方式影响您的应用程序的运行方式。如果您有一个好的单元测试套件,那么这些类型更改引起的问题应该在开发时就很明显了。

【讨论】:

  • 100% 的代码覆盖率是一个神话。您应该有足够的测试来涵盖所有关于您的应用程序应该如何运行的已知假设(最好在编写代码 la 测试驱动开发之前编写)。我目前在一个功能非常强大的 TDD 团队中工作,截至我们上次构建时,我们只有 63% 的覆盖率,所有这些都是在我们开发时编写的。这样好吗?谁知道呢?但我认为返回并尝试将其提高到更高是浪费时间。
  • 当然。有些人会争辩说这违反了良好的 TDD。想象你在一个团队中。您假设该方法是最终的,并且不会在任何具体实现中进行测试。有人删除了修饰符并进行了更改,从而影响了继承层次结构的整个分支。您不想让您的测试套件捕捉到这一点吗?
  • 我不同意。无论您是否在 TDD 中工作,您的抽象类的具体方法都包含代码,因此,它们应该有测试(无论是否有子类)。此外,Java 测试(通常)类中的单元测试。因此,在测试方法中确实没有逻辑,这些方法不是类的一部分,而是它的超类的一部分。按照这个逻辑,我们不应该在 Java 中测试任何类,除了根本没有子类的类。关于被覆盖的方法,这正是您添加测试以检查对子类测试的更改/添加的时候。
  • @nsfyn55 如果具体方法是final呢?如果实现不能改变,我看不出有理由多次测试同一个方法
  • 我们不应该让测试以抽象接口为目标,以便我们可以为所有实现运行它们吗?如果不可能,我们将违反 Liskov 的规定,我们想了解并修复它。 只有如果实现添加了一些扩展(兼容)功能,我们应该对其进行特定的单元测试(并且仅针对该额外功能)。
【解决方案2】:

创建一个继承抽象类的具体类,然后测试具体类从抽象类继承的功能。

【讨论】:

  • 如果您有 10 个扩展抽象类的具体类,并且这些具体类中的每一个仅实现 1 个方法,并且假设其他 2 个方法对于这些类中的每一个都相同,您会怎么做,因为它们是在抽象类中实现的?我的情况是我不想将抽象类的测试复制粘贴到每个子类。
【解决方案3】:

对于您发布的示例类,测试 getFuel()getSpeed() 似乎没有多大意义,因为它们只能返回 0(没有设置器)。

但是,假设这只是一个用于说明目的的简化示例,并且您有正当理由测试抽象基类中的方法(其他人已经指出了含义),您可以设置您的测试代码,以便它创建一个基类的匿名子类,它只为抽象方法提供虚拟(无操作)实现。

例如,在您的TestCase 中,您可以这样做:

c = new Car() {
       void drive() { };
   };

然后测试其余的方法,例如:

public class CarTest extends TestCase
{
    private Car c;

    public void setUp()
    {
        c = new Car() {
            void drive() { };
        };
    }

    public void testGetFuel() 
    {
        assertEquals(c.getFuel(), 0);
    }

    [...]
}

(这个例子是基于JUnit3的语法。对于JUnit4,代码会略有不同,但思路是一样的。)

【讨论】:

  • 感谢您的回答。是的,我的例子被简化了(而且不是很好)。在这里阅读所有答案后,我写了虚拟课程。但正如@nsfyn55 在他的回答中所写,我为这个抽象类的每个后代编写测试。
  • 这是最好的答案。
【解决方案4】:

如果您仍然需要解决方案(例如,因为抽象类的实现太多并且测试总是重复相同的过程),那么您可以使用抽象工厂方法创建一个抽象测试类,该方法将由该测试类的实现。此示例适用于 TestNG 或我:

Car的抽象测试类:

abstract class CarTest {

// the factory method
abstract Car createCar(int speed, int fuel);

// all test methods need to make use of the factory method to create the instance of a car
@Test
public void testGetSpeed() {
    Car car = createCar(33, 44);
    assertEquals(car.getSpeed(), 33);
    ...

Car的实现

class ElectricCar extends Car {

    private final int batteryCapacity;

    public ElectricCar(int speed, int fuel, int batteryCapacity) {
        super(speed, fuel);
        this.batteryCapacity = batteryCapacity;
    }

    ...

ElectricCar类的单元测试类ElectricCarTest

class ElectricCarTest extends CarTest {

    // implementation of the abstract factory method
    Car createCar(int speed, int fuel) {
        return new ElectricCar(speed, fuel, 0);
    }

    // here you cann add specific test methods
    ...

【讨论】:

    【解决方案5】:

    你可以这样做

    public abstract MyAbstractClass {
    
        @Autowire
        private MyMock myMock;        
    
        protected String sayHello() {
                return myMock.getHello() + ", " + getName();
        }
    
        public abstract String getName();
    }
    
    // this is your JUnit test
    public class MyAbstractClassTest extends MyAbstractClass {
    
        @Mock
        private MyMock myMock;
    
        @InjectMocks
        private MyAbstractClass thiz = this;
    
        private String myName = null;
    
        @Override
        public String getName() {
            return myName;
        }
    
        @Test
        public void testSayHello() {
            myName = "Johnny"
            when(myMock.getHello()).thenReturn("Hello");
            String result = sayHello();
            assertEquals("Hello, Johnny", result);
        }
    }
    

    【讨论】:

      【解决方案6】:

      我将创建一个继承自抽象类的 jUnit 内部类。这可以被实例化并且可以访问抽象类中定义的所有方法。

      public class AbstractClassTest {
         public void testMethod() {
         ...
         }
      }
      
      
      class ConcreteClass extends AbstractClass {
      
      }
      

      【讨论】:

      • 这是很好的建议。不过,可以通过提供一个示例来改进它。也许是你所描述的类的一个例子。
      【解决方案7】:

      您可以实例化一个匿名类,然后测试该类。

      public class ClassUnderTest_Test {
      
          private ClassUnderTest classUnderTest;
      
          private MyDependencyService myDependencyService;
      
          @Before
          public void setUp() throws Exception {
              this.myDependencyService = new MyDependencyService();
              this.classUnderTest = getInstance();
          }
      
          private ClassUnderTest getInstance() {
              return new ClassUnderTest() {    
                  private ClassUnderTest init(
                          MyDependencyService myDependencyService
                  ) {
                      this.myDependencyService = myDependencyService;
                      return this;
                  }
      
                  @Override
                  protected void myMethodToTest() {
                      return super.myMethodToTest();
                  }
              }.init(myDependencyService);
          }
      }
      

      请记住,抽象类ClassUnderTest 的属性myDependencyService 的可见性必须为protected

      您还可以将此方法与 Mockito 巧妙地结合使用。见here

      【讨论】:

        【解决方案8】:

        我的测试方法很简单,在每个abstractUnitTest.java 中。我只是在 abstractUnitTest.java 中创建一个扩展抽象类的类。并以这种方式进行测试。

        【讨论】:

          【解决方案9】:

          你不能测试整个抽象类。在这种情况下,您有抽象方法,这意味着它们应该由扩展给定抽象类的类实现。

          在那个类中,程序员必须编写专门用于他的逻辑的源代码。

          换句话说,测试抽象类没有意义,因为您无法检查它的最终行为。

          如果您在某个抽象类中具有与抽象方法无关的主要功能,只需创建另一个抽象方法将引发异常的类。

          【讨论】:

            【解决方案10】:

            作为一个选项,您可以创建抽象测试类,覆盖抽象类内部的逻辑,并为每个子类测试扩展它。这样您就可以确保为每个孩子单独测试此逻辑。

            【讨论】:

              【解决方案11】:

              您不需要花哨的 Mockito 插件、匿名课程或其他答案推荐的任何其他东西。 Junit 支持相互扩展的测试类:因此,为您的抽象基类编写一个彻底的抽象测试类(实际上只是使测试类抽象),它检查每个方法的行为方式。对一组实例变量对象执行这些测试,这些对象是在此基础测试类的 @BeforeEach 方法中根据需要设置的。让该方法调用一个抽象的 allocateObjects() 方法,该方法将完成所有对象分配。

              然后,对于扩展您的抽象基础的每个类,都有一个扩展您刚刚编写的抽象测试类的测试类。这个测试类将在被覆盖的 allocateObjects() 方法中进行实际的对象分配。它分配的对象将被父测试类中的方法使用:由该测试类继承的方法,因此作为其测试的一部分运行。

              你能做工厂傻事吗?我猜:但是由于您可能无论如何都需要为每个子类创建测试类,所以您最好通过继承保持简单。我想如果你有很多子类,并且它们都没有从超类的东西中做任何值得测试的事情,那将是值得的:但是在这种情况下你到底为什么要创建子类?

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2023-03-04
                • 1970-01-01
                • 2018-09-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多