Mockito chế nhạo lớp cuối cùng tại địa phương nhưng thất bại ở Jenkins


11

Tôi đã viết một số bài kiểm tra đơn vị cho một phương thức tĩnh. Phương thức tĩnh chỉ mất một đối số. Kiểu của đối số là một lớp cuối cùng. Về mặt mã:

public class Utility {

   public static Optional<String> getName(Customer customer) {
       // method's body.
   }
}

public final class Customer {
   // class definition
}

Vì vậy, đối với Utilitylớp tôi đã tạo ra một lớp kiểm tra, UtilityTeststrong đó tôi đã viết các bài kiểm tra cho phương thức này , getName. Khung kiểm thử đơn vị là TestNG và thư viện giả được sử dụng là Mockito. Vì vậy, một thử nghiệm điển hình có cấu trúc sau:

public class UtilityTests {

   @Test
   public void getNameTest() {
     // Arrange
     Customer customerMock = Mockito.mock(Customer.class);
     Mockito.when(...).thenReturn(...);

     // Act
     Optional<String> name = Utility.getName(customerMock);

     // Assert
     Assert.assertTrue(...);
   }
}

Vấn đề là gì?

Trong khi các thử nghiệm chạy thành công cục bộ, bên trong IntelliJ, chúng thất bại trên Jenkins (khi tôi đẩy mã của mình trong nhánh từ xa, một bản dựng được kích hoạt và các thử nghiệm đơn vị chạy ở cuối). Thông báo lỗi là sth như sau:

org.mockito.exceptions.base.MockitoException: Không thể lớp mock / spy com.packagename.Customer Mockito không thể mock / spy vì: - lớp cuối cùng

Những gì tôi đã cố gắng?

Tôi đã tìm kiếm một chút, để tìm giải pháp nhưng tôi đã không thực hiện. Tôi lưu ý ở đây rằng tôi không được phép thay đổi thực tế đó Customerlà lớp cuối cùng . Ngoài ra, tôi muốn nếu không thể thay đổi thiết kế của nó (ví dụ: tạo giao diện, sẽ giữ các phương thức mà tôi muốn chế giễu và nói rằng lớp Khách hàng thực hiện giao diện đó, như chính xác mà Jose đã chỉ ra trong bình luận). Điều mà tôi đã thử là lựa chọn thứ hai được đề cập tại trận chung kết mockito . Mặc dù thực tế là điều này đã khắc phục được sự cố, nhưng nó đã phanh một số bài kiểm tra đơn vị khác :(, điều đó không thể được khắc phục theo cách rõ ràng.

Câu hỏi

Vì vậy, đây là hai câu hỏi tôi có:

  1. Làm thế nào là có thể ở nơi đầu tiên? Không nên thử nghiệm thất bại cả ở địa phương và ở Jenkins?
  2. Làm thế nào điều này có thể được sửa chữa dựa trên các ràng buộc tôi đã đề cập ở trên?

Cảm ơn trước sự giúp đỡ nào.


1
Tôi đoán là enable finalcấu hình hoạt động trong không gian làm việc của bạn, nhưng khi chạy trên Jenkinsnó không thể tìm thấy tệp này. Kiểm tra nơi Jenkinsđang tìm kiếm tập tin và liệu nó thực sự có ở đó hay không.
thứ hai

Chủ đề khác này giải thích cách kích hoạt chế độ mô phỏng lớp cuối cùng trong Mockito 2, bằng cách thêm tệp cấu hình mockito trong thư mục tài nguyên: stackoverflow.com/questions/14292863/
Lỗi

3
Có thể, trong mã bạn đang xử lý, để trích xuất một giao diện từ lớp Khách hàng, nói ICustomer và sử dụng nó trong lớp Tiện ích? Sau đó, bạn có thể chế giễu giao diện đó thay vì lớp cuối cùng cụ thể
Jose Tepedino

@JoseTepedino Đây là một điểm hợp lệ. Nó hoàn toàn có ý nghĩa và đó chắc chắn là một cách thanh lịch để khắc phục vấn đề này. Tuy nhiên tôi tự hỏi liệu có cách nào khác và quan trọng hơn, tôi muốn hiểu tại sao cách tiếp cận hiện tại thành công cục bộ và thất bại ở Jenkins.
Christos

1
Customerlogic nào trong đó không, hay nó chỉ là một lớp dữ liệu câm? Nếu nó chỉ là một loạt các trường với getters và setters, thì bạn có thể khởi tạo nó.
Willis Blackburn

Câu trả lời:


2

Một cách tiếp cận khác là sử dụng mẫu 'phương thức cho lớp'.

  1. Di chuyển các phương thức ra khỏi lớp khách hàng sang một lớp / lớp khác, ví dụ như CustomerS Something, ví dụ như / CustomerFinances (hoặc bất cứ trách nhiệm nào của nó).
  2. Thêm một nhà xây dựng cho khách hàng.
  3. Bây giờ bạn không cần phải chế giễu Khách hàng, chỉ là lớp Khách hàng! Bạn có thể không cần phải chế giễu rằng nếu nó không có phụ thuộc bên ngoài.

Đây là một blog tốt về chủ đề: https://simpleprogrammer.com/back-to-basics-mock-006inating-potypes/


1
Cảm ơn câu trả lời của bạn (+1). Tôi tìm thấy một cách để sửa nó (trả lời cho câu hỏi thứ hai). Tuy nhiên, lý do tại sao các bài kiểm tra thất bại trong IntelliJ vẫn chưa rõ ràng đối với tôi. Hơn nữa, tôi không thể tái tạo nó nữa (sự thất bại bên trong IntelliJ), điều này hoàn toàn kỳ lạ.
Christos

1

Làm thế nào là có thể ở nơi đầu tiên? Không nên thử nghiệm thất bại cả ở địa phương và ở Jenkins?

Nó rõ ràng là một loại đặc thù của env. Câu hỏi duy nhất là - làm thế nào để xác định nguyên nhân của sự khác biệt.

Tôi khuyên bạn nên kiểm tra org.mockito.internal.util.MockUtil#typeMockabilityOfphương pháp và so sánh, cái gì mockMakerthực sự được sử dụng trong cả hai môi trường và tại sao.

Nếu mockMakergiống nhau - so sánh các lớp được tải IDE-Clientvới Jenkins-Client- chúng có khác biệt gì về thời gian thực hiện kiểm tra không.

Làm thế nào điều này có thể được sửa chữa dựa trên các ràng buộc tôi đã đề cập ở trên?

Đoạn mã sau được viết theo giả định của OpenJDK 12 và Mockito 2.28.2, nhưng tôi tin rằng bạn có thể điều chỉnh nó thành bất kỳ phiên bản thực sự được sử dụng nào.

public class UtilityTest {    
    @Rule
    public InlineMocksRule inlineMocksRule = new InlineMocksRule();

    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Test
    public void testFinalClass() {
        // Given
        String testName = "Ainz Ooal Gown";
        Client client = Mockito.mock(Client.class);
        Mockito.when(client.getName()).thenReturn(testName);

        // When
        String name = Utility.getName(client).orElseThrow();

        // Then
        assertEquals(testName, name);
    }

    static final class Client {
        final String getName() {
            return "text";
        }
    }

    static final class Utility {
        static Optional<String> getName(Client client) {
            return Optional.ofNullable(client).map(Client::getName);
        }
    }    
}

Với một quy tắc riêng cho các giả lập nội tuyến:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.MockUtil;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class InlineMocksRule implements TestRule {
    private static Field MOCK_MAKER_FIELD;

    static {
        try {
            MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
            VarHandle modifiers = lookup.findVarHandle(Field.class, "modifiers", int.class);

            MOCK_MAKER_FIELD = MockUtil.class.getDeclaredField("mockMaker");
            MOCK_MAKER_FIELD.setAccessible(true);

            int mods = MOCK_MAKER_FIELD.getModifiers();
            if (Modifier.isFinal(mods)) {
                modifiers.set(MOCK_MAKER_FIELD, mods & ~Modifier.FINAL);
            }
        } catch (IllegalAccessException | NoSuchFieldException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                Object oldMaker = MOCK_MAKER_FIELD.get(null);
                MOCK_MAKER_FIELD.set(null, Plugins.getPlugins().getInlineMockMaker());
                try {
                    base.evaluate();
                } finally {
                    MOCK_MAKER_FIELD.set(null, oldMaker);
                }
            }
        };
    }
}

Cảm ơn câu trả lời của bạn (+1). Tôi tìm thấy một cách để sửa nó (trả lời cho câu hỏi thứ hai). Tuy nhiên, lý do tại sao các bài kiểm tra thất bại trong IntelliJ vẫn chưa rõ ràng đối với tôi. Hơn nữa, tôi không thể tái tạo nó nữa (sự thất bại bên trong IntelliJ), điều này hoàn toàn kỳ lạ.
Christos

1

Hãy chắc chắn rằng bạn chạy thử nghiệm với các đối số tương tự. Kiểm tra xem cấu hình chạy intellij của bạn có khớp với jenkins không. https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html . Bạn có thể thử chạy thử nghiệm trên máy cục bộ với các đối số tương tự như trên jenkins (từ thiết bị đầu cuối), nếu nó không thành công có nghĩa là sự cố nằm trong các đối số


Các tập tin org.mockito.plugins.MockMakercũng tồn tại trong máy jenkins. Tôi sử dụng cùng một JVM trong các máy bot. Tôi sẽ kiểm tra 3 bạn chỉ ra. Cảm ơn
Christos

Tôi đã thử chạy thử nghiệm thông qua bảng điều khiển, sử dụng lệnh được sử dụng trong Jenkins. Họ thất bại với thông báo lỗi chính xác tương tự. Vì vậy, một cái gì đó kỳ lạ xảy ra bên trong IntelliJ.
Christos

Hãy xem .idea / workspace.xml tại cấu hình chạy của bạn, nó nằm trong thẻ <thành phần>. Sau đó, bạn có thể tìm hiểu cách chuyển đổi xml đó thành lệnh bash
Link182 6/12/19

Bạn có thể hiển thị lệnh đầu cuối jenkins được sử dụng để chạy thử nghiệm không? Ngoài ra bạn có thể cho tôi biết bạn sử dụng trình quản lý gói nào?
Link182

Là một công cụ xây dựng, tôi sử dụng Gradle.
Christos
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.