Menguji Kelas Abstrak Dengan JUnit

1. Ikhtisar

Dalam tutorial ini, kita akan menganalisis berbagai kasus penggunaan dan kemungkinan solusi alternatif untuk pengujian unit kelas abstrak dengan metode non-abstrak.

Perhatikan bahwa menguji kelas abstrak harus hampir selalu melalui API publik dari implementasi konkret , jadi jangan terapkan teknik di bawah ini kecuali Anda yakin apa yang Anda lakukan.

2. Ketergantungan Maven

Mari kita mulai dengan dependensi Maven:

 org.junit.jupiter junit-jupiter-engine 5.1.0 test   org.mockito mockito-core 2.8.9 test   org.powermock powermock-module-junit4 1.7.4 test   junit junit     org.powermock powermock-api-mockito2 1.7.4 test 

Anda dapat menemukan versi terbaru dari perpustakaan ini di Maven Central.

Powermock tidak sepenuhnya didukung untuk Junit5. Selain itu, powermock-module-junit4 hanya digunakan untuk satu contoh yang disajikan di bagian 5.

3. Metode Non-Abstrak Independen

Mari pertimbangkan sebuah kasus ketika kita memiliki kelas abstrak dengan metode non-abstrak publik:

public abstract class AbstractIndependent { public abstract int abstractFunc(); public String defaultImpl() { return "DEFAULT-1"; } }

Kami ingin menguji metode defaultImpl () , dan kami memiliki dua solusi yang mungkin - menggunakan kelas konkret, atau menggunakan Mockito.

3.1. Menggunakan Kelas Beton

Buat kelas konkret yang memperluas kelas AbstractIndependent , dan gunakan untuk menguji metode:

public class ConcreteImpl extends AbstractIndependent { @Override public int abstractFunc() { return 4; } }
@Test public void givenNonAbstractMethod_whenConcreteImpl_testCorrectBehaviour() { ConcreteImpl conClass = new ConcreteImpl(); String actual = conClass.defaultImpl(); assertEquals("DEFAULT-1", actual); }

Kelemahan dari solusi ini adalah kebutuhan untuk membuat kelas konkret dengan implementasi dummy dari semua metode abstrak.

3.2. Menggunakan Mockito

Alternatifnya, kita dapat menggunakan Mockito untuk membuat tiruan:

@Test public void givenNonAbstractMethod_whenMockitoMock_testCorrectBehaviour() { AbstractIndependent absCls = Mockito.mock( AbstractIndependent.class, Mockito.CALLS_REAL_METHODS); assertEquals("DEFAULT-1", absCls.defaultImpl()); }

Bagian terpenting di sini adalah persiapan tiruan untuk menggunakan kode sebenarnya ketika metode dipanggil menggunakan Mockito.CALLS_REAL_METHODS .

4. Metode Abstrak Disebut Dari Metode Non Abstrak

Dalam kasus ini, metode non-abstrak mendefinisikan aliran eksekusi global, sedangkan metode abstrak dapat ditulis dengan cara yang berbeda bergantung pada kasus penggunaan:

public abstract class AbstractMethodCalling { public abstract String abstractFunc(); public String defaultImpl() { String res = abstractFunc(); return (res == null) ? "Default" : (res + " Default"); } }

Untuk menguji kode ini, kita dapat menggunakan dua pendekatan yang sama seperti sebelumnya - membuat kelas konkret atau menggunakan Mockito untuk membuat tiruan:

@Test public void givenDefaultImpl_whenMockAbstractFunc_thenExpectedBehaviour() { AbstractMethodCalling cls = Mockito.mock(AbstractMethodCalling.class); Mockito.when(cls.abstractFunc()) .thenReturn("Abstract"); Mockito.doCallRealMethod() .when(cls) .defaultImpl(); assertEquals("Abstract Default", cls.defaultImpl()); }

Di sini, abstractFunc () diberi stub dengan nilai kembalian yang kita sukai untuk pengujian. Ini berarti bahwa ketika kita memanggil metode non-abstrak defaultImpl () , ini akan menggunakan rintisan ini.

5. Metode Non Abstrak Dengan Uji Obstruksi

Dalam beberapa skenario, metode yang ingin kami uji memanggil metode privat yang berisi obstruksi pengujian.

Kita perlu melewati metode pengujian yang menghalangi sebelum menguji metode target:

public abstract class AbstractPrivateMethods { public abstract int abstractFunc(); public String defaultImpl() { return getCurrentDateTime() + "DEFAULT-1"; } private String getCurrentDateTime() { return LocalDateTime.now().toString(); } }

Dalam contoh ini, metode defaultImpl () memanggil metode pribadi getCurrentDateTime () . Metode pribadi ini mendapatkan waktu saat ini pada waktu proses, yang harus dihindari dalam pengujian unit kami.

Sekarang, untuk mengejek perilaku standar metode privat ini, kami bahkan tidak dapat menggunakan Mockito karena tidak dapat mengontrol metode privat.

Sebaliknya, kita perlu menggunakan PowerMock ( n ote bahwa contoh ini hanya bekerja dengan JUnit 4 karena dukungan untuk ketergantungan ini tidak tersedia untuk JUnit 5 ):

@RunWith(PowerMockRunner.class) @PrepareForTest(AbstractPrivateMethods.class) public class AbstractPrivateMethodsUnitTest { @Test public void whenMockPrivateMethod_thenVerifyBehaviour() { AbstractPrivateMethods mockClass = PowerMockito.mock(AbstractPrivateMethods.class); PowerMockito.doCallRealMethod() .when(mockClass) .defaultImpl(); String dateTime = LocalDateTime.now().toString(); PowerMockito.doReturn(dateTime).when(mockClass, "getCurrentDateTime"); String actual = mockClass.defaultImpl(); assertEquals(dateTime + "DEFAULT-1", actual); } }

Bit penting dalam contoh ini:

  • @RunWith mendefinisikan PowerMock sebagai pelari untuk pengujian
  • @PrepareForTest (class) memberi tahu PowerMock untuk mempersiapkan kelas untuk diproses nanti

Menariknya, kami meminta PowerMock untuk menghentikan metode pribadi getCurrentDateTime (). PowerMock akan menggunakan refleksi untuk menemukannya karena tidak dapat diakses dari luar.

Jadi , saat kita memanggil defaultImpl () , rintisan yang dibuat untuk metode privat akan dipanggil alih-alih metode sebenarnya.

6. Metode Non-Abstrak Yang Mengakses Bidang Instance

Kelas abstrak dapat memiliki status internal yang diimplementasikan dengan bidang kelas. Nilai bidang dapat berpengaruh signifikan pada metode yang diuji.

Jika suatu bidang bersifat publik atau dilindungi, kami dapat dengan mudah mengaksesnya dari metode pengujian.

Tetapi jika itu pribadi, kita harus menggunakan PowerMockito :

public abstract class AbstractInstanceFields { protected int count; private boolean active = false; public abstract int abstractFunc(); public String testFunc() { if (count > 5) { return "Overflow"; } return active ? "Added" : "Blocked"; } }

Here, the testFunc() method is using instance-level fields count and active before it returns.

When testing testFunc(), we can change the value of the count field by accessing instance created using Mockito.

On the other hand, to test the behavior with the private active field, we'll again have to use PowerMockito, and its Whitebox class:

@Test public void whenPowerMockitoAndActiveFieldTrue_thenCorrectBehaviour() { AbstractInstanceFields instClass = PowerMockito.mock(AbstractInstanceFields.class); PowerMockito.doCallRealMethod() .when(instClass) .testFunc(); Whitebox.setInternalState(instClass, "active", true); assertEquals("Added", instClass.testFunc()); }

We're creating a stub class using PowerMockito.mock(), and we're using Whitebox class to control object's internal state.

The value of the active field is changed to true.

7. Kesimpulan

Dalam tutorial ini, kami telah melihat banyak contoh yang mencakup banyak kasus penggunaan. Kita dapat menggunakan kelas abstrak dalam lebih banyak skenario tergantung pada desain yang diikuti.

Selain itu, menulis pengujian unit untuk metode kelas abstrak sama pentingnya dengan metode dan kelas normal. Kami dapat menguji masing-masing menggunakan teknik yang berbeda atau pustaka dukungan pengujian berbeda yang tersedia.

Kode sumber lengkap tersedia di GitHub.