Menguji Kelas Abstrak Dengan JUnit

1. Gambaran keseluruhan

Dalam tutorial ini, kami akan menganalisis pelbagai kes penggunaan dan kemungkinan penyelesaian alternatif untuk pengujian unit kelas abstrak dengan kaedah bukan abstrak.

Perhatikan bahawa menguji kelas abstrak hampir selalu melalui API awam pelaksanaan konkrit , jadi jangan gunakan teknik di bawah ini kecuali anda yakin apa yang anda lakukan.

2. Pergantungan Maven

Mari mulakan dengan pergantungan 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 boleh mendapatkan versi terbaru perpustakaan ini di Maven Central.

Powermock tidak disokong sepenuhnya untuk Junit5. Juga, powermock-modul-junit4 hanya digunakan untuk satu contoh yang dipersembahkan dalam seksyen 5.

3. Kaedah Tidak Abstrak Bebas

Mari pertimbangkan kes apabila kita mempunyai kelas abstrak dengan kaedah bukan abstrak umum:

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

Kami ingin menguji kaedah defaultImpl () , dan kami mempunyai dua kemungkinan penyelesaian - menggunakan kelas konkrit, atau menggunakan Mockito.

3.1. Menggunakan Kelas Konkrit

Buat kelas konkrit yang meluaskan kelas AbstractIndependent , dan gunakannya untuk menguji kaedah:

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 penyelesaian ini adalah keperluan untuk mewujudkan kelas konkrit dengan pelaksanaan palsu dari semua kaedah abstrak.

3.2. Menggunakan Mockito

Sebagai alternatif, kita boleh 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()); }

Bahagian yang paling penting di sini adalah penyediaan tiruan untuk menggunakan kod sebenar apabila kaedah dipanggil menggunakan Mockito.CALLS_REAL_METHODS .

4. Kaedah Abstrak Dipanggil Dari Kaedah Bukan Abstrak

Dalam kes ini, kaedah bukan abstrak menentukan aliran pelaksanaan global, sementara kaedah abstrak dapat ditulis dengan cara yang berbeza bergantung pada kes penggunaan:

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

Untuk menguji kod ini, kita dapat menggunakan dua pendekatan yang sama seperti sebelumnya - sama ada membuat kelas konkrit 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 () disekat dengan nilai pulangan yang kami sukai untuk ujian. Ini bermaksud bahawa apabila kita memanggil kaedah non-abstrak defaultImpl () , ia akan menggunakan rintisan ini.

5. Kaedah Tidak Abstrak Dengan Halangan Ujian

Dalam beberapa senario, kaedah yang ingin kami uji memanggil kaedah peribadi yang mengandungi halangan ujian.

Kita perlu memintas kaedah ujian yang menghalang sebelum menguji kaedah sasaran:

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, kaedah defaultImpl () memanggil kaedah peribadi getCurrentDateTime () . Kaedah peribadi ini mendapat masa semasa runtime, yang harus dielakkan dalam ujian unit kami.

Sekarang, untuk memperolok tingkah laku standard kaedah peribadi ini, kita bahkan tidak dapat menggunakan Mockito kerana tidak dapat mengawal kaedah peribadi.

Sebaliknya, kita perlu menggunakan PowerMock ( n Nota bahawa contoh ini hanya berfungsi dengan JUnit 4 kerana sokongan kepada pergantungan 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 ujian
  • @PrepareForTest (class) memberitahu PowerMock untuk menyediakan kelas untuk diproses kemudian

Menariknya, kami meminta PowerMock untuk menghentikan kaedah getCurrentDateTime (). PowerMock akan menggunakan pantulan untuk mencarinya kerana tidak boleh diakses dari luar.

Oleh itu , apabila kita memanggil defaultImpl () , rintisan yang dibuat untuk kaedah peribadi akan dipanggil dan bukannya kaedah yang sebenarnya.

6. Kaedah Tidak Abstrak yang Mengakses Bidang Instance

Kelas abstrak boleh mempunyai keadaan dalaman yang dilaksanakan dengan medan kelas. Nilai bidang boleh memberi kesan yang signifikan terhadap kaedah yang diuji.

Sekiranya medan bersifat umum atau dilindungi, kita dapat mengaksesnya dengan mudah dari kaedah ujian.

Tetapi jika ia bersifat peribadi, 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. Kesimpulannya

Dalam tutorial ini, kami telah melihat beberapa contoh yang merangkumi banyak kes penggunaan. Kita boleh menggunakan kelas abstrak dalam banyak senario bergantung pada reka bentuk yang diikuti.

Juga, ujian unit menulis untuk kaedah kelas abstrak sama pentingnya dengan kelas dan kaedah biasa. Kami dapat menguji masing-masing menggunakan teknik yang berbeza atau perpustakaan sokongan ujian yang berbeza.

Kod sumber lengkap boleh didapati di GitHub.