Mockito mocks ชั้นสุดท้ายในประเทศ แต่ล้มเหลวในเจนกินส์


11

ฉันได้เขียนการทดสอบหน่วยสำหรับวิธีการคงที่ วิธีการคงที่ใช้เวลาเพียงหนึ่งข้อโต้แย้ง ประเภทของการโต้แย้งเป็นชั้นสุดท้าย ในแง่ของรหัส:

public class Utility {

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

public final class Customer {
   // class definition
}

ดังนั้นสำหรับUtilityชั้นเรียนฉันได้สร้างชั้นทดสอบUtilityTestsซึ่งฉันได้เขียนข้อสอบสำหรับวิธีนี้, getName. กรอบการทดสอบหน่วยเป็นTestNGMockitoและห้องสมุดเยาะเย้ยที่ใช้คือ ดังนั้นการทดสอบทั่วไปมีโครงสร้างดังต่อไปนี้:

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

อะไรคือปัญหา ?

ในขณะที่การทดสอบทำงานได้สำเร็จในเครื่องภายใน IntelliJ พวกเขาล้มเหลวในเจนกินส์ (เมื่อฉันกดรหัสของฉันในสาขาระยะไกลบิลด์จะถูกทริกเกอร์และการทดสอบหน่วยจะทำงานในตอนท้าย) ข้อความแสดงข้อผิดพลาดคือ sth ดังนี้:

org.mockito.exceptions.base.MockitoException: ไม่สามารถเยาะเย้ย / คลาสสอดแนม com.packagename.Customer Mockito ไม่สามารถเยาะเย้ย / สายลับเพราะ: - คลาสสุดท้าย

ฉันพยายามทำอะไร

ฉันค้นหาเล็กน้อยเพื่อหาทางออก แต่ฉันไม่ได้ทำมัน ฉันทราบที่นี่ว่าฉันไม่ได้รับอนุญาตให้เปลี่ยนความจริงที่ว่าCustomerเป็นชั้นสุดท้าย นอกจากนี้ฉันต้องการถ้าเป็นไปได้ที่จะไม่เปลี่ยนแปลงการออกแบบเลย (เช่นการสร้างอินเทอร์เฟซที่จะเก็บวิธีการที่ฉันต้องการเยาะเย้ยและระบุว่าระดับลูกค้าใช้อินเทอร์เฟซนั้นเป็นอย่างถูกต้อง แสดงความคิดเห็น) สิ่งที่ฉันพยายามเป็นตัวเลือกที่สองกล่าวถึงMockito สุดท้าย แม้จะมีข้อเท็จจริงที่ว่าสิ่งนี้แก้ไขปัญหาได้ แต่มันก็ทำการทดสอบหน่วยอื่น ๆ :( ซึ่งไม่สามารถแก้ไขได้ด้วยวิธีที่ชัดเจน

คำถาม

ดังนั้นนี่คือคำถามสองข้อที่ฉันมี:

  1. เป็นไปได้อย่างไรในตอนแรก? การทดสอบไม่ควรล้มเหลวทั้งในประเทศและในเจนกินส์หรือ
  2. วิธีนี้สามารถแก้ไขได้ตามข้อ จำกัด ที่ฉันกล่าวถึงข้างต้น

ขอบคุณล่วงหน้าสำหรับความช่วยเหลือใด ๆ


1
ฉันเดาว่าคงเป็นเพราะการenable finalกำหนดค่าทำงานในพื้นที่ทำงานของคุณ แต่เมื่อทำงานบนJenkinsมันไม่สามารถหาไฟล์นี้ ตรวจสอบว่าJenkinsกำลังค้นหาไฟล์อยู่ที่ไหนและอยู่ที่นั่นจริงหรือไม่
ที่สอง

เธรดอื่นนี้อธิบายวิธีการเปิดใช้งานการจำลองระดับสุดท้ายใน Mockito 2 โดยการเพิ่มไฟล์การกำหนดค่า mockito ภายใต้ไดเรกทอรีทรัพยากร: stackoverflow.com/questions/14292863/ …
Jose Tepedino

3
เป็นไปได้ไหมในรหัสที่คุณติดต่อเพื่อแยกอินเทอร์เฟซจากคลาสลูกค้าพูดว่า ICustomer และใช้ในคลาสยูทิลิตี้ จากนั้นคุณสามารถเยาะเย้ยอินเทอร์เฟซนั้นแทนคลาสสุดท้ายอย่างเป็นรูปธรรม
Jose Tepedino

@JoseTepedino นี่คือจุดที่ถูกต้อง มันสมเหตุสมผลอย่างสมบูรณ์และเป็นวิธีที่ยอดเยี่ยมในการเอาชนะปัญหานี้ อย่างไรก็ตามฉันสงสัยว่ามีวิธีอื่นและที่สำคัญกว่านั้นฉันต้องการที่จะเข้าใจว่าทำไมวิธีการในปัจจุบันประสบความสำเร็จในพื้นที่และล้มเหลวในเจนกินส์
Christos

1
ไม่Customerมีเหตุผลใด ๆ ในเรื่องนี้หรือมันเป็นเพียงข้อมูลชั้นใบ้? หากเป็นเพียงกลุ่มของทุ่งหญ้าที่มี getters และ setters คุณสามารถสร้างอินสแตนซ์
Willis Blackburn

คำตอบ:


2

อีกวิธีคือการใช้รูปแบบ 'method to class'

  1. ย้ายเมธอดออกจากคลาสลูกค้าไปยังคลาส / คลาสอื่นกล่าวว่า CustomerSomething เช่น / CustomerFinances (หรืออะไรก็ตามที่เป็นความรับผิดชอบ)
  2. เพิ่มตัวสร้างให้กับลูกค้า
  3. ตอนนี้คุณไม่จำเป็นต้องล้อเลียนลูกค้าแค่คลาส CustomerSomething คุณอาจไม่จำเป็นต้องเยาะเย้ยเช่นนั้นหากไม่มีการพึ่งพาภายนอก

นี่คือบล็อกที่ดีในหัวข้อ: https://simpleprogrammer.com/back-to-basics-mock-eliminating-patterns/


1
ขอบคุณสำหรับคำตอบของคุณ (+1) ฉันพบวิธีแก้ไข (ตอบคำถามที่สอง) อย่างไรก็ตามเหตุผลที่การทดสอบล้มเหลวภายใน IntelliJ ยังไม่ชัดเจนสำหรับฉัน นอกจากนี้ฉันไม่สามารถทำซ้ำได้อีก (ความล้มเหลวภายใน IntelliJ) ซึ่งแปลกมาก
Christos

1

เป็นไปได้อย่างไรในตอนแรก? การทดสอบไม่ควรล้มเหลวทั้งในประเทศและในเจนกินส์หรือ

เห็นได้ชัดว่ามันเป็นลักษณะเฉพาะของ env คำถามเดียวคือ - วิธีการตรวจสอบสาเหตุของความแตกต่าง

ฉันขอแนะนำให้คุณตรวจสอบorg.mockito.internal.util.MockUtil#typeMockabilityOfวิธีการและเปรียบเทียบสิ่งที่mockMakerใช้จริงในสภาพแวดล้อมทั้งสองและทำไม

ถ้าmockMakerเหมือนกัน - เปรียบเทียบคลาสที่โหลดIDE-ClientกับJenkins-Client- พวกเขามีความแตกต่างในช่วงเวลาของการดำเนินการทดสอบหรือไม่

วิธีนี้สามารถแก้ไขได้ตามข้อ จำกัด ที่ฉันกล่าวถึงข้างต้น

รหัสต่อไปนี้เขียนขึ้นโดยใช้สมมติฐานของ OpenJDK 12 และ Mockito 2.28.2 แต่ฉันเชื่อว่าคุณสามารถปรับให้เป็นรุ่นที่ใช้งานจริงได้

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

ด้วยกฎแยกต่างหากสำหรับ mocks แบบอินไลน์:

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

ขอบคุณสำหรับคำตอบของคุณ (+1) ฉันพบวิธีแก้ไข (ตอบคำถามที่สอง) อย่างไรก็ตามเหตุผลที่การทดสอบล้มเหลวภายใน IntelliJ ยังไม่ชัดเจนสำหรับฉัน นอกจากนี้ฉันไม่สามารถทำซ้ำได้อีก (ความล้มเหลวภายใน IntelliJ) ซึ่งแปลกมาก
Christos

1

ตรวจสอบให้แน่ใจว่าคุณทำการทดสอบด้วยอาร์กิวเมนต์เดียวกัน ตรวจสอบว่าการกำหนดค่าการรันภายในระบบของคุณตรงกับเจนกินส์หรือไม่ https://www.jetbrains.com/help/idea/creating-and-editing-run-debug-configurations.html คุณสามารถลองเรียกใช้การทดสอบบนเครื่องโลคัลที่มีอาร์กิวเมนต์เดียวกันกับบน jenkins (จากเทอร์มินัล) ถ้ามันจะล้มเหลวนั่นหมายความว่าปัญหานั้นอยู่ในข้อโต้แย้ง


ไฟล์org.mockito.plugins.MockMakerนี้มีอยู่ในเครื่อง jenkins ฉันใช้ JVM เดียวกันในเครื่องบอท ฉันจะตรวจสอบ 3 คุณชี้ให้เห็น ขอบคุณ
Christos

ฉันพยายามเรียกใช้การทดสอบผ่านคอนโซลโดยใช้คำสั่งที่ใช้ในเจนกินส์ พวกเขาล้มเหลวด้วยข้อความผิดพลาดที่แน่นอนเหมือนกัน ดังนั้นมีบางอย่างแปลก ๆ เกิดขึ้นภายใน IntelliJ
Christos

ดู. .idea / workspace.xml ที่การกำหนดค่าการรันของคุณซึ่งอยู่ในแท็ก <component> หลังจากนั้นคุณสามารถเรียนรู้วิธีการแปลง xml นั้นเป็นคำสั่ง bash
Link182

คุณสามารถแสดงคำสั่ง jenkins terminal ที่ใช้ในการทดสอบได้หรือไม่? นอกจากนี้คุณสามารถบอกฉันว่าคุณใช้โปรแกรมจัดการแพคเกจไหน?
Link182

เป็นเครื่องมือสร้างฉันใช้ Gradle
Christos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.