Apa perbedaan antara mengejek dan memata-matai saat menggunakan Mockito?

142

Bagaimana kasus penggunaan mata-mata Mockito?

Menurut saya, setiap kasus penggunaan mata-mata dapat ditangani dengan tiruan, menggunakan callRealMethod.

Satu perbedaan yang dapat saya lihat adalah jika Anda ingin sebagian besar pemanggilan metode menjadi nyata, ini menghemat beberapa baris kode untuk menggunakan tiruan vs. mata-mata. Apakah itu atau apakah saya kehilangan gambaran yang lebih besar?

Victor Grazi
sumber

Jawaban:

103

Jawabannya ada di dokumentasi :

Parsial nyata (Sejak 1.8.0)

Akhirnya, setelah banyak debat & diskusi internal di milis, dukungan tiruan parsial ditambahkan ke Mockito. Sebelumnya kami menganggap olok-olok parsial sebagai bau kode. Namun, kami menemukan kasus penggunaan yang sah untuk tiruan sebagian.

Sebelum rilis 1.8 spy () tidak menghasilkan tiruan parsial yang nyata dan itu membingungkan bagi beberapa pengguna. Baca lebih lanjut tentang mata-mata: di sini atau di javadoc untuk metode mata-mata (Objek).

callRealMethod()diperkenalkan setelahnya spy(), tetapi spy () ditinggalkan di sana tentu saja, untuk memastikan kompatibilitas ke belakang.

Jika tidak, Anda benar: semua metode mata-mata adalah nyata kecuali jika dihentikan. Semua metode tiruan dihentikan kecuali callRealMethod()dipanggil. Secara umum, saya lebih suka menggunakan callRealMethod(), karena tidak memaksa saya menggunakan doXxx().when()idiom daripada tradisionalwhen().thenXxx()

JB Nizet
sumber
Masalah dengan lebih memilih mata-mata tiruan daripada mata-mata dalam kasus ini, adalah ketika kelas menggunakan anggota yang tidak diinjeksi ke dalamnya (tetapi diinisialisasi secara lokal), dan kemudian digunakan oleh metode "nyata"; dalam tiruan, anggota akan diinisialisasi ke nilai Java defaultnya, yang mungkin menyebabkan perilaku yang salah atau bahkan NullPointerException. Cara untuk meneruskan ini adalah dengan menambahkan metode "init" dan kemudian "benar-benar" memanggilnya, tetapi itu tampaknya sedikit berlebihan bagi saya.
Eyal Roth
Dari dokumen: "mata-mata harus digunakan dengan hati-hati dan sesekali, misalnya saat berurusan dengan kode lama." Ruang pengujian unit mengalami terlalu banyak cara untuk melakukan hal yang sama.
gdbj
95

Perbedaan antara Spy dan Mock

Saat Mockito membuat tiruan - ia melakukannya dari Class of a Type, bukan dari instance sebenarnya. Tiruan itu hanya membuat contoh shell tanpa tulang dari Kelas, yang sepenuhnya diinstrumentasi untuk melacak interaksi dengannya. Di sisi lain, mata-mata akan membungkus contoh yang ada. Ini akan tetap berperilaku dengan cara yang sama seperti contoh normal - satu-satunya perbedaan adalah bahwa itu juga akan diinstrumentasi untuk melacak semua interaksi dengannya.

Dalam contoh berikut - kami membuat tiruan kelas ArrayList:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

Seperti yang Anda lihat - menambahkan elemen ke dalam daftar tiruan sebenarnya tidak menambahkan apa-apa - itu hanya memanggil metode tanpa efek samping lain. Di sisi lain, mata-mata akan berperilaku berbeda - ia akan memanggil implementasi sebenarnya dari metode add dan menambahkan elemen ke daftar yang mendasarinya:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Di sini kita pasti dapat mengatakan bahwa metode internal sebenarnya dari objek dipanggil karena ketika Anda memanggil metode size () Anda mendapatkan ukuran sebagai 1, tetapi metode size () ini tidak diejek! Jadi darimana saya berasal? Metode internal real size () disebut size () tidak diejek (atau distub) dan karenanya kita dapat mengatakan bahwa entri telah ditambahkan ke objek nyata.

Sumber: http://www.baeldung.com/mockito-spy + catatan diri.

Saurabh Patil
sumber
1
Bukankah maksudmu size () mengembalikan 1?
Hitam
Dalam contoh pertama, mengapa mockedList.size()kembali 0jika metode itu belum dimatikan juga? Apakah itu hanya nilai default yang diberikan tipe kembalian dari metode ini?
mike
@mike: mockedList.size()mengembalikan intnilai default dan intadalah 0 di Java. Jika Anda mencoba mengeksekusi assertEquals(0, mockedList.size());setelahnya mockedList.clear();, hasilnya tetap sama.
realPK
2
Jawaban ini ditulis dengan baik dan sederhana dan membantu saya untuk akhirnya memahami perbedaan antara tiruan dan mata-mata. Bagus.
PesaThe
38

Jika ada objek dengan 8 metode dan Anda memiliki pengujian di mana Anda ingin memanggil 7 metode nyata dan metode rintisan satu Anda memiliki dua opsi:

  1. Menggunakan tiruan Anda harus menyiapkannya dengan memanggil 7 callRealMethod dan metode stub satu
  2. Menggunakan spyAnda harus mengaturnya dengan mematikan satu metode

The dokumentasi resmi pada doCallRealMethodmerekomendasikan menggunakan mata-mata untuk mengolok-olok parsial.

Lihat juga mata-mata javadoc (Object) untuk mengetahui lebih banyak tentang tiruan parsial. Mockito.spy () adalah cara yang disarankan untuk membuat tiruan parsial. Alasannya adalah itu menjamin metode nyata dipanggil terhadap objek yang dibangun dengan benar karena Anda bertanggung jawab untuk membuat objek yang diteruskan ke metode spy ().

pengguna2412398
sumber
7

Spy dapat berguna ketika Anda ingin membuat tes unit untuk kode lama .

Saya telah membuat contoh yang dapat dijalankan di sini https://www.surasint.com/mockito-with-spy/ , saya menyalin sebagian di sini.

Jika Anda memiliki sesuatu seperti kode ini:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Anda mungkin tidak perlu memata-matai karena Anda hanya dapat meniru DepositMoneyService dan WithdrawMoneyService.

Tetapi dengan beberapa, kode lama, ketergantungan ada dalam kode seperti ini:

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Ya, Anda dapat mengubah ke kode pertama tetapi kemudian API diubah. Jika metode ini digunakan di banyak tempat, Anda harus mengubah semuanya.

Alternatifnya adalah Anda dapat mengekstrak dependensi seperti ini:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Kemudian Anda dapat menggunakan mata-mata untuk menyuntikkan ketergantungan seperti ini:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

Lebih detail ada di tautan di atas.

Surasin Tancharoen
sumber
4

[Uji tipe ganda]

Mock vs. Spy

Mockadalah objek ganda yang telanjang. Objek ini memiliki tanda tangan metode yang sama tetapi realisasinya kosong dan mengembalikan nilai default - 0 dan null

Spyadalah objek ganda hasil kloning. Objek baru diklon berdasarkan objek nyata tetapi Anda memiliki kemungkinan untuk mengejeknya

class A {
    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() { foo4(); }

    void foo4() { }
}
@Test
public void testMockA() {
    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}
yoAlex5
sumber