Menguji metode Privat menggunakan mockito

104
kelas publik A {

    metode public void (boolean b) {
          jika (b == benar)
               metode1 ();
          lain
               metode2 ();
    }

    private void method1 () {}
    private void method2 () {}
}
public class TestA {

    @Uji
    public void testMethod () {
      A a = mock (A.class);
      a. metode (benar);
      // cara menguji seperti verifikasi (a) .method1 ();
    }
}

Bagaimana cara menguji metode privat dipanggil atau tidak, dan bagaimana cara menguji metode privat menggunakan mockito ???

Nageswaran
sumber

Jawaban:

81

Anda tidak dapat melakukannya dengan Mockito tetapi Anda dapat menggunakan Powermock untuk memperluas Mockito dan metode pribadi tiruan. Powermock mendukung Mockito. Ini contohnya.

shift66
sumber
19
Saya bingung dengan jawaban ini. Ini mengejek, Tapi judulnya menguji metode privat
diyoda_
Saya telah menggunakan Powermock untuk mengejek metode privat, tetapi bagaimana cara menguji metode privat dengan Powermock. Di mana, saya dapat memberikan beberapa masukan dan mengharapkan beberapa keluaran dari metode dan kemudian memverifikasi keluaran?
Rito
Anda tidak bisa. Anda mengejek output input, Anda tidak dapat menguji fungsionalitas sebenarnya.
Talha
131

Tidak mungkin melalui mockito. Dari wiki mereka

Mengapa Mockito tidak mengejek metode pribadi?

Pertama, kami tidak dogmatis tentang mengejek metode privat. Kami hanya tidak peduli dengan metode privat karena dari sudut pandang pengujian metode privat tidak ada. Berikut beberapa alasan Mockito tidak mengejek metode privat:

Ini membutuhkan peretasan pemuat kelas yang tidak pernah anti peluru dan itu mengubah api (Anda harus menggunakan runner pengujian khusus, memberi anotasi kelas, dll.).

Sangat mudah untuk menyiasatinya - cukup ubah visibilitas metode dari pribadi menjadi dilindungi paket (atau dilindungi).

Saya harus menghabiskan waktu untuk menerapkan & memeliharanya. Dan itu tidak masuk akal mengingat poin # 2 dan fakta bahwa itu sudah diterapkan di alat yang berbeda (powermock).

Akhirnya ... Mengejek metode privat adalah petunjuk bahwa ada yang salah dengan pemahaman OO. Di OO ​​Anda ingin objek (atau peran) berkolaborasi, bukan metode. Lupakan kode pascal & prosedural. Pikirkan benda.

Aravind Yarram
sumber
1
Ada asumsi fatal yang dibuat oleh pernyataan itu:> Mengolok-olok metode privat adalah petunjuk bahwa ada yang salah dengan pemahaman OO. Jika saya menguji metode publik, dan memanggil metode privat, saya ingin mengejek pengembalian metode privat. Dengan asumsi di atas menghilangkan kebutuhan untuk bahkan mengimplementasikan metode pribadi. Bagaimana pemahaman yang buruk tentang OO?
eggmatters
1
@eggmatters Menurut Baeldung "Teknik mengejek harus diterapkan pada dependensi eksternal kelas dan bukan ke kelas itu sendiri. Jika mengejek metode privat penting untuk menguji kelas kita, biasanya ini menunjukkan desain yang buruk." Berikut adalah utas keren tentang itu softwareengineering.stackexchange.com/questions/100959/…
Jason Glez
34

Berikut adalah contoh kecil bagaimana melakukannya dengan powermock

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

Untuk menguji metode1 gunakan kode:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

Untuk mengatur private object obj, gunakan ini:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);
Mindaugas Jaraminas
sumber
Tautan Anda hanya mengarah ke repo tiruan daya @Mindaugas
Xavier
@JAV_banget. Anda dapat menggunakannya jika Anda suka dalam proyek Anda.
Mindaugas Jaraminas
1
Hebat !!! dijelaskan dengan sangat baik dengan contoh sederhana ini hampir semuanya :) Karena tujuannya hanya untuk menguji kode dan bukan apa yang disediakan oleh semua kerangka :)
siddhusingh
Harap perbarui ini. Whitebox tidak lagi menjadi bagian dari API publik.
pengguna447607
17

Pikirkan tentang hal ini dalam kaitannya dengan perilaku, bukan dalam metode apa yang ada. Metode yang dipanggil methodmemiliki perilaku tertentu jika bbenar. Ini memiliki perilaku yang berbeda jika bsalah. Ini berarti Anda harus menulis dua tes berbeda untuk method; satu untuk setiap kasus. Jadi, alih-alih memiliki tiga pengujian berorientasi metode (satu untuk method, satu untuk method1, satu untuk method2, Anda memiliki dua pengujian berorientasi perilaku.

Terkait dengan ini (saya menyarankan ini di utas SO lain baru-baru ini, dan sebagai hasilnya disebut kata empat huruf, jadi jangan ragu untuk mengambil ini dengan sebutir garam); Saya merasa terbantu untuk memilih nama pengujian yang mencerminkan perilaku yang saya uji, daripada nama metode. Jadi jangan sebut tes Anda testMethod(), testMethod1(), testMethod2()dan sebagainya. Saya suka nama yang disukai calculatedPriceIsBasePricePlusTax()atau taxIsExcludedWhenExcludeIsTrue()yang menunjukkan perilaku apa yang saya uji; kemudian dalam setiap metode pengujian, uji hanya perilaku yang ditunjukkan. Kebanyakan perilaku tersebut hanya akan melibatkan satu panggilan ke metode publik, tetapi mungkin melibatkan banyak panggilan ke metode privat.

Semoga ini membantu.

Dawood ibn Kareem
sumber
13

Meskipun Mockito tidak menyediakan kemampuan itu, Anda dapat mencapai hasil yang sama menggunakan Mockito + kelas JUnit ReflectionUtils atau kelas Spring ReflectionTestUtils . Silakan lihat contoh di bawah ini yang diambil dari sini yang menjelaskan cara memanggil metode pribadi:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

Contoh lengkap dengan ReflectionTestUtils dan Mockito dapat ditemukan di buku Mockito for Spring

AR1
sumber
ReflectionTestUtils.invokeMethod (mahasiswa, "saveOrUpdate", "argument1", "argument2", "argument3"); Argumen terakhir dari invokeMethod, menggunakan Varg yang dapat mengambil banyak argumen yang perlu diteruskan ke metode privat. berhasil.
Tim
Jawaban ini seharusnya memiliki lebih banyak suara positif, sejauh ini merupakan cara termudah untuk menguji metode privat.
Max
9

Anda tidak seharusnya menguji metode privat. Hanya metode non-privat yang perlu diuji karena metode ini harus memanggil metode privat. Jika Anda "ingin" menguji metode privat, ini mungkin menunjukkan bahwa Anda perlu memikirkan kembali desain Anda:

Apakah saya menggunakan injeksi ketergantungan yang tepat? Apakah saya mungkin perlu memindahkan metode privat ke kelas terpisah dan lebih baik mengujinya? Haruskah metode ini bersifat pribadi? ... tidak bisakah mereka menjadi default atau dilindungi?

Dalam contoh di atas, dua metode yang disebut "secara acak" mungkin sebenarnya perlu ditempatkan di kelasnya sendiri, diuji, dan kemudian dimasukkan ke dalam kelas di atas.

Jaco Van Niekerk
sumber
26
Poin yang valid. Namun, bukankah alasan menggunakan pengubah privat untuk metode adalah karena Anda hanya ingin memotong kode yang terlalu panjang dan / atau berulang? Memisahkannya sebagai kelas lain seperti Anda mempromosikan baris kode tersebut menjadi warga negara kelas satu yang tidak akan digunakan kembali di tempat lain karena itu dimaksudkan secara khusus untuk mempartisi kode yang panjang dan untuk mencegah baris kode berulang. Jika Anda akan memisahkannya ke kelas lain, rasanya tidak benar; Anda akan dengan mudah mendapatkan ledakan kelas.
supertonsky
2
Tercatat supertonsky, saya mengacu pada kasus umum. Saya setuju bahwa dalam kasus di atas, seharusnya tidak berada di kelas yang terpisah. (+1 pada komentar Anda meskipun - itu adalah poin yang sangat valid yang Anda buat untuk mempromosikan anggota pribadi)
Jaco Van Niekerk
4
@ supertonsky, saya tidak dapat menemukan tanggapan yang memuaskan untuk masalah ini. Ada beberapa alasan mengapa saya mungkin menggunakan anggota pribadi dan sangat sering mereka tidak menunjukkan bau kode dan saya akan mendapat banyak manfaat dari pengujian mereka. Orang-orang tampaknya mengabaikan hal ini dengan mengatakan "jangan lakukan itu".
LuddyPants
3
Maaf, saya memilih untuk memberi suara negatif berdasarkan "Jika Anda 'ingin' menguji metode pribadi, ini mungkin menunjukkan Anda perlu memberi suara negatif pada desain Anda". Oke, cukup adil, tetapi salah satu alasan untuk menguji semuanya adalah karena, di bawah tenggat waktu ketika Anda tidak punya waktu untuk memikirkan ulang desain, Anda mencoba menerapkan perubahan yang perlu dilakukan pada metode pribadi. Dalam dunia yang ideal, bukankah metode privat itu perlu diubah karena desainnya akan sempurna? Tentu, tapi di dunia yang sempurna, tapi bisa diperdebatkan karena di dunia yang sempurna yang membutuhkan ujian, semuanya berfungsi begitu saja. :)
John Lockwood
2
@Tokopedia Poin diambil, suara negatif Anda dijamin (+1). Terima kasih atas komentarnya juga - Saya setuju dengan Anda tentang poin yang Anda buat. Dalam kasus seperti itu, saya dapat melihat salah satu dari dua opsi: Metode dibuat menjadi paket pribadi atau dilindungi dan pengujian unit ditulis seperti biasa; atau (dan ini isapan jempol dan praktik buruk) sebuah metode utama ditulis dengan cepat untuk memastikannya masih berhasil. Namun, tanggapan saya didasarkan pada skenario kode BARU sedang ditulis dan tidak refactoring ketika Anda mungkin tidak dapat merusak desain aslinya.
Jaco Van Niekerk
6

Saya dapat menguji metode pribadi di dalam menggunakan mockito menggunakan refleksi. Berikut adalah contohnya, mencoba menamainya sedemikian rupa sehingga masuk akal

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));
Abdullah Choudhury
sumber
6
  1. Dengan menggunakan refleksi, metode privat dapat dipanggil dari kelas pengujian. Pada kasus ini,

    // metode pengujian akan seperti ini ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. Jika metode privat memanggil metode privat lainnya, maka kita perlu memata-matai objek dan menghentikan metode lain. Kelas pengujian akan seperti ...

    // metode pengujian akan seperti ini ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

** Pendekatannya adalah menggabungkan refleksi dan memata-matai objek. ** metode1 dan ** metode2 adalah metode pribadi dan metode1 memanggil metode2.

Singh Arun
sumber
4

Saya tidak begitu mengerti kebutuhan Anda untuk menguji metode privat. Akar masalahnya adalah metode publik Anda memiliki void sebagai tipe kembalian, dan karenanya Anda tidak dapat menguji metode publik Anda. Karenanya Anda dipaksa untuk menguji metode pribadi Anda. Apakah tebakan saya benar ??

Beberapa solusi yang mungkin (AFAIK):

  1. Mengolok-olok metode pribadi Anda, tetapi tetap saja Anda tidak akan "benar-benar" menguji metode Anda.

  2. Verifikasi status objek yang digunakan dalam metode ini. KEBANYAKAN metode melakukan beberapa pemrosesan nilai input dan mengembalikan output, atau mengubah status objek. Menguji objek untuk keadaan yang diinginkan juga dapat digunakan.

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [Di sini Anda dapat menguji metode privat, dengan memeriksa perubahan status classObj dari nol menjadi bukan nol.]

  3. Ubah sedikit kode Anda (Semoga ini bukan kode lama). Funda saya menulis metode adalah, seseorang harus selalu mengembalikan sesuatu (a int / a boolean). Nilai yang dikembalikan MUNGKIN atau MUNGKIN TIDAK digunakan oleh implementasi, tetapi PASTI akan digunakan oleh pengujian

    kode.

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }
Reji
sumber
3

Sebenarnya ada cara untuk menguji metode dari anggota pribadi dengan Mockito. Katakanlah Anda memiliki kelas seperti ini:

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

Jika Anda ingin menguji a.methodakan memanggil metode dari SomeOtherClass, Anda dapat menulis sesuatu seperti di bawah ini.

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); akan menghentikan anggota pribadi dengan sesuatu yang dapat Anda mata-matai.

Fan Jin
sumber
2

Letakkan pengujian Anda dalam paket yang sama, tetapi folder sumber berbeda (src / main / java vs. src / test / java) dan jadikan metode tersebut sebagai paket pribadi. Kemampuan untuk diuji lebih penting daripada privasi.

Roland Schneider
sumber
6
Satu-satunya alasan yang sah adalah untuk menguji sebagian dari sistem warisan. Jika Anda mulai menguji metode private / package-private, Anda mengekspos objek Anda secara internal. Melakukannya biasanya menghasilkan kode refactorable yang buruk. Prefer komposisi sehingga Anda dapat mencapai testability, dengan semua keunggulan sistem berorientasi objek.
Brice
1
Setuju - itu akan menjadi cara yang disukai. Namun, jika Anda benar-benar ingin menguji metode privat dengan mockito, ini adalah satu-satunya opsi (aman jenis) yang Anda miliki. Jawaban saya agak terburu-buru, saya seharusnya menunjukkan risikonya, seperti Anda dan yang lain telah melakukannya.
Roland Schneider
Ini cara yang saya sukai. Tidak ada salahnya untuk mengekspos objek internal di package-private level; dan pengujian unit adalah pengujian kotak putih, Anda perlu mengetahui internal untuk pengujian.
Andrew Feng
0

Jika metode privat tidak kosong dan nilai yang dikembalikan digunakan sebagai parameter untuk metode dependensi eksternal, Anda dapat memalsukan dependensi dan menggunakan an ArgumentCaptoruntuk menangkap nilai yang dikembalikan. Sebagai contoh:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
seperti susu
sumber