1. 概述
在Java中,继承是一个重要的概念,而接口是实现这一概念的一种方式。接口定义了一个契约,多个类可以实现它。因此,确保这些实现类遵循相同的规范至关重要。
本教程将探讨如何使用JUnit测试为Java接口编写不同方法。
2. 准备工作
首先,我们创建一个简单的名为Shape
的接口,它有一个area()
方法:
public interface Shape {
double area();
}
接下来,定义一个Circle
类,它实现了Shape
接口,并且有自己特有的circumference()
方法:
public class Circle implements Shape {
private double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return 3.14 * radius * radius;
}
public double circumference() {
return 2 * 3.14 * radius;
}
}
最后,定义另一个类Rectangle
,它也实现了Shape
接口,还新增了一个perimeter()
方法:
public class Rectangle implements Shape {
private double length;
private double breadth;
public Rectangle(double length, double breadth) {
this.length = length;
this.breadth = breadth;
}
@Override
public double area() {
return length * breadth;
}
public double perimeter() {
return 2 * (length + breadth);
}
}
3. 测试方法
现在让我们看看可以采用的不同测试策略。
3.1. 实现类的独立测试
一种常见的做法是为接口的每个实现类创建单独的JUnit测试类。我们会测试每个类继承的接口方法以及它们自身定义的方法。
首先,我们创建CircleUnitTest
类,针对area()
和circumference()
方法编写测试用例:
@Test
void whenAreaIsCalculated_thenSuccessful() {
Shape circle = new Circle(5);
double area = circle.area();
assertEquals(78.5, area);
}
@Test
void whenCircumferenceIsCalculated_thenSuccessful(){
Circle circle = new Circle(2);
double circumference = circle.circumference();
assertEquals(12.56, circumference);
}
接着,我们创建RectangleUnitTest
类,针对area()
和perimeter()
方法编写测试用例:
@Test
void whenAreaIsCalculated_thenSuccessful() {
Shape rectangle = new Rectangle(5,4);
double area = rectangle.area();
assertEquals(20, area);
}
@Test
void whenPerimeterIsCalculated_thenSuccessful() {
Rectangle rectangle = new Rectangle(5,4);
double perimeter = rectangle.perimeter();
assertEquals(18, perimeter);
}
如上述两个类所示,我们可以成功地测试接口方法以及实现类可能定义的额外方法。
通过这种方法,我们可能需要为接口方法在所有实现类中重复编写相同的测试。例如,我们在两个实现类中都测试了area()
方法。
随着实现类数量的增长,由于接口定义的方法数量增加,测试用例也会成倍增长。这会导致代码复杂性和冗余性增加,随着时间的推移维护和修改变得困难。
3.2. 参数化测试
为了解决这个问题,我们可以创建一个参数化测试,它接受不同实现类的实例作为输入:
@ParameterizedTest
@MethodSource("data")
void givenShapeInstance_whenAreaIsCalculated_thenSuccessful(Shape shapeInstance, double expectedArea){
double area = shapeInstance.area();
assertEquals(expectedArea, area);
}
private static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ new Circle(5), 78.5 },
{ new Rectangle(4, 5), 20 }
});
}
这样,我们已经成功地验证了接口对实现类的合同要求。
然而,我们无法在接口定义之外定义任何其他内容。因此,可能仍需要以某种形式测试实现类。这可能需要在它们自己的JUnit类中进行测试。
3.3. 使用基类测试
前两种方法缺乏足够的灵活性来扩展测试用例,除了验证接口合同外。同时,我们也希望避免代码冗余。接下来,我们将探讨另一种可以解决这两个问题的方法。
在这种方法中,我们定义一个基础测试类。这个抽象的测试类定义了要测试的方法,即接口的合同。随后,实现类的测试类可以扩展这个抽象测试类,以增强测试。
我们将使用模板方法模式,在基础测试类中定义测试area()
方法的算法,然后子类只需要提供要在算法中使用的实现即可。
首先,定义基础测试类来测试area()
方法:
public abstract Map<String, Object> instantiateShapeWithExpectedArea();
@Test
void givenShapeInstance_whenAreaIsCalculated_thenSuccessful() {
Map<String, Object> shapeAreaMap = instantiateShapeWithExpectedArea();
Shape shape = (Shape) shapeAreaMap.get("shape");
double expectedArea = (double) shapeAreaMap.get("area");
double area = shape.area();
assertEquals(expectedArea, area);
}
接下来,创建Circle
类的JUnit测试类:
@Override
public Map<String, Object> instantiateShapeWithExpectedArea() {
Map<String,Object> shapeAreaMap = new HashMap<>();
shapeAreaMap.put("shape", new Circle(5));
shapeAreaMap.put("area", 78.5);
return shapeAreaMap;
}
@Test
void whenCircumferenceIsCalculated_thenSuccessful(){
Circle circle = new Circle(2);
double circumference = circle.circumference();
assertEquals(12.56, circumference);
}
最后,创建Rectangle
类的测试类:
@Override
public Map<String, Object> instantiateShapeWithExpectedArea() {
Map<String,Object> shapeAreaMap = new HashMap<>();
shapeAreaMap.put("shape", new Rectangle(5,4));
shapeAreaMap.put("area", 20.0);
return shapeAreaMap;
}
@Test
void whenPerimeterIsCalculated_thenSuccessful() {
Rectangle rectangle = new Rectangle(5,4);
double perimeter = rectangle.perimeter();
assertEquals(18, perimeter);
}
在这个方法中,我们重写了instantiateShapeWithExpectedArea()
方法。在这个方法中,我们提供了Shape
实例和预期的面积。这些参数可以被基础测试类中的测试方法用来执行测试。
总结来说,通过这种方法,实现类可以为其自身方法编写测试,并继承接口方法的测试。
4. 结论
在这篇文章中,我们探讨了验证接口合同的不同JUnit测试方法。
首先,我们看到了为每个实现类单独创建测试类的直接性,但这也可能导致大量冗余代码。
然后,我们研究了如何使用参数化测试来避免冗余,但其灵活性较低。
最后,我们看到了基类测试方法,它解决了前两种方法中的问题。
如往常一样,源代码可以在GitHub上找到。