用Java測試介面契約
1. 概述
繼承是Java中的一個重要概念。接口是我們實現這一概念的方式之一。
介面定義了多個類別可以實現的契約。隨後,必須測試這些實作類別以確保它們遵循相同的要求。
在本教程中,我們將了解用 Java 編寫介面 JUnit 測試的不同方法。
2. 設定
讓我們建立一個用於不同方法的基本設定。
首先,我們先建立一個名為Shape,
它有一個方法area()
:
public interface Shape {
double area();
}
其次,我們定義一個實作Shape
介面的Circle
類別。它還有一個自己的方法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.使用基底測試類
使用前兩種方法,除了驗證介面契約之外,我們沒有足夠的彈性來擴展測試案例。同時,我們也要避免程式碼冗餘。因此,讓我們看看另一種可以解決這兩個問題的方法。
在這種方法中,我們定義一個基底測試類別。這個abstract
測試類別定義了要測試的方法,即介面契約。隨後,實作類別的測試類別可以擴展這個抽象測試類別以建構測試。
我們將使用模板方法模式,其中我們定義演算法來測試基底測試類別中的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 上取得。