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


87

我是第一次使用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()运行。

与此问题类似的问题在这里,但它没有使用JUnit。

在“ JUnit常见问题解答”部分中,我找到了此链接,但是我不理解作者想在此示例中说些什么。这行代码是什么意思?

public abstract Source getSource() ;

4
有关使用Mockito的两种解决方案,请参见stackoverflow.com/questions/1087339/…
2011年

学习另一个测试框架有什么好处吗?Mockito只是jUnit的扩展,还是完全不同的项目?
vasco 2011年

Mockito不替代JUnit。像其他模拟框架一样,它除用于单元测试框架外,还可以帮助您创建要在测试用例中使用的模拟对象。
2011年

Answers:


104

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

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


17
100%的代码覆盖率是一个神话。您应该有足够的测试来涵盖所有有关应用程序行为的已知假设(最好在编写代码之前进行测试驱动开发)。我目前正在一个功能非常强大的TDD团队中工作,截至上一版,我们只有63%的覆盖率是在开发时编写的。这样好吗 谁知道?但是我会认为浪费时间去尝试将其提高到更高水平。
nsfyn55 2011年

3
当然。有人会认为这违反了良好的TDD。想象你在一个团队中。您假设该方法是最终方法,并且不会在任何具体实现中进行测试。有人删除修饰符并进行更改,从而沿袭继承层次结构的整个分支。您不想让您的测试套件抓住这一点吗?
nsfyn55

31
我不同意。无论您是否在TDD中工作,抽象类的具体方法都包含代码,因此,它们应具有测试(无论是否存在子类)。而且,Java中的单元测试(通常)测试类。因此,测试方法中实际上没有逻辑,这些逻辑不是该类的一部分,而是其超类。按照这种逻辑,除了完全没有子类的类之外,我们不应该在Java中测试任何类。关于被重写的方法,这恰恰是在添加测试以检查对子类的测试的更改/添加时。
ethanfar

3
@ nsfyn55如果具体方法是final什么?如果实现无法更改,我看不出要多次测试相同方法的理由
Dioxin 2015年

3
我们是否应该使测试针对抽象接口,以便我们可以针对所有实现运行它们?如果不可能的话,我们将违反我们想要了解和解决的Liskov。只有在实现中添加了一些扩展的(兼容)功能后,我们才应该对其进行特定的单元测试(并仅针对该额外功能)。
TNE

36

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


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

12

使用您发布的示例类,测试似乎没有多大意义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一样,我为该抽象类的每个后代编写了test。
vasco 2011年

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);
    ...

实施 Car

class ElectricCar extends Car {

    private final int batteryCapacity;

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

    ...

班级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
    ...

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

4

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

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


class ConcreteClass extends AbstractClass {

}

3
这是极好的建议。不过,可以通过提供示例来进行改进。也许是您要描述的类的一个示例。
SDJMcHattie 2014年

2

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

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

请记住,可见性必须是抽象类protected的属性。myDependencyServiceClassUnderTest

您也可以将这种方法与Mockito巧妙地结合在一起。看这里


2

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


0

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

在该类中,程序员必须编写专用于其逻辑的源代码。

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

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


0

作为一种选择,您可以创建涵盖抽象类内部逻辑的抽象测试类,并将其扩展到每个子类测试。这样,您就可以确保分别为每个孩子测试此逻辑。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.