Làm thế nào để kiểm tra lớp trừu tượng trong Java với JUnit?


87

Tôi mới thử nghiệm Java với JUnit. Tôi phải làm việc với Java và tôi muốn sử dụng các bài kiểm tra đơn vị.

Vấn đề của tôi là: Tôi có một lớp trừu tượng với một số phương thức trừu tượng. Nhưng có một số phương pháp không trừu tượng. Làm cách nào để kiểm tra lớp này với JUnit? Mã ví dụ (rất đơn giản):

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

Tôi muốn kiểm tra getSpeed()và các getFuel()chức năng.

Câu hỏi tương tự cho vấn đề này là ở đây , nhưng nó không sử dụng JUnit.

Trong phần JUnit FAQ, tôi tìm thấy liên kết này , nhưng tôi không hiểu tác giả muốn nói gì với ví dụ này. Dòng mã này có nghĩa là gì?

public abstract Source getSource() ;

4
Xem stackoverflow.com/questions/1087339/… để biết hai giải pháp sử dụng Mockito.
ddso

Có lợi thế nào để học một khuôn khổ khác để thử nghiệm không? Mockito chỉ là một phần mở rộng cho jUnit, hay một dự án hoàn toàn khác?
vasco

Mockito không thay thế JUnit. Giống như các khuôn khổ mô phỏng khác, nó được sử dụng ngoài khuôn khổ thử nghiệm đơn vị và giúp bạn tạo các đối tượng giả để sử dụng trong các trường hợp thử nghiệm của mình.
ddso

Câu trả lời:


104

Nếu bạn không có triển khai cụ thể của lớp và các phương thức không có ý nghĩa staticgì để kiểm tra chúng? Nếu bạn có một lớp cụ thể thì bạn sẽ thử nghiệm các phương thức đó như một phần của API công khai của lớp cụ thể.

Tôi biết bạn đang nghĩ gì "Tôi không muốn thử nghiệm lặp đi lặp lại các phương pháp này đó là lý do tôi tạo lớp trừu tượng", nhưng lập luận phản bác của tôi là quan điểm của các bài kiểm tra đơn vị là cho phép các nhà phát triển thực hiện thay đổi, chạy các bài kiểm tra và phân tích kết quả. Một phần của những thay đổi đó có thể bao gồm việc ghi đè các phương thức của lớp trừu tượng của bạn, cả hai protectedpublic, điều này có thể dẫn đến các thay đổi hành vi cơ bản. Tùy thuộc vào bản chất của những thay đổi đó, nó có thể ảnh hưởng đến cách ứng dụng của bạn chạy theo những cách không mong muốn, có thể là tiêu cực. Nếu bạn có một bộ thử nghiệm đơn vị tốt, các vấn đề phát sinh từ các loại này sẽ thay đổi rõ ràng tại thời điểm phát triển.


17
100% mã bao phủ là một huyền thoại. Bạn nên có đủ chính xác các bài kiểm tra để bao gồm tất cả các giả thuyết đã biết về cách ứng dụng của bạn sẽ hoạt động (tốt nhất là nên viết trước khi bạn viết mã la Test Driven Development). Tôi hiện đang làm việc trong một nhóm TDD hoạt động rất hiệu quả và chúng tôi chỉ có 63% mức độ phù hợp với phiên bản cuối cùng của chúng tôi, tất cả đều được viết khi chúng tôi phát triển. Điều đó có tốt không? ai biết được ?, nhưng tôi sẽ coi việc quay lại và cố gắng tăng mức đó lên cao hơn là một sự lãng phí thời gian.
nsfyn55

3
chắc chắn rồi. Một số người cho rằng đó là vi phạm TDD tốt. Hãy tưởng tượng bạn đang ở trong một đội. Bạn giả định rằng phương pháp là cuối cùng và không đưa các thử nghiệm vào bất kỳ triển khai cụ thể nào. Ai đó loại bỏ công cụ sửa đổi và thực hiện các thay đổi làm xáo trộn toàn bộ nhánh của hệ thống phân cấp kế thừa. Bạn có muốn bộ thử nghiệm của mình bắt được điều đó không?
nsfyn55

31
Tôi không đồng ý. Cho dù bạn có làm việc trong TDD hay không, phương thức cụ thể của lớp trừu tượng của bạn chứa mã, do đó, chúng phải có các bài kiểm tra (bất kể có lớp con hay không). Hơn nữa, kiểm thử đơn vị trong các lớp kiểm tra Java (thông thường). Do đó, thực sự không có logic nào trong các phương pháp kiểm thử không phải là một phần của lớp, mà là siêu lớp của nó. Theo logic đó, chúng ta không nên kiểm tra bất kỳ lớp nào trong Java, ngoại trừ các lớp không có lớp con nào cả. Về việc các phương thức bị ghi đè, đó chính xác là khi bạn thêm một bài kiểm tra để kiểm tra những thay đổi / bổ sung cho bài kiểm tra của lớp con.
ethanfar

3
@ nsfyn55 Điều gì sẽ xảy ra nếu các phương pháp cụ thể là final? Tôi không thấy có lý do để thử nghiệm các phương pháp tương tự nhiều lần nếu việc thực hiện không thể thay đổi
Dioxin

3
Chúng ta không nên để các bài kiểm tra nhắm mục tiêu đến giao diện trừu tượng để chúng ta có thể chạy chúng cho tất cả các triển khai? Nếu không thể, chúng tôi sẽ vi phạm Liskov, điều mà chúng tôi muốn biết và sửa chữa. Chỉ khi việc triển khai thêm một số chức năng mở rộng (tương thích) thì chúng tôi mới nên có một bài kiểm tra đơn vị cụ thể cho nó (và chỉ cho chức năng bổ sung đó).
tne

36

Tạo một lớp cụ thể kế thừa lớp trừu tượng và sau đó kiểm tra các chức năng mà lớp cụ thể kế thừa từ lớp trừu tượng.


Bạn sẽ làm gì trong trường hợp bạn có 10 lớp cụ thể mở rộng lớp trừu tượng và mỗi lớp cụ thể này sẽ chỉ triển khai 1 phương thức và giả sử rằng 2 phương thức khác giống nhau đối với mỗi lớp này, bởi vì chúng được triển khai trong phần tóm tắt lớp học? Trường hợp của tôi là tôi không muốn sao chép-dán các bài kiểm tra cho lớp trừu tượng vào mỗi lớp con.
Scarface

12

Với lớp mẫu mà bạn đã đăng, dường như không có ý nghĩa gì để kiểm tra getFuel()getSpeed()vì chúng chỉ có thể trả về 0 (không có bộ cài đặt).

Tuy nhiên, giả sử rằng đây chỉ là một ví dụ đơn giản cho mục đích minh họa và bạn có lý do chính đáng để kiểm tra các phương thức trong lớp cơ sở trừu tượng (những người khác đã chỉ ra các hàm ý), bạn có thể thiết lập mã kiểm tra của mình để nó tạo ra một ẩn danh lớp con của lớp cơ sở chỉ cung cấp các triển khai giả (no-op) cho các phương thức trừu tượng.

Ví dụ, trong của TestCasebạn, bạn có thể làm điều này:

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

Sau đó kiểm tra phần còn lại của các phương pháp, ví dụ:

public class CarTest extends TestCase
{
    private Car c;

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

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

    [...]
}

(Ví dụ này dựa trên cú pháp JUnit3. Đối với JUnit4, mã sẽ hơi khác một chút, nhưng ý tưởng thì giống nhau.)


Cảm ơn về câu trả lời. Vâng, ví dụ của tôi đã được đơn giản hóa (và không quá tốt). Sau khi đọc tất cả các câu trả lời ở đây, tôi đã viết lớp học giả. Nhưng như đã viết @ nsfyn55 trong câu trả lời của anh ấy, tôi viết bài kiểm tra cho mọi hậu duệ của lớp trừu tượng này.
vasco

9

Nếu bạn vẫn cần một giải pháp (ví dụ: vì bạn có quá nhiều triển khai của lớp trừu tượng và việc kiểm tra sẽ luôn lặp lại các thủ tục giống nhau) thì bạn có thể tạo một lớp kiểm tra trừu tượng với một phương thức nhà máy trừu tượng sẽ được thực thi bằng cách triển khai nó lớp kiểm tra. Ví dụ này hoạt động hoặc tôi với TestNG:

Lớp kiểm tra trừu tượng của 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);
    ...

Tiến hành làm 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 ElectricCarTestcủa 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
    ...

5

Bạn có thể làm điều gì đó như thế này

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

Tôi sẽ tạo một lớp bên trong jUnit kế thừa từ lớp trừu tượng. Điều này có thể được khởi tạo và có quyền truy cập vào tất cả các phương thức được định nghĩa trong lớp trừu tượng.

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


class ConcreteClass extends AbstractClass {

}

3
Đây là lời khuyên tuyệt vời. Tuy nhiên, nó có thể được cải thiện bằng cách cung cấp một ví dụ. Có lẽ một ví dụ về lớp học mà bạn đang mô tả.
SDJMcHattie

2

Bạn có thể khởi tạo một lớp ẩn danh và sau đó kiểm tra lớp đó.

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

Hãy nhớ rằng khả năng hiển thị phải protecteddành cho thuộc tính myDependencyServicecủa lớp trừu tượng ClassUnderTest.

Bạn cũng có thể kết hợp cách tiếp cận này một cách gọn gàng với Mockito. Xem tại đây .


2

Cách kiểm tra của tôi khá đơn giản, trong mỗi cái abstractUnitTest.java. Tôi chỉ cần tạo một lớp trong abstractUnitTest.java để mở rộng lớp trừu tượng. Và kiểm tra nó theo cách đó.


0

Bạn không thể kiểm tra toàn bộ lớp trừu tượng. Trong trường hợp này, bạn có các phương thức trừu tượng, điều này có nghĩa là chúng phải được thực thi bởi lớp mở rộng lớp trừu tượng đã cho.

Trong lớp đó, lập trình viên phải viết mã nguồn dành riêng cho logic của mình.

Nói cách khác, không có cảm giác kiểm tra lớp trừu tượng vì bạn không thể kiểm tra hành vi cuối cùng của nó.

Nếu bạn có chức năng chính không liên quan đến các phương thức trừu tượng trong một số lớp trừu tượng, chỉ cần tạo một lớp khác nơi phương thức trừu tượng sẽ ném ra một số ngoại lệ.


0

Như một tùy chọn, bạn có thể tạo lớp kiểm tra trừu tượng bao gồm logic bên trong lớp trừu tượng và mở rộng nó cho mỗi bài kiểm tra lớp con. Vì vậy, bằng cách này, bạn có thể đảm bảo logic này sẽ được kiểm tra cho từng đứa trẻ riêng biệt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.