JavaRush /Java Blog /Random-ID /Pengujian Unit Java: teknik, konsep, praktik

Pengujian Unit Java: teknik, konsep, praktik

Dipublikasikan di grup Random-ID
Saat ini Anda hampir tidak akan menemukan aplikasi yang tidak tercakup dalam pengujian, jadi topik ini akan lebih relevan dari sebelumnya untuk pengembang pemula: tanpa pengujian Anda tidak dapat mencapai apa pun. Sebagai iklan, saya sarankan Anda melihat artikel saya sebelumnya. Beberapa di antaranya mencakup tes (dan meskipun demikian artikelnya akan sangat berguna):
  1. Pengujian integrasi database menggunakan MariaDB untuk menggantikan MySql
  2. Implementasi aplikasi multibahasa
  3. Menyimpan file ke aplikasi dan data tentangnya ke database
Mari kita pertimbangkan jenis pengujian apa yang digunakan pada prinsipnya, dan setelah itu kita akan mempelajari secara detail semua yang perlu Anda ketahui tentang pengujian unit.

Jenis pengujian

Apa itu tes? Seperti yang dikatakan Wiki: “ Tes atau pengujian adalah cara mempelajari proses yang mendasari suatu sistem dengan menempatkan sistem dalam situasi yang berbeda dan melacak perubahan yang dapat diamati di dalamnya.” Dengan kata lain, ini adalah ujian atas berfungsinya sistem kami dengan benar dalam situasi tertentu. Semua tentang pengujian Unit: metode, konsep, praktik - 2Baiklah, mari kita lihat apa saja jenis pengujiannya:
  1. Pengujian unit adalah pengujian yang tugasnya menguji setiap modul sistem secara individual. Diinginkan bahwa ini merupakan bagian sistem yang dapat dibagi secara minimal, misalnya modul.

  2. Pengujian sistem adalah pengujian tingkat tinggi untuk menguji pengoperasian bagian yang lebih besar dari suatu aplikasi atau sistem secara keseluruhan.

  3. Pengujian regresi adalah pengujian yang digunakan untuk memeriksa apakah fitur baru atau perbaikan bug mempengaruhi fungsionalitas aplikasi yang ada dan apakah bug lama muncul kembali.

  4. Pengujian fungsional memeriksa kepatuhan bagian aplikasi dengan persyaratan yang tercantum dalam spesifikasi, cerita pengguna, dll.

    Jenis pengujian fungsional:

    • Uji “kotak putih” untuk kesesuaian bagian aplikasi dengan persyaratan dengan pengetahuan tentang implementasi internal sistem;
    • Uji “kotak hitam” untuk kesesuaian sebagian aplikasi dengan persyaratan tanpa mengetahui implementasi internal sistem.
  5. Pengujian kinerja adalah jenis pengujian yang ditulis untuk menentukan kecepatan suatu sistem atau bagiannya berjalan di bawah beban tertentu.
  6. Pengujian beban - pengujian yang dirancang untuk memeriksa stabilitas sistem di bawah beban standar dan untuk menemukan puncak maksimum yang memungkinkan aplikasi bekerja dengan benar.
  7. Pengujian stres adalah jenis pengujian yang dirancang untuk memeriksa fungsionalitas aplikasi di bawah beban non-standar dan untuk menentukan puncak maksimum yang mungkin terjadi di mana sistem tidak akan mogok.
  8. Pengujian keamanan - pengujian yang digunakan untuk memeriksa keamanan sistem (dari serangan peretas, virus, akses tidak sah ke data rahasia, dan kesenangan hidup lainnya).
  9. Pengujian lokalisasi adalah pengujian lokalisasi untuk suatu aplikasi.
  10. Pengujian kegunaan adalah jenis pengujian yang bertujuan untuk memeriksa kegunaan, pemahaman, daya tarik, dan kemampuan belajar bagi pengguna.
  11. Ini semua kedengarannya bagus, tapi bagaimana cara kerjanya dalam praktik? Sederhana saja: Piramida pengujian Mike Cohn digunakan: Semua tentang pengujian Unit: teknik, konsep, praktik - 4Ini adalah versi piramida yang disederhanakan: sekarang dibagi menjadi beberapa bagian yang lebih kecil. Tapi hari ini kita tidak akan memutarbalikkan dan mempertimbangkan opsi paling sederhana.
    1. Unit - pengujian unit yang digunakan di berbagai lapisan aplikasi, menguji logika aplikasi terkecil yang dapat dibagi: misalnya, kelas, tetapi paling sering metode. Pengujian ini biasanya mencoba mengisolasi sebanyak mungkin dari logika eksternal, yaitu menciptakan ilusi bahwa aplikasi lainnya bekerja dalam mode standar.

      Tes ini harus selalu ada banyak (lebih banyak dari jenis lainnya), karena tes ini menguji bagian-bagian kecil dan sangat ringan, tidak memakan banyak sumber daya (yang saya maksud dengan sumber daya adalah RAM dan waktu).

    2. Integrasi - pengujian integrasi. Ia memeriksa bagian sistem yang lebih besar, yaitu kombinasi beberapa bagian logika (beberapa metode atau kelas), atau kebenaran bekerja dengan komponen eksternal. Biasanya jumlah tes ini lebih sedikit daripada tes Unit, karena lebih berat.

      Sebagai contoh pengujian integrasi, Anda dapat mempertimbangkan untuk menyambung ke database dan memeriksa apakah metode yang bekerja dengannya berfungsi dengan benar .

    3. UI - tes yang memeriksa pengoperasian antarmuka pengguna. Mereka mempengaruhi logika di semua level aplikasi, itulah sebabnya mereka juga disebut end-to-end. Biasanya, jumlahnya jauh lebih sedikit, karena mereka adalah yang paling kelas berat dan harus memeriksa jalur yang paling diperlukan (bekas).

      Pada gambar di atas kita melihat rasio luas bagian-bagian berbeda dari segitiga: kira-kira proporsi yang sama dipertahankan dalam jumlah pengujian ini dalam pekerjaan nyata.

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

    Konsep kunci pengujian unit

    Cakupan pengujian (Code Coverage) merupakan salah satu penilaian utama terhadap kualitas pengujian aplikasi. Ini adalah persentase kode yang tercakup dalam pengujian (0-100%). Dalam praktiknya, banyak orang mengejar persentase ini, yang saya tidak setuju, karena mereka mulai menambahkan tes yang tidak diperlukan. Misalnya, layanan kami memiliki operasi CRUD (buat/dapatkan/perbarui/hapus) standar tanpa logika tambahan. Metode-metode ini murni perantara yang mendelegasikan pekerjaan ke lapisan yang bekerja dengan repositori. Dalam situasi ini, kita tidak perlu menguji apa pun: mungkin apakah metode ini memanggil metode dari Tao, tetapi ini tidak serius. Untuk menilai cakupan tes, biasanya digunakan alat tambahan: JaCoCo, Cobertura, Clover, Emma, ​​​​dll. Untuk mempelajari lebih detail tentang masalah ini, simpanlah beberapa artikel yang sesuai: TDD (Pengembangan berbasis pengujian) - pengembangan berbasis pengujian. Dalam pendekatan ini, pertama-tama, sebuah tes ditulis yang akan memeriksa kode tertentu. Ternyata ini merupakan pengujian kotak hitam: kita mengetahui apa yang ada pada masukan dan kita mengetahui apa yang seharusnya terjadi pada keluaran. Ini menghindari duplikasi kode. Pengembangan berbasis pengujian dimulai dengan merancang dan mengembangkan pengujian untuk setiap fungsionalitas kecil aplikasi. Dalam pendekatan TDD, pertama-tama, tes dikembangkan untuk mendefinisikan dan memverifikasi apa yang akan dilakukan kode. Tujuan utama TDD adalah membuat kode lebih jelas, sederhana, dan bebas kesalahan. Semua tentang pengujian Unit: metode, konsep, praktik - 6Pendekatan ini terdiri dari komponen-komponen berikut:
    1. Kami sedang menulis tes kami.
    2. Kami menjalankan tes, apakah lulus atau tidak (kami melihat semuanya berwarna merah - jangan panik: begitulah seharusnya).
    3. Kami menambahkan kode yang harus memenuhi pengujian ini (jalankan pengujian).
    4. Kami memfaktorkan ulang kodenya.
    Berdasarkan fakta bahwa pengujian unit adalah elemen terkecil dalam piramida otomatisasi pengujian, TDD didasarkan pada elemen tersebut. Dengan bantuan unit test kita dapat menguji logika bisnis kelas mana pun. BDD (Perkembangan yang digerakkan oleh perilaku) - pengembangan melalui perilaku. Pendekatan ini didasarkan pada TDD. Lebih khusus lagi, ini menggunakan contoh-contoh yang ditulis dalam bahasa yang jelas (biasanya dalam bahasa Inggris) yang menggambarkan perilaku sistem untuk semua orang yang terlibat dalam pengembangan. Kami tidak akan membahas istilah ini lebih dalam, karena istilah ini terutama memengaruhi penguji dan analis bisnis. Test Case - skrip yang menjelaskan langkah-langkah, kondisi spesifik, dan parameter yang diperlukan untuk memverifikasi implementasi kode yang diuji. Fixture adalah keadaan lingkungan pengujian yang diperlukan untuk keberhasilan pelaksanaan metode yang diuji. Ini adalah sekumpulan objek yang telah ditentukan sebelumnya dan perilakunya dalam kondisi yang digunakan.

    Tahapan pengujian

    Tes ini terdiri dari tiga tahap:
    1. Menentukan data yang akan diuji (fixtures).
    2. Menggunakan kode yang sedang diuji (memanggil metode yang sedang diuji).
    3. Memeriksa hasilnya dan membandingkannya dengan yang diharapkan.
    Semua tentang pengujian Unit: metode, konsep, praktik - 7Untuk memastikan modularitas pengujian, Anda perlu diisolasi dari lapisan aplikasi lainnya. Hal ini dapat dilakukan dengan menggunakan stub, mocks dan spies. Mock adalah objek yang dapat disesuaikan (misalnya, khusus untuk setiap pengujian) dan memungkinkan Anda menetapkan ekspektasi untuk pemanggilan metode dalam bentuk respons yang kami rencanakan untuk diterima. Pemeriksaan ekspektasi dilakukan melalui panggilan ke objek Mock. Stub - memberikan respons terprogram terhadap panggilan selama pengujian. Mereka juga dapat menyimpan informasi tentang panggilan tersebut (misalnya, parameter atau nomor panggilan tersebut). Ini kadang-kadang disebut dengan istilah mereka sendiri - mata-mata ( Spy ). Terkadang istilah stub dan mock ini membingungkan: perbedaannya adalah stub tidak memeriksa apa pun, tetapi hanya mensimulasikan keadaan tertentu. Tiruan adalah objek yang memiliki ekspektasi. Misalnya, metode kelas tertentu harus dipanggil beberapa kali. Dengan kata lain, pengujian Anda tidak akan pernah rusak karena adanya stub, namun mungkin rusak karena tiruan.

    Lingkungan pengujian

    Jadi sekarang mari kita mulai berbisnis. Ada beberapa lingkungan pengujian (kerangka kerja) yang tersedia untuk Java. Yang paling populer adalah JUnit dan TestNG. Untuk ulasan kami, kami menggunakan: Semua tentang pengujian Unit: metode, konsep, praktik - 8Tes JUnit adalah metode yang terdapat dalam kelas yang hanya digunakan untuk pengujian. Sebuah kelas biasanya diberi nama yang sama dengan kelas yang diujinya dengan +Test di akhir. Misalnya, CarService→ CarServiceTest. Sistem pembangunan Maven secara otomatis menyertakan kelas-kelas tersebut di area pengujian. Sebenarnya kelas ini disebut kelas tes. Mari kita bahas sedikit anotasi dasarnya: @Test - definisi metode ini sebagai metode pengujian (sebenarnya, metode yang ditandai dengan anotasi ini adalah pengujian unit). @Sebelum - menandai metode yang akan dieksekusi sebelum setiap pengujian. Misalnya, mengisi data pengujian kelas, membaca data masukan, dll. @After - ditempatkan di atas metode yang akan dipanggil setelah setiap pengujian (membersihkan data, memulihkan nilai default). @BeforeClass - ditempatkan di atas metode - analog dengan @Before. Namun metode ini dipanggil hanya sekali sebelum semua pengujian untuk kelas tertentu dan oleh karena itu harus bersifat statis. Ini digunakan untuk melakukan operasi yang lebih berat, seperti mengangkat database pengujian. @AfterClass adalah kebalikan dari @BeforeClass: dieksekusi sekali untuk kelas tertentu, tetapi dieksekusi setelah semua pengujian. Digunakan, misalnya, untuk membersihkan sumber daya persisten atau memutuskan sambungan dari database. @Ignore - mencatat bahwa metode di bawah ini dinonaktifkan dan akan diabaikan saat menjalankan tes secara keseluruhan. Ini digunakan dalam kasus yang berbeda, misalnya, jika metode dasar diubah dan tidak ada waktu untuk mengulangi pengujiannya. Dalam kasus seperti itu, disarankan juga untuk menambahkan deskripsi - @Ignore("Beberapa deskripsi"). @Test (diharapkan = Exception.class) - digunakan untuk tes negatif. Ini adalah pengujian yang memeriksa bagaimana suatu metode berperilaku jika terjadi kesalahan, yaitu pengujian mengharapkan metode tersebut mengeluarkan beberapa pengecualian. Metode seperti ini dilambangkan dengan anotasi @Test, tetapi dengan kesalahan yang harus ditangkap. @Test(timeout=100) - memeriksa apakah metode dijalankan dalam waktu tidak lebih dari 100 milidetik. @Mock - kelas digunakan pada bidang untuk menyetel objek tertentu sebagai tiruan (ini bukan dari perpustakaan Junit, tetapi dari Mockito), dan jika kita membutuhkannya, kita akan mengatur perilaku tiruan dalam situasi tertentu , langsung dalam metode pengujian. @RunWith(MockitoJUnitRunner.class) - metode ditempatkan di atas kelas. Ini adalah tombol untuk menjalankan tes di dalamnya. Pelarinya bisa berbeda-beda: misalnya, ada yang berikut ini: MockitoJUnitRunner, JUnitPlatform, SpringRunner, dll.). Di JUnit 5, anotasi @RunWith digantikan oleh anotasi @ExtendWith yang lebih kuat. Mari kita lihat beberapa metode untuk membandingkan hasil:
    • assertEquals(Object expecteds, Object actuals)— memeriksa apakah objek yang ditransmisikan sama.
    • assertTrue(boolean flag)— memeriksa apakah nilai yang diteruskan mengembalikan nilai true.
    • assertFalse(boolean flag)— memeriksa apakah nilai yang diteruskan menghasilkan nilai salah.
    • assertNull(Object object)– memeriksa apakah objeknya null.
    • assertSame(Object firstObject, Object secondObject)— memeriksa apakah nilai yang diteruskan merujuk ke objek yang sama.
    • assertThat(T t, Matcher<T> matcher)— memeriksa apakah t memenuhi kondisi yang ditentukan dalam pencocokan.
    Ada juga bentuk perbandingan yang berguna dari Asserj - assertThat(firstObject).isEqualTo(secondObject) Di sini saya berbicara tentang metode dasar, karena sisanya merupakan variasi berbeda dari metode di atas.

    Latihan pengujian

    Sekarang mari kita lihat materi di atas dengan menggunakan contoh spesifik. Kami akan menguji metode layanan - perbarui. Kami tidak akan mempertimbangkan lapisan dao, karena ini adalah default kami. 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>
    Jadi, kelas layanannya:
    
    @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());
       }
    }
    8 - tarik objek yang diperbarui dari database 9-14 - buat objek melalui pembuat, jika objek yang masuk memiliki bidang - atur, jika tidak - tinggalkan apa yang ada di database Dan 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);
       }
    1 - Pelari kita 4 - mengisolasi layanan dari lapisan dao dengan mengganti tiruan 11 - menetapkan entitas pengujian untuk kelas (yang akan kita gunakan sebagai hamster pengujian) 22 - menetapkan 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 pembagian tes yang jelas menjadi tiga bagian: 3-9 - mengatur perlengkapan 11 - melaksanakan bagian yang diuji 13-17 - memeriksa hasil Lebih detail: 3-4 - mengatur perilaku untuk moka dao 5 - mengatur sebuah instance yang akan kami perbarui di atas standar kami 11 - gunakan metode ini dan ambil contoh yang dihasilkan 13 - periksa apakah itu bukan nol 14 - periksa ID hasil dan argumen metode yang ditentukan 15 - periksa apakah namanya telah diperbarui 16 - lihat pada hasil cpu 17 - karena kita tidak menyetel ini di kolom instance pembaruan, seharusnya tetap sama, mari kita periksa. Semua tentang pengujian Unit: metode, konsep, praktik - 9Mari kita luncurkan: Semua tentang pengujian Unit: teknik, konsep, praktik - 10Tesnya berwarna hijau, Anda bisa menghembuskannya)) Jadi, mari kita rangkum: pengujian meningkatkan kualitas kode dan membuat proses pengembangan lebih fleksibel dan dapat diandalkan. Bayangkan berapa banyak usaha yang harus kita keluarkan saat mendesain ulang perangkat lunak dengan ratusan file kelas. Setelah pengujian unit kami ditulis untuk semua kelas ini, kami dapat melakukan refaktorisasi dengan percaya diri. Dan yang paling penting, ini membantu kita dengan mudah menemukan kesalahan selama pengembangan. Teman-teman, itu saja untuk saya hari ini: tuangkan suka, tulis komentar))) Semua tentang pengujian Unit: metode, konsep, praktik - 11
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION