CodeGym /Blog Java /rawak /Semua tentang ujian unit: teknik, konsep, amalan
John Squirrels
Tahap
San Francisco

Semua tentang ujian unit: teknik, konsep, amalan

Diterbitkan dalam kumpulan
Hari ini anda tidak akan menemui aplikasi yang tidak diselubungi ujian, jadi topik ini akan menjadi lebih relevan berbanding sebelum ini untuk pembangun pemula: anda tidak boleh berjaya tanpa ujian. Mari kita pertimbangkan jenis ujian yang digunakan pada dasarnya, dan kemudian kita akan mengkaji secara terperinci semua yang perlu diketahui tentang ujian unit. Semua tentang ujian unit: teknik, konsep, amalan - 1

Jenis-jenis ujian

Apakah ujian? Menurut Wikipedia: "Pengujian perisian melibatkan pelaksanaan komponen perisian atau komponen sistem untuk menilai satu atau lebih sifat yang diminati." Dalam erti kata lain, ia adalah semakan ketepatan sistem kami dalam situasi tertentu. Baiklah, mari kita lihat jenis ujian yang ada secara umum:
  • Ujian unit — Ujian yang bertujuan untuk menyemak setiap modul sistem secara berasingan. Ujian ini hendaklah digunakan pada bahagian atom terkecil sistem, contohnya modul.
  • Ujian sistem — Ujian peringkat tinggi untuk menyemak operasi sekeping aplikasi yang lebih besar atau sistem secara keseluruhan.
  • Ujian regresi — Ujian yang digunakan untuk menyemak sama ada ciri baharu atau pembetulan pepijat menjejaskan fungsi sedia ada aplikasi atau memperkenalkan pepijat lama.
  • Ujian fungsional — Memeriksa sama ada sebahagian daripada aplikasi memenuhi keperluan yang dinyatakan dalam spesifikasi, cerita pengguna, dsb.

    Jenis ujian fungsi:

    • Ujian kotak putih — Memeriksa sama ada sebahagian daripada aplikasi memenuhi keperluan sambil mengetahui pelaksanaan dalaman sistem;
    • Ujian kotak hitam — Menyemak sama ada sebahagian daripada aplikasi memenuhi keperluan tanpa mengetahui pelaksanaan dalaman sistem.

  • Ujian prestasi — Ujian yang ditulis untuk menentukan prestasi sistem atau bahagian sistem di bawah beban tertentu.
  • Ujian beban — Ujian direka untuk memeriksa kestabilan sistem di bawah beban standard dan untuk mencari beban maksimum yang mana aplikasi masih berfungsi dengan betul.
  • Ujian tekanan — Ujian direka untuk menyemak prestasi aplikasi di bawah beban bukan standard dan untuk menentukan beban maksimum sebelum kegagalan sistem.
  • Ujian keselamatan — Ujian yang digunakan untuk menyemak keselamatan sistem (daripada penggodam, virus, akses tanpa kebenaran kepada data sulit dan serangan menarik lain).
  • Ujian penyetempatan — Ujian penyetempatan aplikasi.
  • Ujian kebolehgunaan — Ujian bertujuan untuk menyemak kebolehgunaan, kebolehfahaman, daya tarikan dan kebolehpelajaran.
Ini semua kedengaran bagus, tetapi bagaimana ia berfungsi dalam amalan? Mudah! Kami menggunakan piramid ujian Mike Cohn: Semua tentang ujian unit: teknik, konsep, amalan - 2Ini adalah versi ringkas piramid: ia kini dibahagikan kepada bahagian yang lebih kecil. Tetapi hari ini kita tidak akan menjadi terlalu canggih. Kami akan mempertimbangkan versi paling mudah.
  1. Unit — Bahagian ini merujuk kepada ujian unit, yang digunakan dalam lapisan aplikasi yang berbeza. Mereka menguji unit terkecil yang boleh dibahagikan bagi logik aplikasi. Sebagai contoh, kelas, tetapi kaedah yang paling kerap. Ujian ini biasanya cuba sebanyak mungkin untuk mengasingkan apa yang diuji daripada mana-mana logik luaran. Iaitu, mereka cuba mencipta ilusi bahawa seluruh aplikasi berjalan seperti yang diharapkan.

    Perlu sentiasa ada banyak ujian ini (lebih daripada jenis lain), kerana ia menguji kepingan kecil dan sangat ringan, tidak memakan banyak sumber (bermaksud RAM dan masa).

  2. Penyepaduan — Bahagian ini merujuk kepada ujian penyepaduan. Ujian ini memeriksa bahagian sistem yang lebih besar. Iaitu, ia sama ada menggabungkan beberapa keping logik (beberapa kaedah atau kelas), atau ia menyemak ketepatan interaksi dengan komponen luaran. Ujian ini biasanya lebih kecil daripada ujian unit kerana ia lebih berat.

    Contoh ujian integrasi boleh menyambung ke pangkalan data dan menyemak ketepatan operasi kaedah untuk bekerja dengannya.

  3. UI — Bahagian ini merujuk kepada ujian yang menyemak operasi antara muka pengguna. Ia melibatkan logik pada semua peringkat aplikasi, itulah sebabnya ia juga dipanggil ujian hujung ke hujung. Sebagai peraturan, terdapat lebih sedikit daripada mereka, kerana mereka adalah yang paling rumit dan mesti menyemak laluan yang paling diperlukan (terpakai).

    Dalam gambar di atas, kita melihat bahawa bahagian-bahagian segitiga yang berbeza berbeza dalam saiz: kira-kira perkadaran yang sama wujud dalam bilangan pelbagai jenis ujian dalam kerja sebenar.

    Hari ini kita akan melihat dengan lebih dekat pada ujian yang paling biasa, ujian unit, kerana semua pembangun Java yang menghormati diri seharusnya boleh menggunakannya pada tahap asas.

Konsep utama dalam ujian unit

Liputan ujian (liputan kod) adalah salah satu ukuran utama sejauh mana aplikasi diuji. Ini ialah peratusan kod yang diliputi oleh ujian (0-100%). Dalam amalan, ramai yang mengejar peratusan ini sebagai matlamat mereka. Itu sesuatu yang saya tidak bersetuju, kerana ini bermakna ujian mula digunakan di mana ia tidak diperlukan. Sebagai contoh, katakan kami mempunyai operasi CRUD standard (buat/dapatkan/kemas kini/padam) dalam perkhidmatan kami tanpa logik tambahan. Kaedah ini adalah perantara semata-mata yang mewakilkan kerja kepada lapisan yang bekerja dengan repositori. Dalam keadaan ini, kami tidak mempunyai apa-apa untuk diuji, kecuali mungkin sama ada kaedah yang diberikan memanggil kaedah DAO, tetapi itu jenaka. Alat tambahan biasanya digunakan untuk menilai liputan ujian: JaCoCo, Cobertura, Clover, Emma, ​​dll. Untuk kajian yang lebih terperinci tentang topik ini, TDD bermaksud pembangunan dipacu ujian. Dalam pendekatan ini, sebelum melakukan apa-apa lagi, anda menulis ujian yang akan menyemak kod tertentu. Ini ternyata ujian kotak hitam: kami tahu inputnya dan kami tahu apa output yang sepatutnya. Ini memungkinkan untuk mengelakkan pertindihan kod. Pembangunan dipacu ujian bermula dengan mereka bentuk dan membangunkan ujian untuk setiap sedikit fungsi dalam aplikasi anda. Dalam pendekatan TDD, kami mula-mula membuat ujian yang mentakrifkan dan menguji tingkah laku kod. Matlamat utama TDD adalah untuk menjadikan kod anda lebih mudah difahami, lebih mudah dan bebas ralat. Semua tentang ujian unit: teknik, konsep, amalan - 3Pendekatannya terdiri daripada yang berikut:
  • Kami menulis ujian kami.
  • Kami menjalankan ujian. Tidak mengejutkan, ia gagal, kerana kami belum lagi melaksanakan logik yang diperlukan.
  • Tambah kod yang menyebabkan ujian lulus (kami menjalankan ujian sekali lagi).
  • Kami memfaktorkan semula kod.
TDD adalah berdasarkan ujian unit, kerana ia adalah blok binaan terkecil dalam piramid automasi ujian. Dengan ujian unit, kami boleh menguji logik perniagaan mana-mana kelas. BDD bermaksud pembangunan yang didorong oleh tingkah laku. Pendekatan ini adalah berdasarkan TDD. Secara lebih khusus, ia menggunakan contoh bahasa biasa yang menerangkan tingkah laku sistem untuk semua orang yang terlibat dalam pembangunan. Kami tidak akan menyelidiki istilah ini, kerana ia memberi kesan terutamanya kepada penguji dan penganalisis perniagaan. Kes ujian ialah senario yang menerangkan langkah, keadaan khusus dan parameter yang diperlukan untuk menyemak kod yang sedang diuji. Lekapan ujian ialah kod yang menyediakan persekitaran ujian supaya mempunyai keadaan yang diperlukan untuk kaedah yang diuji berjalan dengan jayanya. Ia adalah set objek yang dipratentukan dan kelakuannya di bawah keadaan tertentu.

Peringkat ujian

Ujian terdiri daripada tiga peringkat:
  • Nyatakan data ujian (lekapan).
  • Gunakan kod yang sedang diuji (panggil kaedah yang diuji).
  • Sahkan keputusan dan bandingkan dengan keputusan yang dijangkakan.
Semua tentang ujian unit: teknik, konsep, amalan - 4Untuk memastikan modulariti ujian, anda perlu mengasingkan daripada lapisan lain aplikasi. Ini boleh dilakukan menggunakan stub, ejekan dan pengintip. Olok-olok ialah objek yang boleh disesuaikan (contohnya, disesuaikan untuk setiap ujian). Mereka membenarkan kami menentukan perkara yang kami harapkan daripada panggilan kaedah, iaitu respons yang dijangkakan. Kami menggunakan objek olok-olok untuk mengesahkan bahawa kami mendapat apa yang kami harapkan. Stub memberikan respons berkod keras kepada panggilan semasa ujian. Mereka juga boleh menyimpan maklumat tentang panggilan (contohnya, parameter atau bilangan panggilan). Mereka ini kadangkala dirujuk sebagai pengintip. Kadangkala orang mengelirukan istilah rintisan dan mengejek: perbezaannya ialah rintisan tidak memeriksa apa-apa — ia hanya mensimulasikan keadaan tertentu. Olok-olok ialah objek yang mempunyai jangkaan. Sebagai contoh, kaedah yang diberikan mesti dipanggil beberapa kali. Dalam kata lain,

Persekitaran ujian

Jadi, sekarang ke titik. Terdapat beberapa persekitaran ujian (rangka kerja) tersedia untuk Java. Yang paling popular ialah JUnit dan TestNG. Untuk semakan kami di sini, kami menggunakan: Semua tentang ujian unit: teknik, konsep, amalan - 5Ujian JUnit ialah kaedah dalam kelas yang digunakan hanya untuk ujian. Kelas biasanya dinamakan sama dengan kelas yang diuji, dengan "Ujian" dilampirkan pada penghujung. Contohnya, CarService -> CarServiceTest. Sistem binaan Maven secara automatik memasukkan kelas sedemikian dalam skop ujian. Malah, kelas ini dipanggil kelas ujian. Mari kita lihat secara ringkas anotasi asas:

  • @Test menunjukkan bahawa kaedah itu adalah ujian (pada asasnya, kaedah yang ditandakan dengan anotasi ini ialah ujian unit).
  • @Before menandakan kaedah yang akan dilaksanakan sebelum setiap ujian. Contohnya, untuk mengisi kelas dengan data ujian, membaca data input, dsb.
  • @After digunakan untuk menandakan kaedah yang akan dipanggil selepas setiap ujian (cth untuk mengosongkan data atau memulihkan nilai lalai).
  • @BeforeClass diletakkan di atas kaedah, sama dengan @Before. Tetapi kaedah sedemikian dipanggil sekali sahaja sebelum semua ujian untuk kelas yang diberikan dan oleh itu mestilah statik. Ia digunakan untuk melaksanakan lebih banyak operasi intensif sumber, seperti memutar pangkalan data ujian.
  • @AfterClass adalah bertentangan dengan @BeforeClass: ia dilaksanakan sekali untuk kelas yang diberikan, tetapi hanya selepas semua ujian. Ia digunakan, sebagai contoh, untuk mengosongkan sumber berterusan atau memutuskan sambungan daripada pangkalan data.
  • @Ignore menandakan bahawa kaedah dilumpuhkan dan akan diabaikan semasa keseluruhan ujian dijalankan. Ini digunakan dalam pelbagai situasi, contohnya, jika kaedah asas telah diubah dan ujian belum lagi diolah semula untuk menampung perubahan. Dalam kes sedemikian, adalah wajar untuk menambah penerangan, iaitu @Ignore("Some description").
  • @Test(expected = Exception.class) digunakan untuk ujian negatif. Ini adalah ujian yang mengesahkan cara kaedah bertindak sekiranya berlaku ralat, iaitu, ujian menjangkakan kaedah itu akan mengeluarkan beberapa jenis pengecualian. Kaedah sedemikian ditunjukkan oleh anotasi @Test, tetapi dengan petunjuk ralat mana yang perlu ditangkap.
  • @Test(timeout = 100) menyemak bahawa kaedah itu dilaksanakan dalam masa tidak lebih daripada 100 milisaat.
  • @Mock digunakan di atas medan untuk menetapkan objek olok-olok (ini bukan anotasi JUnit, sebaliknya datang daripada Mockito). Seperti yang diperlukan, kami menetapkan tingkah laku olok-olok untuk situasi tertentu secara langsung dalam kaedah ujian.
  • @RunWith(MockitoJUnitRunner.class) diletakkan di atas kelas. Anotasi ini memberitahu JUnit untuk menggunakan ujian dalam kelas. Terdapat pelbagai pelari, termasuk ini: MockitoJUnitRunner, JUnitPlatform dan SpringRunner. Dalam JUnit 5, anotasi @RunWith telah digantikan dengan anotasi @ExtendWith yang lebih berkuasa.
Mari kita lihat beberapa kaedah yang digunakan untuk membandingkan hasil:

  • assertEquals(Objek dijangka, Objek sebenar) — menyemak sama ada objek yang diluluskan adalah sama.
  • assertTrue(boolean flag) — menyemak sama ada nilai yang diluluskan adalah benar.
  • assertFalse(boolean flag) — menyemak sama ada nilai yang diluluskan adalah palsu.
  • assertNull(Object object) — menyemak sama ada objek yang dilalui adalah batal.
  • assertSame(Object firstObject, Object secondObject) — menyemak sama ada nilai yang diluluskan merujuk kepada objek yang sama.
  • menegaskanBahawa(T t, Matcher padanan) — Semak sama ada t memenuhi syarat yang dinyatakan dalam pemadan.
AssertJ juga menyediakan kaedah perbandingan yang berguna: assertThat(firstObject).isEqualTo(secondObject) . Di sini saya telah menyebut kaedah asas - yang lain adalah variasi di atas.

Ujian dalam amalan

Sekarang mari kita lihat bahan di atas dalam contoh khusus. Kami akan menguji kaedah kemas kini perkhidmatan. Kami tidak akan mempertimbangkan lapisan DAO, kerana kami menggunakan lalai. Mari tambahkan pemula untuk ujian:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <version>2.2.2.RELEASE</version>
   <scope>test</scope>
</dependency>
Dan di sini kami mempunyai kelas perkhidmatan:

@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
   private final RobotDAO robotDAO;

   @Override
   public Robot update(Long id, Robot robot) {
       Robot found = robotDAO.findById(id);
       return robotDAO.update(Robot.builder()
               .id(id)
               .name(robot.getName() != null ? robot.getName() : found.getName())
               .cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
               .producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
               .build());
   }
}
Baris 8 — tarik objek yang dikemas kini daripada pangkalan data. Baris 9-14 — cipta objek melalui pembina. Jika objek masuk mempunyai medan, tetapkannya. Jika tidak, kami akan meninggalkan apa yang ada dalam pangkalan data. Sekarang lihat ujian kami:

@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
   @Mock
   private RobotDAO robotDAO;

   private RobotServiceImpl robotService;

   private static Robot testRobot;

   @BeforeClass
   public static void prepareTestData() {
       testRobot = Robot
               .builder()
               .id(123L)
               .name("testRobotMolly")
               .cpu("Intel Core i7-9700K")
               .producer("China")
               .build();
   }

   @Before
   public void init() {
       robotService = new RobotServiceImpl(robotDAO);
   }
Baris 1 — Pelari kami. Baris 4 — kami mengasingkan perkhidmatan daripada lapisan DAO dengan menggantikan olok-olok. Baris 11 — kami menetapkan entiti ujian (yang akan kami gunakan sebagai guinea pig) untuk kelas. Baris 22 — kami menetapkan objek perkhidmatan, yang akan kami uji.

@Test
public void updateTest() {
   when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
   when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
   Robot robotForUpdate = Robot
           .builder()
           .name("Vally")
           .cpu("AMD Ryzen 7 2700X")
           .build();

   Robot resultRobot = robotService.update(123L, robotForUpdate);

   assertNotNull(resultRobot);
   assertSame(resultRobot.getId(),testRobot.getId());
   assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
   assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
   assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
Di sini kita melihat bahawa ujian mempunyai tiga bahagian yang jelas: Baris 3-9 — menentukan lekapan. Baris 11 — melaksanakan kod yang sedang diuji. Baris 13-17 — menyemak keputusan. Lebih terperinci: Baris 3-4 — tetapkan gelagat untuk olok-olok DAO. Baris 5 — tetapkan contoh yang akan kami kemas kini di atas standard kami. Baris 11 — gunakan kaedah dan ambil contoh yang terhasil. Baris 13 — semak bahawa ia bukan batal. Baris 14 — bandingkan ID hasil dan hujah kaedah yang diberikan. Baris 15 — semak sama ada nama telah dikemas kini. Baris 16 — lihat hasil CPU. Baris 17 — kami tidak menyatakan medan ini dalam contoh, jadi ia harus kekal sama. Kami menyemak keadaan itu di sini. Mari jalankan:Semua tentang ujian unit: teknik, konsep, amalan - 6Ujian itu hijau! Kami boleh menarik nafas lega :) Secara ringkasnya, ujian meningkatkan kualiti kod dan menjadikan proses pembangunan lebih fleksibel dan boleh dipercayai. Bayangkan betapa banyak usaha yang diperlukan untuk mereka bentuk semula perisian yang melibatkan ratusan fail kelas. Apabila kami mempunyai ujian unit yang ditulis untuk semua kelas ini, kami boleh memfaktorkan semula dengan yakin. Dan yang paling penting, ia membantu kami mencari pepijat dengan mudah semasa pembangunan. Lelaki dan perempuan, itu sahaja yang saya dapat hari ini. Beri saya suka, dan tinggalkan komen :)
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION