CodeGym /Java Blog /Acak /Semua tentang pengujian unit: teknik, konsep, praktik
John Squirrels
Level 41
San Francisco

Semua tentang pengujian unit: teknik, konsep, praktik

Dipublikasikan di grup Acak
Hari ini Anda tidak akan menemukan aplikasi yang tidak dibungkus dengan pengujian, jadi topik ini akan lebih relevan dari sebelumnya untuk pengembang pemula: Anda tidak dapat berhasil tanpa pengujian. Mari pertimbangkan jenis pengujian apa yang digunakan pada prinsipnya, dan kemudian kita akan mempelajari secara detail semua yang perlu diketahui tentang pengujian unit. Semua tentang pengujian unit: teknik, konsep, praktik - 1

Jenis pengujian

Apa itu tes? Menurut Wikipedia: "Pengujian perangkat lunak melibatkan pelaksanaan komponen perangkat lunak atau komponen sistem untuk mengevaluasi satu atau lebih properti yang diminati." Dengan kata lain, ini adalah pemeriksaan kebenaran sistem kami dalam situasi tertentu. Baiklah, mari kita lihat jenis pengujian apa saja yang ada secara umum:
  • Pengujian unit — Pengujian yang bertujuan untuk memeriksa setiap modul sistem secara terpisah. Tes ini harus diterapkan pada bagian atom terkecil dari sistem, misalnya modul.
  • Pengujian sistem — Pengujian tingkat tinggi untuk memeriksa pengoperasian sebagian besar aplikasi atau sistem secara keseluruhan.
  • Pengujian regresi — Pengujian yang digunakan untuk memeriksa apakah fitur baru atau perbaikan bug memengaruhi fungsionalitas aplikasi yang ada atau memperkenalkan bug lama.
  • Pengujian fungsional — Memeriksa apakah suatu bagian dari aplikasi memenuhi persyaratan yang dinyatakan dalam spesifikasi, cerita pengguna, dll.

    Jenis pengujian fungsional:

    • Pengujian kotak putih — Memeriksa apakah suatu bagian dari aplikasi memenuhi persyaratan sambil mengetahui implementasi internal sistem;
    • Pengujian kotak hitam — Memeriksa apakah suatu bagian dari aplikasi memenuhi persyaratan tanpa mengetahui implementasi internal sistem.

  • Pengujian kinerja — Pengujian yang ditulis untuk menentukan bagaimana kinerja sistem atau bagian dari sistem di bawah beban tertentu.
  • Pengujian beban — Pengujian dirancang untuk memeriksa stabilitas sistem di bawah beban standar dan untuk menemukan beban maksimum di mana aplikasi masih berfungsi dengan benar.
  • Stress testing — Pengujian dirancang untuk memeriksa kinerja aplikasi di bawah beban non-standar dan untuk menentukan beban maksimum sebelum kegagalan sistem.
  • Pengujian keamanan — Pengujian yang digunakan untuk memeriksa keamanan sistem (dari peretas, virus, akses tidak sah ke data rahasia, dan serangan menyenangkan lainnya).
  • Pengujian lokalisasi — Pengujian lokalisasi aplikasi.
  • Pengujian kegunaan - Pengujian ditujukan untuk memeriksa kegunaan, pemahaman, daya tarik, dan kemampuan belajar.
Ini kedengarannya bagus, tetapi bagaimana cara kerjanya dalam praktik? Sederhana! Kami menggunakan piramida pengujian Mike Cohn: Semua tentang pengujian unit: teknik, konsep, praktik - 2Ini adalah versi sederhana dari piramida: sekarang dibagi menjadi bagian yang lebih kecil. Tapi hari ini kita tidak akan menjadi terlalu canggih. Kami akan mempertimbangkan versi paling sederhana.
  1. Unit — Bagian ini mengacu pada pengujian unit, yang diterapkan di berbagai lapisan aplikasi. Mereka menguji unit logika aplikasi terkecil yang dapat dibagi. Misalnya, kelas, tetapi paling sering metode. Pengujian ini biasanya mencoba sebisa mungkin untuk mengisolasi apa yang diuji dari logika eksternal mana pun. Artinya, mereka mencoba menciptakan ilusi bahwa aplikasi lainnya berjalan seperti yang diharapkan.

    Harus selalu ada banyak tes ini (lebih banyak dari jenis lainnya), karena mereka menguji bagian kecil dan sangat ringan, tidak memakan banyak sumber daya (artinya RAM dan waktu).

  2. Integrasi — Bagian ini mengacu pada pengujian integrasi. Pengujian ini memeriksa bagian yang lebih besar dari sistem. Yaitu, menggabungkan beberapa bagian logika (beberapa metode atau kelas), atau memeriksa kebenaran interaksi dengan komponen eksternal. Tes ini biasanya lebih kecil dari tes unit karena lebih berat.

    Contoh tes integrasi dapat menghubungkan ke database dan memeriksa kebenaran pengoperasian metode untuk bekerja dengannya.

  3. UI — Bagian ini mengacu pada pengujian yang memeriksa pengoperasian antarmuka pengguna. Mereka melibatkan logika di semua level aplikasi, itulah sebabnya mereka juga disebut tes end-to-end. Sebagai aturan, jumlahnya jauh lebih sedikit, karena mereka yang paling rumit dan harus memeriksa jalur yang paling diperlukan (digunakan).

    Pada gambar di atas, kita melihat bahwa berbagai bagian segitiga memiliki ukuran yang berbeda-beda: kira-kira proporsi yang sama ada dalam jumlah berbagai jenis pengujian dalam pekerjaan nyata.

    Hari ini kita akan melihat lebih dekat pengujian yang paling umum, pengujian unit, karena semua pengembang Java yang menghargai diri sendiri harus dapat menggunakannya di tingkat dasar.

Konsep kunci dalam pengujian unit

Cakupan pengujian (cakupan kode) adalah salah satu ukuran utama seberapa baik aplikasi diuji. Ini adalah persentase kode yang dicakup oleh pengujian (0-100%). Dalam praktiknya, banyak yang mengejar persentase ini sebagai tujuan mereka. Itu adalah sesuatu yang saya tidak setuju, karena itu berarti tes mulai diterapkan di tempat yang tidak diperlukan. Misalnya, kami memiliki operasi CRUD (buat/dapatkan/perbarui/hapus) standar di layanan kami tanpa logika tambahan. Metode ini murni perantara yang mendelegasikan pekerjaan ke lapisan yang bekerja dengan repositori. Dalam situasi ini, kami tidak memiliki apa pun untuk diuji, kecuali mungkin apakah metode yang diberikan memanggil metode DAO, tapi itu lelucon. Alat tambahan biasanya digunakan untuk menilai cakupan tes: JaCoCo, Cobertura, Clover, Emma, ​​dll. Untuk studi yang lebih rinci tentang topik ini, TDD adalah singkatan dari test-driven development. Dalam pendekatan ini, sebelum melakukan hal lain, Anda menulis tes yang akan memeriksa kode tertentu. Ini ternyata menjadi pengujian kotak hitam: kita tahu inputnya dan kita tahu apa outputnya. Ini memungkinkan untuk menghindari duplikasi kode. Pengembangan berbasis pengujian dimulai dengan merancang dan mengembangkan pengujian untuk setiap bit fungsionalitas dalam aplikasi Anda. Dalam pendekatan TDD, pertama-tama kita membuat pengujian yang mendefinisikan dan menguji perilaku kode. Tujuan utama TDD adalah membuat kode Anda lebih mudah dipahami, lebih sederhana, dan bebas dari kesalahan. Semua tentang pengujian unit: teknik, konsep, praktik - 3Pendekatan tersebut terdiri dari hal-hal berikut:
  • Kami menulis tes kami.
  • Kami menjalankan tes. Tidak mengherankan, gagal, karena kami belum mengimplementasikan logika yang diperlukan.
  • Tambahkan kode yang menyebabkan pengujian lulus (kami menjalankan pengujian lagi).
  • Kami memperbaiki kode.
TDD didasarkan pada pengujian unit, karena mereka adalah blok bangunan terkecil dalam piramida otomasi pengujian. Dengan pengujian unit, kita dapat menguji logika bisnis kelas apa pun. BDD adalah singkatan dari behavior-driven development. Pendekatan ini didasarkan pada TDD. Lebih khusus lagi, ini menggunakan contoh bahasa sederhana yang menjelaskan perilaku sistem untuk semua orang yang terlibat dalam pengembangan. Kami tidak akan mempelajari istilah ini, karena ini terutama memengaruhi penguji dan analis bisnis. Kasus uji adalah skenario yang menjelaskan langkah-langkah, kondisi khusus, dan parameter yang diperlukan untuk memeriksa kode yang sedang diuji. Fixture pengujian adalah kode yang mengatur lingkungan pengujian agar memiliki status yang diperlukan agar metode yang diuji dapat berjalan dengan sukses. Ini adalah kumpulan objek yang telah ditentukan sebelumnya dan perilakunya dalam kondisi tertentu.

Tahapan pengujian

Tes terdiri dari tiga tahap:
  • Tentukan data uji (fixtures).
  • Latih kode yang sedang diuji (panggil metode yang diuji).
  • Verifikasi hasil dan bandingkan dengan hasil yang diharapkan.
Semua tentang pengujian unit: teknik, konsep, praktik - 4Untuk memastikan modularitas pengujian, Anda perlu mengisolasi dari lapisan aplikasi lainnya. Ini dapat dilakukan dengan menggunakan rintisan, tiruan, dan mata-mata. Mock adalah objek yang dapat dikustomisasi (misalnya, disesuaikan untuk setiap tes). Mereka membiarkan kami menentukan apa yang kami harapkan dari pemanggilan metode, yaitu tanggapan yang diharapkan. Kami menggunakan objek tiruan untuk memverifikasi bahwa kami mendapatkan apa yang kami harapkan. Rintisan bertopik memberikan respons hard-code untuk panggilan selama pengujian. Mereka juga dapat menyimpan informasi tentang panggilan (misalnya, parameter atau jumlah panggilan). Ini kadang-kadang disebut sebagai mata-mata. Kadang-kadang orang mengacaukan istilah stub dan mock: perbedaannya adalah stub tidak mencentang apa pun — ia hanya mensimulasikan keadaan tertentu. Mock adalah objek yang memiliki ekspektasi. Misalnya, metode yang diberikan harus dipanggil beberapa kali. Dengan kata lain,

Lingkungan pengujian

Jadi, sekarang to the point. Ada beberapa lingkungan pengujian (kerangka kerja) yang tersedia untuk Java. Yang paling populer adalah JUnit dan TestNG. Untuk ulasan kami di sini, kami menggunakan: Semua tentang pengujian unit: teknik, konsep, praktik - 5Tes JUnit adalah metode di kelas yang hanya digunakan untuk pengujian. Kelas biasanya dinamai sama dengan kelas yang diuji, dengan "Test" ditambahkan di akhir. Misalnya, CarService -> CarServiceTest. Sistem build Maven secara otomatis menyertakan kelas-kelas tersebut dalam lingkup pengujian. Bahkan, kelas ini disebut kelas tes. Mari kita bahas secara singkat anotasi dasar:

  • @Test menunjukkan bahwa metode tersebut adalah pengujian (pada dasarnya, metode yang ditandai dengan anotasi ini adalah pengujian unit).
  • @Before menandakan metode yang akan dieksekusi sebelum setiap pengujian. Misalnya, untuk mengisi kelas dengan data pengujian, membaca data input, dll.
  • @After digunakan untuk menandai metode yang akan dipanggil setelah setiap pengujian (misalnya untuk menghapus data atau memulihkan nilai default).
  • @BeforeClass ditempatkan di atas sebuah metode, analog dengan @Before. Tetapi metode seperti itu dipanggil hanya sekali sebelum semua pengujian untuk kelas yang diberikan dan oleh karena itu harus statis. Ini digunakan untuk melakukan operasi yang lebih intensif sumber daya, seperti memutar database pengujian.
  • @AfterClass kebalikan dari @BeforeClass: dieksekusi sekali untuk kelas tertentu, tetapi hanya setelah semua pengujian. Ini digunakan, misalnya, untuk menghapus sumber daya yang persisten atau memutuskan sambungan dari database.
  • @Ignore menunjukkan bahwa metode dinonaktifkan dan akan diabaikan selama keseluruhan uji coba. Ini digunakan dalam berbagai situasi, misalnya, jika metode dasar telah diubah dan pengujian belum dikerjakan ulang untuk mengakomodasi perubahan tersebut. Dalam kasus seperti itu, juga diinginkan untuk menambahkan deskripsi, yaitu @Ignore("Some description").
  • @Test(expected = Exception.class) digunakan untuk pengujian negatif. Ini adalah pengujian yang memverifikasi bagaimana metode berperilaku jika terjadi kesalahan, yaitu, pengujian mengharapkan metode untuk melontarkan semacam pengecualian. Metode seperti itu ditunjukkan oleh anotasi @Test, tetapi dengan indikasi kesalahan mana yang harus ditangkap.
  • @Test(timeout = 100) memeriksa bahwa metode dijalankan tidak lebih dari 100 milidetik.
  • @Mock digunakan di atas bidang untuk menetapkan objek tiruan (ini bukan anotasi JUnit, melainkan berasal dari Mockito). Sesuai kebutuhan, kami menyetel perilaku tiruan untuk situasi tertentu secara langsung dalam metode pengujian.
  • @RunWith(MockitoJUnitRunner.class) ditempatkan di atas kelas. Anotasi ini memberi tahu JUnit untuk menjalankan pengujian di kelas. Ada berbagai pelari, antara lain: MockitoJUnitRunner, JUnitPlatform, dan SpringRunner. Di JUnit 5, anotasi @RunWith telah diganti dengan anotasi @ExtendWith yang lebih kuat.
Mari kita lihat beberapa metode yang digunakan untuk membandingkan hasil:

  • assertEquals(Object expects, Object actuals) — memeriksa apakah objek yang diteruskan sama.
  • assertTrue(boolean flag) — memeriksa apakah nilai yang diteruskan benar.
  • assertFalse(boolean flag) — memeriksa apakah nilai yang diteruskan salah.
  • assertNull(Object object) — memeriksa apakah objek yang diteruskan adalah null.
  • assertSame(Object firstObject, Object secondObject) — memeriksa apakah nilai yang diteruskan merujuk ke objek yang sama.
  • menegaskanItu(T t, Matcher pencocokan) — Memeriksa apakah t memenuhi kondisi yang ditentukan dalam pencocokan.
AssertJ juga menyediakan metode perbandingan yang berguna: assertThat(firstObject).isEqualTo(secondObject) . Di sini saya telah menyebutkan metode dasar — ​​yang lainnya adalah variasi di atas.

Pengujian dalam praktek

Sekarang mari kita lihat materi di atas dalam contoh spesifik. Kami akan menguji metode pembaruan layanan. Kami tidak akan mempertimbangkan lapisan DAO, karena kami menggunakan default. Mari tambahkan starter untuk pengujian:

<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 kita memiliki kelas layanan:

@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 diperbarui dari database. Baris 9-14 — buat objek melalui pembangun. Jika objek yang masuk memiliki bidang, setel. Jika tidak, kami akan meninggalkan apa yang ada di database. Sekarang lihat pengujian 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 mengisolasi layanan dari lapisan DAO dengan mengganti mock. Baris 11 — kita menetapkan entitas uji (yang akan kita gunakan sebagai kelinci percobaan) untuk kelas. Baris 22 — kita menyetel objek layanan, yang akan kita 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 bahwa tes tersebut memiliki tiga divisi yang jelas: Baris 3-9 — menentukan perlengkapan. Baris 11 — mengeksekusi kode yang sedang diuji. Baris 13-17 — memeriksa hasilnya. Secara lebih rinci: Baris 3-4 — menyetel perilaku tiruan DAO. Baris 5 — setel instance yang akan kami perbarui di atas standar kami. Baris 11 — gunakan metode ini dan ambil contoh yang dihasilkan. Baris 13 — periksa apakah ini bukan nol. Baris 14 — bandingkan ID hasil dan argumen metode yang diberikan. Baris 15 — periksa apakah nama telah diperbarui. Baris 16 — lihat hasil CPU. Baris 17 — kita tidak menentukan kolom ini di contoh, jadi harus tetap sama. Kami memeriksa kondisi itu di sini. Mari kita jalankan:Semua tentang pengujian unit: teknik, konsep, praktik - 6Tesnya hijau! Kami bisa bernapas lega :) Singkatnya, pengujian meningkatkan kualitas kode dan membuat proses pengembangan lebih fleksibel dan andal. Bayangkan berapa banyak usaha yang diperlukan untuk mendesain ulang perangkat lunak yang melibatkan ratusan file kelas. Saat kami memiliki tes unit yang ditulis untuk semua kelas ini, kami dapat melakukan refactor dengan percaya diri. Dan yang terpenting, ini membantu kami menemukan bug dengan mudah selama pengembangan. Teman-teman, hanya itu yang saya punya hari ini. Beri aku suka, dan tinggalkan komentar :)
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION