การใช้ JMockit เพื่อส่งคืนอินสแตนซ์จริงจากตัวสร้างที่จำลอง

ฉันได้ดูคำถามต่อไปนี้แล้ว และมันไม่เหมือนกับคำถามของฉัน:

jMockit: จะคาดหวังการเรียก Constructor ไปยังวัตถุ Mocked ได้อย่างไร

คำถามนี้คล้ายกัน แต่คำตอบไม่เป็นประโยชน์สำหรับฉัน:

วิธีจำลอง Constructor เริ่มต้นของ คลาสเดทกับ JMockit?

สิ่งที่ฉันพยายามทำคือจำลองการเรียกคอนสตรัคเตอร์ไปที่ java.util.zip.ZipFile โดยเฉพาะอันที่มีอาร์กิวเมนต์ java.io.File ฉันต้องการให้ Constructor ส่งคืนอินสแตนซ์ของ ZipFile อื่น ซึ่งฉันจะยกตัวอย่างด้วย Constructor ที่รับเฉพาะอาร์กิวเมนต์ String เท่านั้น

การเรียกคอนสตรัคเตอร์นี้เกิดขึ้นภายในวิธีการภายใต้การทดสอบ ดังนั้นฉันจึงไม่สามารถฉีด ZipFile ที่ฉันต้องการเป็นพารามิเตอร์ได้

ตัวอย่างเช่น โค้ดจะมีลักษณะดังนี้:

public void whatever() {
   //some code
   //some more code
   foo();
   //yet more unrelated code
}

private Blah foo() {
    ZipFile zf;
    //a bunch of code we don't care about

    zf = new ZipFile(someFile);// I want to give it a known zipfile! mock this!


    // some more code we don't care about

    Enumeration<?> entries = zf.entries();
    ZipEntry entry = (ZipEntry) entries.nextElement();
    InputStream is = zf.getInputStream(entry)
    //maybe some other calls to the ZipFile

    // do something else
}

ความคิดแรกของฉันคือทำสิ่งต่อไปนี้ด้วยการเยาะเย้ยบางส่วนแบบคงที่:

final ZipFile test = new ZipFile("path/to/actual.zip");
new NonStrictExpectations() {
    @Mocked("(java.io.File)")
    ZipFile zf;
    {
        new ZipFile((File) any); result = test;
    }
};

แต่สิ่งนี้จะไม่ทำงานตามที่ระบุในบรรทัดนี้ในบทช่วยสอน: constructors have void return type, so it makes no sense to record return values for them

ความคิดที่สองของฉันคือลองทำสิ่งต่อไปนี้:

new NonStrictExpectations() {
    {
        newInstance("java.util.zip.ZipFile", new File("path/to/actual.zip"));
    }
};

แต่สิ่งนี้จะส่งสิ่งต่อไปนี้เมื่อพยายามเตรียมใช้งานไฟล์:

java.util.zip.ZipException: error in opening zip file
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(Unknown Source)
at java.util.zip.ZipFile.<init>(Unknown Source)

ความคิดที่สามของฉันคือการใช้ @MockClass ดังต่อไปนี้:

@Before
public void setUp() throws Exception {
    Mockit.setUpMocks(MockedZipFile.class);
}
@After
public void tearDown() {
    Mockit.tearDownMocks();
}

@MockClass(realClass=ZipFile.class)
public static class MockedZipFile {
    public ZipFile it;
    @Mock
    public void $init(File f) throws ZipException, IOException {
        it = new ZipFile("path/to/actual.zip");//this is what would be called
    }
}

แต่นี่เป็นการเยาะเย้ยอื่น ๆ ที่ฉันมีซึ่งโหลดไฟล์การกำหนดค่าสำหรับส่วนอื่นของคลาสทดสอบของฉัน ไม่ต้องพูดถึงฉันต้องการไฟล์ zip ที่แตกต่างกันสำหรับกรณีทดสอบที่แตกต่างกัน

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

ฉันรู้สึกว่ามีวิธีที่ JMockit อนุญาต (ให้อินสแตนซ์เฉพาะของวัตถุเมื่อมีการเรียกตัวสร้าง) แต่ฉันไม่สามารถเข้าใจได้ ไม่มีใครมีความคิดใด ๆ ?

แก้ไข: ฉันลองวิธีที่ @Rogerio แนะนำ แต่ฉันมีข้อผิดพลาดใหม่ นี่คือการตั้งค่าของฉัน:

final ZipFile test = new ZipFile("path/to/actual.zip");
new NonStrictExpectations() {
    ZipFile zf;
    {
        zf.entries();
        result = test.entries();
        zf.getInputStream((ZipEntry) any);
        result = new Delegate() {
            InputStream getInputStream(ZipEntry entry) throws IOException {                 
                return test.getInputStream(entry);
            }
        };
    }
};

แต่ฉันได้รับการติดตามสแต็กต่อไปนี้:

java.lang.InternalError
at path.to.test.ExtractDataTest$1.<init>(ExtractDataTest.java:61)
at path.to.test.ExtractDataTest.setUp(ExtractDataTest.java:61)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

โดยที่บรรทัด 61 คือบรรทัด new NonStrictExpectations() {

ฉันอยากจะพูดว่า "แทนที่จะล้อเลียนวัตถุนี้ ให้แทนที่วัตถุประเภทเดียวกันนี้แทน" บางทีฉันอาจแสดงออกได้ไม่ดี

แก้ไข 2: ฉันคิดว่าฉันควรรวมหมายเลขเวอร์ชัน: การใช้ Eclipse 3.6.1 Java 1.6.0_26 JMockit 0.999.10


person Kane    schedule 05.12.2011    source แหล่งที่มา


คำตอบ (2)


JMockit สามารถจำลองคลาส ZipFile ได้ แต่จะรบกวนการโหลดคลาสเนื่องจากคลาสย่อย JarFile ถูกใช้โดย JVM ตลอดเวลา (เมื่อใดก็ตามที่โหลดคลาสจากไฟล์ jar ใน classpath) ปัจจุบันยังไม่มีวิธีง่ายๆ ที่จะหลีกเลี่ยงการรบกวนนี้ (มีแผนจะ "แก้ไข" เรื่องนี้ แต่คงต้องใช้เวลา)

อย่างไรก็ตาม กรณีทดสอบนี้ไม่เหมาะกับเครื่องมือเยาะเย้ยมากนัก ฉันขอแนะนำให้ตั้งค่าการทดสอบเพื่อให้มีไฟล์ zip จริงพร้อมเนื้อหาที่ต้องการในตำแหน่งที่เหมาะสม

(แก้ไขอีกครั้ง) ฉันเพิ่งใช้การเปลี่ยนแปลงกับ JMockit (สำหรับรุ่น 0.999.12) ซึ่งอนุญาตให้การทดสอบต่อไปนี้ผ่านการทดสอบ โดยมีเงื่อนไขว่ามีไฟล์ test.zip ใน dir การทำงาน และประกอบด้วยไฟล์ข้อความที่มีบรรทัดแรกคือ "test" : :

@Test
public void mockZipFile() throws Exception
{
    final ZipFile testZip = new ZipFile("test.zip");

    new NonStrictExpectations() {
        @Capturing @Injectable ZipFile mock;

        {
            mock.entries(); result = testZip.entries();

            mock.getInputStream((ZipEntry) any);
            result = new Delegate() {
                InputStream delegate(ZipEntry e) throws IOException {
                    return testZip.getInputStream(e);
                }
            };
        }
    };

    ZipFile zf = new ZipFile("non-existing");
    ZipEntry firstEntry = zf.entries().nextElement();
    InputStream content = zf.getInputStream(firstEntry);
    String textContent = new BufferedReader(new InputStreamReader(content)).readLine();

    assertEquals("test", textContent);
}

อย่างไรก็ตาม ฉันยังคงแนะนำว่า ไม่ ใช้ API ที่เยาะเย้ยในกรณีเช่นนี้ ให้ใช้ไฟล์จริงแทน

person Rogério    schedule 06.12.2011
comment
ขออภัย ฉันได้รับข้อผิดพลาดในการลองใช้วิธีนั้น ฉันได้อัปเดตคำถามด้วยโค้ดและ stacktrace แล้ว - person Kane; 06.12.2011
comment
คุณพูดถูก มันใช้งานไม่ได้เนื่องจากการรบกวนการโหลดคลาส ฉันเขียนคำตอบใหม่และแนะนำวิธีแก้ไขปัญหาอื่นโดยไม่มีการล้อเลียน - person Rogério; 06.12.2011
comment
ข้อเสนอแนะของคุณคือสิ่งที่คำถามเกี่ยวกับ! ฉันต้องการจัดเตรียมไฟล์ zip จริง ฉันแค่ต้องการบังคับให้ Constructor ให้สิทธิ์เข้าถึงไฟล์ทดสอบ (ซึ่งอาจแตกต่างกันไปสำหรับกรณีทดสอบที่แตกต่างกัน) ดังที่ฉันได้กล่าวไว้ในคำถาม นี่เป็นตรรกะภายใน และการปรับโครงสร้างใหม่อาจเป็นเรื่องยุ่งเหยิง ฉันคิดว่าฉันสามารถแยกตรรกะภายในครึ่งหนึ่งออกเป็นคลาสที่แยกจากกันได้ แต่นั่นให้ความรู้สึกเละเทะ ฉันยังสามารถสร้างวิธีการในการรับไฟล์และส่งคืนไฟล์ zip ได้ แต่รู้สึกถอยหลังเมื่อเปลี่ยน one-liner ให้เป็นวิธีการสำหรับการทดสอบเท่านั้น บางทีการออกแบบของฉันอาจจะเกะกะ - person Kane; 06.12.2011
comment
ฉันคิดว่าหัวใจของปัญหาคือ หากคุณมี Object ที่กำหนดขอบเขตในเครื่อง และคุณต้องการแทนที่ Object นั้นด้วย Object อื่น สามารถทำได้ด้วยการเยาะเย้ยหรือไม่ - person Kane; 06.12.2011
comment
ขอบคุณสำหรับการอัพเดท. ฉันลงเอยด้วยการสร้างวิธีการที่ห่อหุ้ม Constructor และเยาะเย้ยสิ่งนั้นเพื่อที่ฉันจะได้ส่งคืนไฟล์จริงตามที่คุณแนะนำ - person Kane; 08.12.2011

สิ่งนี้อาจจะไม่ช่วยคุณ แต่ถ้าคุณใช้ Mockito หรือ EasyMock คุณสามารถเพิ่ม PowerMock ซึ่งช่วยให้คุณสามารถจำลองการสร้างวัตถุใหม่ในโค้ดของคุณภายใต้การทดสอบ

person David M. Karr    schedule 06.12.2011
comment
ไม่มันไม่ได้ช่วยฉัน ขอบคุณนะ (เหตุผลก็คือไม่ใช่ว่าเฟรมเวิร์กเหล่านั้นรบกวนเครื่องมือการครอบคลุมโค้ดของเรา ดังนั้นเราจึงไม่สามารถใช้งานได้ ไม่ใช่ว่าฉันต้องการเปลี่ยนเฟรมเวิร์กการทดสอบทั้งหมดของเราเพียงเพื่อปัญหาเดียวที่ฉันมี) - person Kane; 06.12.2011