Menggunakan JMockit untuk mengembalikan instance aktual dari konstruktor tiruan

Saya telah melihat pertanyaan berikut dan itu tidak sama dengan pertanyaan saya:

jMockit: Bagaimana cara mengharapkan panggilan konstruktor ke objek yang diolok-olok?

Pertanyaan ini serupa tetapi jawabannya tidak membantu saya:

Cara meniru konstruktor default Kelas kencan dengan JMockit?

Apa yang saya coba lakukan adalah meniru panggilan konstruktor ke java.util.zip.ZipFile, khususnya yang memiliki argumen java.io.File. Saya ingin konstruktor mengembalikan instance ZipFile yang berbeda, yang akan saya contohkan dengan konstruktor yang hanya menggunakan argumen String.

Panggilan konstruktor ini terjadi di dalam metode yang sedang diuji, jadi saya tidak bisa memasukkan ZipFile yang saya inginkan sebagai parameter.

Misalnya, kodenya terlihat seperti ini:

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
}

Pikiran pertama saya adalah melakukan hal berikut dengan ejekan parsial statis:

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

Tapi ini tidak akan berfungsi seperti yang ditunjukkan oleh baris ini dalam tutorial: constructors have void return type, so it makes no sense to record return values for them

Pikiran kedua saya adalah mencoba yang berikut ini:

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

Tapi ini menimbulkan hal berikut ketika mencoba menginisialisasi file:

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)

Pikiran ketiga saya adalah menggunakan @MockClass seperti di bawah ini:

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

Tapi ini menghilangkan beberapa tiruan lain yang saya miliki yang memuat file konfigurasi untuk bagian berbeda dari kelas pengujian saya. Belum lagi saya ingin file zip yang berbeda untuk kasus uji yang berbeda.

Saya kira saya bisa mengejek apa saja yang akan dilakukan ZipFile, tapi ini akan dengan cepat menjadi masalah besar karena disebut di banyak tempat, outputnya perlu diolok-olok, dll, dll. Refactoring untuk mencoba menjadikannya dapat diakses akan terasa canggung, karena kode yang menggunakan ZipFile bersifat internal pada kode tersebut dan metode publik tidak terlalu mempedulikannya.

Saya merasa ada cara bagi JMockit untuk mengizinkan ini (memberikan contoh tertentu dari suatu objek ketika konstruktor dipanggil), tetapi saya tidak dapat menemukannya. Apakah ada yang punya ide?

EDIT: Saya mencoba metode yang disarankan oleh @Rogerio, tetapi saya mendapat kesalahan baru. Inilah pengaturan saya:

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

tapi saya mendapatkan jejak tumpukan berikut:

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)

dimana baris 61 adalah baris new NonStrictExpectations() {.

Saya benar-benar ingin mengatakan "daripada mengejek objek ini, gantikan objek lain yang bertipe sama". Mungkin saya telah mengungkapkannya dengan buruk.

EDIT2: Saya pikir saya harus memasukkan nomor versi: Menggunakan Eclipse 3.6.1 Java 1.6.0_26 JMockit 0.999.10


person Kane    schedule 05.12.2011    source sumber


Jawaban (2)


JMockit dapat meniru kelas ZipFile, tetapi mengganggu pemuatan kelas karena subkelas JarFile digunakan oleh JVM sepanjang waktu (setiap kali ia memuat kelas dari file jar di jalur kelas). Saat ini, tidak ada cara mudah untuk menghindari gangguan ini (ada rencana untuk "memperbaikinya", namun akan memakan waktu).

Namun, kasus uji khusus ini tidak terlalu cocok untuk alat tiruan. Sebagai gantinya, saya akan merekomendasikan pengaturan pengujian sehingga menyediakan file zip aktual dengan konten yang diinginkan di tempat yang tepat.

(suntingan lain) Saya baru saja menerapkan perubahan pada JMockit (untuk rilis 0.999.12) yang memungkinkan pengujian berikut lulus, asalkan ada file test.zip di direktori yang berfungsi, dan berisi file teks yang baris pertamanya adalah "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);
}

Namun, saya tetap menyarankan tidak menggunakan API tiruan untuk kasus seperti ini. Sebagai gantinya, gunakan file asli.

person Rogério    schedule 06.12.2011
comment
Sayangnya, saya mendapat kesalahan saat mencoba metode itu. Saya telah memperbarui pertanyaan saya dengan kode dan pelacakan tumpukan. - person Kane; 06.12.2011
comment
Anda benar, itu tidak berhasil karena gangguan pemuatan kelas. Saya menulis ulang jawaban saya dan menyarankan solusi yang berbeda, tanpa mengejek. - person Rogério; 06.12.2011
comment
Rekomendasi Anda sesuai dengan pertanyaannya! Saya ingin memberikan file zip yang sebenarnya, saya hanya ingin memaksa konstruktor untuk memberikan akses ke file pengujian (yang mungkin berbeda untuk kasus pengujian yang berbeda). Seperti yang saya catat dalam pertanyaan, ini adalah logika internal dan memfaktorkannya ulang akan berantakan. Saya kira saya bisa mengekstrak setengah logika internal ke dalam kelas terpisah, tapi itu terasa berantakan. Saya juga bisa membuat metode untuk mengambil file dan mengembalikan file zip, tetapi rasanya mundur jika mengubah satu baris menjadi metode hanya untuk pengujian. Mungkin desain saya miring. - person Kane; 06.12.2011
comment
Saya kira inti masalahnya adalah, jika Anda memiliki Objek dengan cakupan lokal, dan Anda ingin mengganti objek itu dengan objek lain, dapatkah itu dilakukan dengan mengejek? - person Kane; 06.12.2011
comment
Terima kasih atas pembaruannya. Saya akhirnya membuat metode yang merangkum konstruktor, dan mengejeknya sehingga saya dapat mengembalikan file sebenarnya seperti yang Anda sarankan. - person Kane; 08.12.2011

Ini mungkin tidak akan membantu Anda, tetapi jika Anda menggunakan Mockito atau EasyMock, Anda dapat menambahkan PowerMock, yang memungkinkan Anda meniru konstruksi objek baru dalam kode yang sedang diuji.

person David M. Karr    schedule 06.12.2011
comment
Tidak, itu tidak membantu saya. Terima kasih. (Alasannya adalah karena kerangka kerja tersebut mengganggu alat cakupan kode kami, sehingga kami tidak akan dapat menggunakannya. Bukan berarti saya ingin mengalihkan seluruh kerangka pengujian kami hanya untuk satu masalah yang saya alami.) - person Kane; 06.12.2011