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

我是用 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 FAQ 部分,我找到了 这个链接,但是我不明白作者在这个例子中想说什么。这行代码是什么意思?

public abstract Source getSource() ;
125199 次浏览

Create a concrete class that inherits the abstract class and then test the functions the concrete class inherits from the abstract class.

If you have no concrete implementations of the class and the methods aren't static whats the point of testing them? If you have a concrete class then you'll be testing those methods as part of the concrete class's public API.

I know what you are thinking "I don't want to test these methods over and over thats the reason I created the abstract class", but my counter argument to that is that the point of unit tests is to allow developers to make changes, run the tests, and analyze the results. Part of those changes could include overriding your abstract class's methods, both protected and public, which could result in fundamental behavioral changes. Depending on the nature of those changes it could affect how your application runs in unexpected, possibly negative ways. If you have a good unit testing suite problems arising from these types changes should be apparent at development time.

You can not test whole abstract class. In this case you have abstract methods, this mean that they should be implemented by class that extend given abstract class.

In that class programmer have to write the source code that is dedicated for logic of his.

In other words there is no sens of testing abstract class because you are not able to check the final behavior of it.

If you have major functionality not related to abstract methods in some abstract class, just create another class where the abstract method will throw some exception.

With the example class you posted it doesn't seem to make much sense to test getFuel() and getSpeed() since they can only return 0 (there are no setters).

However, assuming that this was just a simplified example for illustrative purposes, and that you have legitimate reasons to test methods in the abstract base class (others have already pointed out the implications), you could setup your test code so that it creates an anonymous subclass of the base class that just provides dummy (no-op) implementations for the abstract methods.

For example, in your TestCase you could do this:

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

Then test the rest of the methods, e.g.:

public class CarTest extends TestCase
{
private Car c;


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


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


[...]
}

(This example is based on JUnit3 syntax. For JUnit4, the code would be slightly different, but the idea is the same.)

I would create a jUnit inner class that inherits from the abstract class. This can be instantiated and have access to all the methods defined in the abstract class.

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




class ConcreteClass extends AbstractClass {


}

You could do something like this

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

If you need a solution anyway (e.g. because you have too many implementations of the abstract class and the testing would always repeat the same procedures) then you could create an abstract test class with an abstract factory method which will be excuted by the implementation of that test class. This examples works or me with TestNG:

The abstract test class of 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);
...

Implementation of Car

class ElectricCar extends Car {


private final int batteryCapacity;


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


...

Unit test class ElectricCarTest of the Class 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
...

You can instantiate an anonymous class and then test that class.

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

Keep in mind that the visibility must be protected for the property myDependencyService of the abstract class ClassUnderTest.

You can also combine this approach neatly with Mockito. See here.

As an option, you can create abstract test class covering logic inside abstract class and extend it for each subclass test. So that in this way you can ensure this logic will be tested for each child separately.

My way of testing this is quite simple, within each abstractUnitTest.java. I simply create a class in the abstractUnitTest.java that extend the abstract class. And test it that way.

You don't need a fancy Mockito add on, or anonymous classes, or whatever other things the other answers are recommending. Junit supports test classes extending each other: so, write a thorough, abstract test class (literally just make the test class abstract) for your abstract base class, that examines how each of the methods behave. Do these tests on a set of instance-variable objects, that are set up as you desire in an @BeforeEach method in this base test class. Have that method call an abstract allocateObjects() method, which will do all the object allocation.

Then, for each class that extends your abstract base, have a test class that extends the abstract test class you just wrote. This test class will do the actual object allocation in the overridden allocateObjects() method. The objects it allocates will be used by the methods in the parent test class: methods which are inhertied by this test class, and therefore run as a part of its testing.

Could you do factory tomfoollery? I guess: but since you probably need to create test classes for each subclass anyways, you might as well just keep things simple with inheritence. I suppose if you have a lot of subclasses, and none of them do anything that is worth testing appart from the superclass stuff, it would be worth it: but why on earth would you be creating subclasses in that case?

Instead of doing @inject mock on abstract class create a spy and create a anonymous implementation in the test class itself and use that to test your abstract class.Better not to do that as there should not be any public method on with you can do unit test.Keep it protected and call those method from implemented classes and test only those classes.