如何使用JUnit在Java中测试抽象类?

vas*_*sco 82 java junit abstract-class

我是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我想测试getSpeed()getFuel()功能.

这个问题的类似问题在这里,但它没有使用JUnit.

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

public abstract Source getSource() ;
Run Code Online (Sandbox Code Playgroud)

nsf*_*n55 102

如果你没有类的具体实现,那么方法不是static测试它们的重点吗?如果你有一个具体的类,那么你将测试这些方法作为具体类的公共API的一部分.

我知道你在想什么"我不想一遍又一遍地测试这些方法,这就是我创建抽象类的原因",但我反驳的是单元测试的重点是允许开发人员进行更改,运行测试,并分析结果.这些变化部分可能包括重写你的抽象类的方法,既protectedpublic,这可能导致根本性的行为变化.根据这些更改的性质,它可能会影响您的应用程序以意外的方式运行,可能是负面的方式.如果您有一个好的单元测试套件,这些类型引起的问题在开发时应该是明显的.

  • 我不同意.无论您是否使用TDD,您的抽象类的具体方法都包含代码,因此,它们应该具有测试(无论是否存在子类).而且,Java测试(通常)类中的单元测试.因此,测试方法中没有逻辑,不是类的一部分,而是它的超类.遵循该逻辑,我们不应该在Java中测试任何类,除了根本没有子类的类.关于被覆盖的方法,这正是您添加测试以检查子类测试的更改/添加的时间. (28认同)
  • 100%的代码覆盖率是一个神话.您应该有足够的测试来涵盖有关应用程序应该如何表现的所有已知假设(最好在编写测试驱动开发代码之前编写).我目前正在开发一个功能非常强大的TDD团队,我们在上一次构建时只有63%的覆盖率都是我们开发的.这样好吗?谁知道?,但我认为回去试图提高那个更高的时间是浪费时间. (15认同)
  • 当然.有人会认为这违反了良好的TDD.想象一下,你是一个团队.您假设该方法是最终的,并且不在任何具体实现中进行测试.有人删除修饰符并进行更改,这些更改会影响继承层次结构的整个分支.你不想让你的测试套件赶上吗? (3认同)
  • 我们不应该将测试作为目标抽象接口,以便我们可以为所有实现运行它们吗?如果不可能,我们会违反Liskov的,我们想知道并修复.*仅*如果实现添加了一些扩展(兼容)功能,我们应该为它进行特定的单元测试(并且只是为了额外的功能). (3认同)
  • @eitanfar我可以看到你不同意的地方,但抽象类不是一个类,它是一个类的模板。在你扩展它之前,它不值得测试。 (2认同)
  • @ nsfyn55如果具体方法是“最终的”怎么办?如果实现*不能*更改,我看不出有理由多次测试同一方法的原因 (2认同)

Kev*_*sox 33

创建一个继承抽象类的具体类,然后测试具体类从抽象类继承的函数.


Gro*_*uez 12

使用您发布的示例类,测试似乎没有多大意义getFuel(),getSpeed()因为它们只能返回0(没有setter).

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

例如,TestCase你可以这样做:

c = new Car() {
       void drive() { };
   };
Run Code Online (Sandbox Code Playgroud)

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

public class CarTest extends TestCase
{
    private Car c;

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

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

    [...]
}
Run Code Online (Sandbox Code Playgroud)

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


tho*_*ork 9

如果你还需要一个解决方案(例如,因为你有太多的抽象类实现,并且测试总是会重复相同的过程),那么你可以用一个抽象的工厂方法创建一个抽象的测试类,这个方法将通过实现考试班.这个例子适用于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);
    ...
Run Code Online (Sandbox Code Playgroud)

实施 Car

class ElectricCar extends Car {

    private final int batteryCapacity;

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

    ...
Run Code Online (Sandbox Code Playgroud)

ElectricCarTest的单元测试类ElectricCar:

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
    ...
Run Code Online (Sandbox Code Playgroud)


小智 8

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

public class AbstractClassTest {
   public void testMethod() {
   ...
   }
}


class ConcreteClass extends AbstractClass {

}
Run Code Online (Sandbox Code Playgroud)

  • 这是很好的建议。不过,可以通过提供示例来改进它。也许是您所描述的课程的一个例子。 (3认同)

小智 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);
    }
}
Run Code Online (Sandbox Code Playgroud)