Penggunaan Lanjutan JMockit

1. Pengenalan

Dalam artikel ini, kita akan melangkaui asas JMockit dan kita akan mula melihat beberapa senario lanjutan, seperti:

  • Palsu (atau API MockUp )
  • The Deencapsulation kelas utiliti
  • Cara mengejek lebih daripada satu antara muka dengan hanya menggunakan satu tiruan
  • Cara menggunakan semula jangkaan dan pengesahan

Sekiranya anda ingin mengetahui asas-asas JMockit, periksa artikel lain dari siri ini. Anda boleh mendapatkan pautan yang relevan di bahagian bawah halaman.

2. Ketergantungan Maven

Pertama, kita perlu menambahkan kebergantungan jmockit ke projek kita:

 org.jmockit jmockit 1.41  

Seterusnya, kami akan meneruskan contohnya.

3. Mengejek Kaedah Swasta / Kelas Dalam

Mengejek dan menguji kaedah peribadi atau kelas dalaman sering tidak dianggap sebagai amalan yang baik.

Alasan di sebalik itu adalah bahawa jika mereka bersifat peribadi, mereka tidak boleh diuji secara langsung kerana mereka adalah kelas terdalam, tetapi kadang-kadang masih perlu dilakukan, terutama ketika berurusan dengan kod warisan.

Dengan JMockit, anda mempunyai dua pilihan untuk mengatasinya:

  • The mockup API untuk mengubah pelaksanaan sebenar (untuk kes kedua)
  • The Deencapsulation kelas utiliti, untuk memanggil mana-mana kaedah langsung (bagi kes pertama)

Semua contoh berikut akan dilakukan untuk kelas berikut dan kami akan menganggapnya dijalankan pada kelas ujian dengan konfigurasi yang sama dengan yang pertama (untuk mengelakkan kod berulang):

public class AdvancedCollaborator { int i; private int privateField = 5; // default constructor omitted public AdvancedCollaborator(String string) throws Exception{ i = string.length(); } public String methodThatCallsPrivateMethod(int i) { return privateMethod() + i; } public int methodThatReturnsThePrivateField() { return privateField; } private String privateMethod() { return "default:"; } class InnerAdvancedCollaborator {...} }

3.1. Palsu Dengan MockUp

API Mockup JMockit memberikan sokongan untuk pembuatan implementasi palsu atau mock-up . Biasanya, mock-up mensasarkan beberapa kaedah dan / atau konstruktor di dalam kelas untuk dipalsukan, sementara kebanyakan kaedah dan konstruktor lain tidak diubah suai. Ini membolehkan penulisan semula kelas sepenuhnya, jadi kaedah atau konstruktor apa pun (dengan pengubah akses) boleh disasarkan.

Mari lihat bagaimana kita dapat menentukan semula kaedah PrivateMode () menggunakan API Mockup:

@RunWith(JMockit.class) public class AdvancedCollaboratorTest { @Tested private AdvancedCollaborator mock; @Test public void testToMockUpPrivateMethod() { new MockUp() { @Mock private String privateMethod() { return "mocked: "; } }; String res = mock.methodThatCallsPrivateMethod(1); assertEquals("mocked: 1", res); } }

Dalam contoh ini kita mendefinisikan MockUp baru untuk kelas AdvancedCollaborator menggunakan anotasi @Mock pada kaedah dengan tandatangan yang sepadan. Selepas ini, panggilan ke kaedah itu akan diberikan kepada kaedah olok-olok kami.

Kami juga dapat menggunakan ini untuk mengejek konstruktor kelas yang memerlukan argumen atau konfigurasi khusus untuk mempermudah ujian:

@Test public void testToMockUpDifficultConstructor() throws Exception{ new MockUp() { @Mock public void $init(Invocation invocation, String string) { ((AdvancedCollaborator)invocation.getInvokedInstance()).i = 1; } }; AdvancedCollaborator coll = new AdvancedCollaborator(null); assertEquals(1, coll.i); }

Dalam contoh ini, kita dapat melihat bahawa untuk mengejek konstruktor, anda perlu mengejek kaedah $ init . Anda boleh menyampaikan argumen tambahan tentang jenis Permintaan, dengan mana anda dapat mengakses maklumat mengenai pemanggilan kaedah yang diperolok-olokkan, termasuk contoh ketika pemanggilan dilakukan.

3.2. Menggunakan Kelas Deencapsulation

JMockit merangkumi kelas utiliti ujian: Deencapsulation . Seperti namanya, itu digunakan untuk menghilangkan keadaan objek, dan menggunakannya, anda dapat mempermudah pengujian dengan mengakses bidang dan kaedah yang tidak dapat diakses sebaliknya.

Anda boleh menggunakan kaedah:

@Test public void testToCallPrivateMethodsDirectly(){ Object value = Deencapsulation.invoke(mock, "privateMethod"); assertEquals("default:", value); }

Anda juga boleh menetapkan medan:

@Test public void testToSetPrivateFieldDirectly(){ Deencapsulation.setField(mock, "privateField", 10); assertEquals(10, mock.methodThatReturnsThePrivateField()); }

Dan dapatkan bidang:

@Test public void testToGetPrivateFieldDirectly(){ int value = Deencapsulation.getField(mock, "privateField"); assertEquals(5, value); }

Dan buat contoh kelas baru:

@Test public void testToCreateNewInstanceDirectly(){ AdvancedCollaborator coll = Deencapsulation .newInstance(AdvancedCollaborator.class, "foo"); assertEquals(3, coll.i); }

Malah contoh baru kelas dalaman:

@Test public void testToCreateNewInnerClassInstanceDirectly(){ InnerCollaborator inner = Deencapsulation .newInnerInstance(InnerCollaborator.class, mock); assertNotNull(inner); }

Seperti yang anda lihat, kelas Deencapsulation sangat berguna ketika menguji kelas kedap udara. Salah satu contohnya ialah menetapkan kebergantungan kelas yang menggunakan anotasi @Autowired di medan persendirian dan tidak mempunyai penentu untuknya, atau menguji kelas dalaman tanpa perlu bergantung pada antara muka umum kelas kontena.

4. Mengolok-olokkan Pelbagai Antaramuka dalam Satu ejekan Sama

Mari kita anggap bahawa anda ingin menguji kelas - belum dilaksanakan - tetapi anda pasti tahu bahawa ia akan melaksanakan beberapa antara muka.

Biasanya, anda tidak dapat menguji kelas tersebut sebelum melaksanakannya, tetapi dengan JMockit anda mempunyai kemampuan untuk menyiapkan ujian terlebih dahulu dengan mengejek lebih daripada satu antara muka menggunakan satu objek tiruan.

Ini dapat dicapai dengan menggunakan generik dan menentukan jenis yang meluaskan beberapa antara muka. Jenis generik ini boleh ditakrifkan untuk keseluruhan kelas ujian atau hanya untuk satu kaedah ujian.

Sebagai contoh, kita akan membuat tiruan untuk Senarai antara muka dan dua cara yang boleh dibandingkan :

@RunWith(JMockit.class) public class AdvancedCollaboratorTest
    
     > { @Mocked private MultiMock multiMock; @Test public void testOnClass() { new Expectations() {{ multiMock.get(5); result = "foo"; multiMock.compareTo((List) any); result = 0; }}; assertEquals("foo", multiMock.get(5)); assertEquals(0, multiMock.compareTo(new ArrayList())); } @Test public 
     
      > void testOnMethod(@Mocked M mock) { new Expectations() {{ mock.get(5); result = "foo"; mock.compareTo((List) any); result = 0; }}; assertEquals("foo", mock.get(5)); assertEquals(0, mock.compareTo(new ArrayList())); } }
     
    

Seperti yang anda lihat di baris 2, kita dapat menentukan jenis ujian baru untuk keseluruhan ujian dengan menggunakan generik pada nama kelas. Dengan cara itu, MultiMock akan tersedia sebagai jenis dan anda akan dapat membuat ejekan untuknya menggunakan mana-mana anotasi JMockit.

In lines from 7 to 18, we can see an example using a mock of a multi-class defined for the whole test class.

If you need the multi-interface mock for just one test, you can achieve this by defining the generic type on the method signature and passing a new mock of that new generic as the test method argument. In lines 20 to 32, we can see an example of doing so for the same tested behavior as in the previous test.

5. Reusing Expectations and Verifications

In the end, when testing classes, you may encounter cases where you're repeating the same Expectations and/or Verifications over and over. To ease that, you can reuse both easily.

We're going to explain it by an example (we're using the classes Model, Collaborator, and Performer from our JMockit 101 article):

@RunWith(JMockit.class) public class ReusingTest { @Injectable private Collaborator collaborator; @Mocked private Model model; @Tested private Performer performer; @Before public void setup(){ new Expectations(){{ model.getInfo(); result = "foo"; minTimes = 0; collaborator.collaborate("foo"); result = true; minTimes = 0; }}; } @Test public void testWithSetup() { performer.perform(model); verifyTrueCalls(1); } protected void verifyTrueCalls(int calls){ new Verifications(){{ collaborator.receive(true); times = calls; }}; } final class TrueCallsVerification extends Verifications{ public TrueCallsVerification(int calls){ collaborator.receive(true); times = calls; } } @Test public void testWithFinalClass() { performer.perform(model); new TrueCallsVerification(1); } }

In this example, you can see in lines from 15 to 18 that we're preparing an expectation for every test so that model.getInfo() always returns “foo” and for collaborator.collaborate() to always expect “foo” as the argument and returning true. We put the minTimes = 0 statement so no fails appear when not actually using them in tests.

Also, we've created method verifyTrueCalls(int) to simplify verifications to the collaborator.receive(boolean) method when the passed argument is true.

Lastly, you can also create new types of specific expectations and verifications just extending any of Expectations or Verifications classes. Then you define a constructor if you need to configure the behavior and create a new instance of said type in a test as we do in lines from 33 to 43.

6. Conclusion

With this installment of the JMockit series, we have touched on several advanced topics that will definitely help you with everyday mocking and testing.

We may do more articles on JMockit, so stay tuned to learn even more.

Dan, seperti biasa, pelaksanaan penuh tutorial ini boleh didapati di GitHub.

6.1. Artikel dalam Siri

Semua artikel siri ini:

  • JMockit 101
  • Panduan untuk JMockit Harapan
  • Penggunaan Lanjutan JMockit