Pengujian unit

Manfaat

Tujuan dari pengujian unit adalah untuk mengisolasi setiap bagian dari program dan menunjukkan bahwa bagian-bagian individu program tersebut benar (sahih). Sebuah pengujian unit menyediakan kontrak tertulis yang ketat yang harus dipenuhi oleh sepotong kode program. Ada berbagai manfaat yang bisa didapat dari proses ini.

Menemukan masalah dari awal

Pengujian unit menemukan masalah di awal dalam siklus pengembangan. Ini termasuk kekutu (Inggris: bug) dalam implementasi pemrogram dan kekurangan atau bagian-bagian yang hilang dari spesifikasi untuk unit tersebut. Proses penulisan kumpulan uji menyeluruh dari memaksa penulis untuk memikirkan masak-masak masukan, keluaran, dan kondisi kesalahan, dan dengan demikian lebih tajam mendefinisikan perilaku unit yang diinginkan. Biaya untuk menemukan kekutu sebelum penulisan kode dimulai atau ketika kode ini pertama kali ditulis jauh lebih rendah daripada biaya mendeteksi, mengidentifikasi, dan memperbaiki bug sesudahnya; kekutu juga dapat menyebabkan masalah bagi pengguna akhir dari perangkat lunak. Beberapa berpendapat bahwa kode yang tidak mungkin atau sulit dibikin pengujian unitnya adalah kode yang ditulis dengan buruk. Dengan demikian pengujian unit dapat memaksa pengembang untuk membuat struktur fungsi dan objek dalam cara yang lebih baik.
Dalam pengembangan digerakkan pengujian (test-driven development atau TDD), yang sering digunakan dalam pemrograman ekstrem dan scrum, pengujian unit diciptakan sebelum kode itu sendiri ditulis. Ketika lulus tes, kode itu dianggap selesai. Pengujian unit yang sama dijalankan terhadap fungsi itu berkali-kali seiring dengan pengembangan basis kode yang semakin besar, baik saat kode itu berubah atau melalui proses yang diotomatisasi. Jika pengujian unit gagal, hal ini dianggap sebagai kekutu, baik di dalam kode yang sudah berubah itu atau dalam pengujian unit itu sendiri. Pengujian unit kemudian memungkinkan tempat kesalahan atau kegagalan tersebut dengan mudah dilacak. Karena pengujian unit memperingatkan tim pengembang akan masalah tersebut sebelum kode program diserahkan ke untuk penguji atau klien, ini berarti masalah ditemukan masih dalam awal proses pengembangan perangkat lunak.

Memfasilitasi perubahan

Pengujian unit memungkinkan pemrogram untuk merefaktor kode atau meng-upgrade pustaka sistem di kemudian hari, dan memastikan modul ini masih bekerja dengan benar (misalnya, dalam pengujian regresi). Prosedurnya adalah dengan menulis kasus uji untuk semua fungsi dan metode sehingga perubahan yang menyebabkan kesalahan, dapat dengan cepat diidentifikasi. Pengujian unit mendeteksi perubahan yang mungkin melanggar kontrak desain.

Menyederhanakan integrasi

Pengujian unit dapat mengurangi ketidakpastian dalam unit itu sendiri dan dapat digunakan dalam pengujian bergaya pendekatan bottom-up . Dengan menguji bagian-bagian dari sebuah program terlebih dahulu, dan baru kemudian pengujian keseluruhan bagian secara terpadu, pengujian integrasi menjadi lebih mudah.

Dokumentasi

Pengujian unit menyediakan semacam dokumentasi hidup dari sistem. Pengembang yang ingin mempelajari apa saja fungsi yang disediakan sebuah unit, dan bagaimana menggunakannya, dapat melihat pengujian unit untuk mendapatkan pemahaman dasar tentang antarmuka pemrograman unit tersebut (API).
Kasus uji sebuah unit mengandung sifat-sifat yang sangat penting untuk keberhasilan unit tersebut. Sifat-sifat ini dapat menunjukkan penggunaan unit, baik yang tepat ataupun tidak layak, serta perilaku negatif yang akan dijebak oleh unit tersebut. Kasus uji sebuah unit mendokumentasikan semua karakteristik penting ini, meskipun banyak lingkungan pengembangan perangkat lunak yang tidak mengandalkan kode untuk mendokumentasikan produk yang sedang dikembangkan.

Desain

Ketika perangkat lunak dikembangkan dengan menggunakan pendekatan digerakkan pengujian, kombinasi dari menulis pengujian unit untuk menentukan antarmuka, ditambah dengan kegiatan refaktorisasi yang dilakukan setelah kelulusan uji, dapat mengambil alih desain formal. Masing-masing pengujian unit dapat dilihat sebagai elemen desain untuk menentukan kelas-kelas, metode, dan perilaku yang dapat diamati. Berikut contoh dalam bahasa pemrograman Java yang akan membantu menjelaskan hal ini.
Berikut ini adalah sekumpulan kasus uji yang menentukan sejumlah unsur-unsur implementasi. Pertama, harus ada sebuah antarmuka yang disebut Adder, dan kelas yang mengimplementasi dengan konstruktor tanpa argumen yang disebut AdderImpl. Kemudian kasus uji ini menegaskan bahwa antarmuka Adder harus memiliki sebuah metode yang disebut add, dengan dua parameter integer (bilangan bulat), yang menghasilkan integer pula. Kasus uji ini juga menentukan perilaku dari metode ini untuk kisaran nilai tertentu pada serangkaian metode uji.
import static org.junit.Assert.*;import org.junit.Test;public class TestAdder {

    public void testSumPositiveNumbersOneAndOne() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 1) == 2);
    }

    // can it add the positive numbers 1 and 2?
    @Test
    public void testSumPositiveNumbersOneAndTwo() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 2) == 3);
    }

    // can it add the positive numbers 2 and 2?
    @Test
    public void testSumPositiveNumbersTwoAndTwo() {
        Adder adder = new AdderImpl();
        assert(adder.add(2, 2) == 4);
    }

    // is zero neutral?
    @Test
    public void testSumZeroNeutral() {
        Adder adder = new AdderImpl();
        assert(adder.add(0, 0) == 0);
    }

    // can it add the negative numbers -1 and -2?
    @Test
    public void testSumNegativeNumbers() {
        Adder adder = new AdderImpl();
        assert(adder.add(-1, -2) == -3);
    }

    // can it add a positive and a negative?
    @Test
    public void testSumPositiveAndNegative() {
        Adder adder = new AdderImpl();
        assert(adder.add(-1, 1) == 0);
    }

    // how about larger numbers?
    @Test
    public void testSumLargeNumbers() {
        Adder adder = new AdderImpl();
        assert(adder.add(1234, 988) == 2222);
    }}
Dalam hal ini pengujian unit, karena telah ditulis terlebih dahulu, bertindak sebagai dokumen desain yang menentukan bentuk dan perilaku dari solusi yang diinginkan, tapi tanpa perincian pelaksanaan. Perincian ini diserahkan kepada pemrogram. Mengikuti praktik "melakukan hal yang paling sederhana yang mungkin bisa bekerja", solusi termudah yang akan lulus pengujian ditunjukkan di bawah ini.
interface Adder {
    int add(int a, int b);}class AdderImpl implements Adder {
    public int add(int a, int b) {
        return a + b;
    }}
Tidak seperti metode desain berbasis diagram lainnya, menggunakan pengujian unit sebagai desain spesifikasi memiliki satu keuntungan penting. Dokumen desain (pengujian unit itu sendiri) dapat digunakan untuk memverifikasi bahwa implementasi mematuhi desain. Dengan metode desain pengujian unit, kode tidak akan pernah lulus uji jika pengembang tidak melaksanakan solusi sesuai dengan desain.
Memang benar bahwa pengujian unit tidak lebih mudah dipahami dibandingkan diagram, tetapi diagram UML saat ini dengan mudah dihasilkan buat sebagian besar bahasa-bahasa pemrograman modern menggunakan alat-alat gratis (biasanya tersedia sebagai ekstensi untuk IDEs). Alat-alat gratis ini, seperti yang berdasarkan kepada kerangka kerja xUnit kerangka, mengalihkan ke sistem lain tampilan grafis diagram untuk konsumsi manusia.

Pemisahan antarmuka dari pelaksanaan

Karena beberapa kelas dapat memiliki referensi ke kelas-kelas lain, pengujian suatu kelas kerap kali berdampak ke pengujian kelas lain. Sebuah contoh umum dari hal ini adalah kelas yang tergantung pada sebuah basis data: untuk menguji kelas, penguji sering menulis kode yang berinteraksi dengan basis data. Ini adalah kesalahan, karena pengujian unit biasanya melanggar ke luar batas kelas itu sendiri, dan terutama tidak boleh menyeberangi batas proses/jaringan itu karena dapat memasukkan masalah kinerja yang tidak dapat diterima terhadap kumpulan pengujian unit. Melanggar batas-batas unit tersebut mengubah pengujian unit menjadi pengujian integrasi, dan ketika kasus uji gagal, kurang jelas komponen mana yang menyebabkan kegagalan.
Sebaliknya, pengembang perangkat lunak harus membuat antarmuka abstrak untuk permohonan (query) basis data, dan kemudian menerapkan antarmuka tersebut dengan objek pura-pura mereka sendiri. Dengan mengabstraksi keterkaitan yang diperlukan ini dari kode, unit independen dapat menjadi lebih benar-benar teruji dibandingkan yang sebelumnya mungkin tercapai. Hasilnya adalah unit yang lebih berkualitas tinggi dan juga lebih mudah dipelihara.

Pengujian unit terparameterisasi

Pengujian unit terparameterisasi (PUT, parameterized unit tests) adalah uji yang menerima parameter. Tidak seperti pengujian unit tradisional yang biasanya merupakan metode tertutup, PUT menerima parameter mana pun. PUT telah didukung oleh TestNG, JUnit dan berbagai kerangka kerja pengujian .NET. Parameter yang cocok untuk pengujian unit dapat diberikan secara manual atau dalam beberapa kasus dihasilkan secara otomatis oleh kerangka kerja pengujian. Alat-alat pengujian seperti QuickCheck ada untuk menghasilkan masukan pengujian untuk PUT.

Keterbatasan pengujian unit

Pengujian tidak akan menangkap setiap kesalahan dalam program, karena proses ini tidak dapat mengevaluasi setiap jalur eksekusi kecuali pada program yang paling sepele. Masalah ini merupakan superset dari masalah penghentian (halting problem), yang tergolong undecidable. Hal yang sama berlaku untuk pengujian unit.
Selain itu, pengujian unit secara definisi hanya menguji fungsi dari unit itu sendiri. Oleh karena itu, pengujian tidak akan menangkap galat integrasi atau galat aras sistem yang lebih luas (seperti fungsi-fungsi yang dilakukan melintasi beberapa unit, atau wilayah uji non-fungsional, seperti kinerja). Pengujian unit harus dilakukan bersama-sama dengan kegiatan pengujian perangkat lunak lainnya, karena pengujian unit hanya dapat menunjukkan ada atau tidak adanya kesalahan tertentu, dan tidak dapat membuktikan unit bersih dari kesalahan. Untuk menjamin perilaku yang benar untuk setiap jalur eksekusi dan untuk setiap kemungkinan masukan, dan untuk memastikan tidak adanya kesalahan, diperlukan teknik yang lain. Teknik ini adalah penerapan metode formal untuk membuktikan bahwa komponen perangkat lunak tidak memiliki perilaku tak terduga.
Hierarki pengujian unit yang rumit tidak sama dengan pengujian integrasi. Integrasi dengan unit pinggiran (periferal) harus dimasukkan dalam pengujian integrasi, tapi tidak diperlukan dalam pengujian unit. Pengujian integrasi biasanya masih sangat bergantung pada pengujian secara manual oleh manusia; Pengujian tingkat tinggi atau pengujian ruang lingkup global bisa jadi sukar untuk diotomatisasi, sehingga pengujian manual sering lebih cepat dan lebih murah.
Pengujian perangkat lunak adalah masalah kombinatorial. Misalnya, setiap pernyataan keputusan boolean membutuhkan setidaknya dua uji: satu dengan hasil yang "benar" dan satu dengan hasil "salah". Akibatnya, untuk setiap baris kode yang ditulis, pemrogram sering membutuhkan 3 sampai 5 baris kode uji. hal Ini jelas membutuhkan waktu dan investasi yang mungkin tidak layak. Ada juga banyak masalah yang tidak dapat dengan mudah diuji sama sekali – misalnya masalah yang nondeterministik atau melibatkan beberapa utas (thread). Selain itu, kode untuk pengujian unit bisa jadi memiliki kekutu sama banyaknya dengan kode program yang diujinya. Fred Brooks dalam The Mythical Man-Month menyebutkan: "Jangan pernah pergi ke laut dengan dua kronometer; bawa satu atau tiga." Artinya, jika dua kronometer bertentangan, bagaimana Anda tahu mana yang benar?
Tantangan lain yang terkait dengan menulis pengujian unit adalah kesulitan menciptakan uji yang realistis dan berguna. Kondisi awal yang relevan sangat perlu diciptakan agar aplikasi yang diuji berperilaku seperti bagian dari sistem yang lengkap. Jika kondisi awal ini tidak diatur dengan benar, pengujian tidak akan menggerakkan kode dalam konteks realistis, yang akan mengurangi nilai dan akurasi dari hasil pengujian unit.
Untuk mendapatkan manfaat yang diharapkan dari pengujian unit, disiplin ketat diperlukan sepanjang proses pengembangan perangkat lunak. Penting untuk mencatat dengan hati-hati tidak hanya uji yang telah dilakukan, tetapi juga dari semua perubahan yang telah dilakukan ke kode sumber unit tersebut atau unit lain dalam perangkat lunak. Penggunaansistem pengontrol versi adalah penting. Jika versi yang lebih baru dari suatu unit gagal melalui uji tertentu yang sebelumnya berhasil dilewati, perangkat lunak pengontrol versi dapat memberikan daftar perubahan kode sumber yang telah diterapkan (jika ada) terhadap unit sejak saat itu.
Hal penting pula untuk menerapkan proses yang berkelanjutan untuk memastikan bahwa kegagalan kasus uji ditinjau setiap hari dan segera diatasi. Jika proses tersebut tidak dilaksanakan dan tidak tertanam ke dalam alur kerja tim, aplikasi akan berkembang liar, tidak lagi selaras dengan kumpulan pengujian unit, meningkatkan positif palsu dan mengurangi keefektifan kumpulan pengujian.
Pengujian unit perangkat lunak sistem benam (embedded system) menghadirkan tantangan unik: karena perangkat lunak tersebut dikembangkan pada landasan yang berbeda dari landasan yang nantinya akan menjalankannya, Anda tidak dapat dengan mudah menjalankan program pengujian pada lingkungan pelaksanaan (deployment) sebenarnya, seperti yang mungkin dilakukan dengan program desktop.

Pemrograman ekstrem

Pengujian unit adalah dasar dari pemrograman ekstrem, yang bergantung pada kerangka kerja pengujian unit terotomatisasi. Kerangka kerja pengujian unit terotomatisasi ini mungkin berasal dari pihak ketiga, misalnya, xUnit, atau diciptakan dalam kelompok pengembangan perangkat lunak itu sendiri.
Pemrograman ekstrem menggunakan penciptaan pengujian unit untuk pengembangan digerakkan pengujian. Pengembang menulis pengujian unit yang mengekspos baik persyaratan atau cacat suatu perangkat lunak. Uji ini akan gagal karena salah satu persyaratan belum diimplementasikan, atau karena sengaja memperlihatkan cacat dalam kode yang sudah ada. Kemudian, pengembang menulis kode sederhana untuk agar unit lulus uji tersebut, bersama dengan uji yang lainnya.
Sebagian besar kode dalam sistem melewati pengujian unit, tetapi belum tentu menguji semua jalur dalam kode. Pemrograman ekstrem memandatkan strategi "menguji segala sesuatu yang mungkin gagal", dan menghindari metode tradisional "menguji setiap jalur eksekusi". Hal ini menyebabkan pengembang untuk menciptakan uji yang lebih sedikit dari metode klasik, tapi ini tidak benar-benar masalah. Metode klasik pun jarang pernah diikuti secara ketat sehingga tidak semua jalur eksekusi telah diuji secara menyeluruh. Pemrograman ekstrem hanya mengakui bahwa pengujian jarang dilakukan secara menyeluruh (karena sering terlalu mahal dan memakan waktu ) dan memberikan panduan tentang bagaimana memusatkan sumber daya yang terbatas secara efektif.
Kode pengujian ini dianggap artefak proyek kelas satu, dalam artian dipertahankan pada kualitas yang sama dengan kode implementasi, dengan menghapus semua duplikasi kode. Pengembang merilis pengujian unit ke repositori kode bersama-sama dengan kode yang diujinya. Pengujian unit yang menyeluruh dari pemrograman ekstrem memungkinkan manfaat yang disebutkan di atas, seperti pengembangan kode yang dan refaktorisasi lebih sederhana dan lebih meyakinkan, integrasi kode yang disederhanakan, dokumentasi yang akurat, dan desain yang lebih modular. Pengujian unit ini juga terus berjalan sebagai bentuk dari uji regresi.
Pengujian unit juga penting untuk konsep desain emergen. Karena desain emergen sangat tergantung pada refaktorisasi, pengujian unit merupakan satu komponen tidak terpisahkan.

Teknik

Pengujian uni ini umumnya otomatis, tapi mungkin masih dilakukan secara manual. IEEE tidak menganjurkan salah satunya dibandingkan yang lain. Tujuanpengujian unit ini adalah untuk mengisolasi suatu unit dan memvalidasi kebenarannya. Pendekatan manual dari pengujian unit dapat menggunakan dokumen yang berisi perintah langkah demi langkah. Namun ini dapat dilakukan dengan efisien dengan otomatisasi, dan memungkinkan banyak manfaat yang tercantum dalam artikel ini. Sebaliknya, jika tidak direncanakan dengan hati-hati, kasus pengujian unit yang ceroboh dapat dieksekusi sebagai kasus uji integrasi yang melibatkan banyak komponen-komponen perangkat lunak, dan dengan demikian menghalangi pencapaian kebanyakan tujuan yang ditetapkan untuk pengujian unit, atau malah menggagalkan semuanya.
Untuk sepenuhnya merealisasikan efek isolasi saat menggunakan pendekatan otomatis, unit atau badan kode yang diuji dieksekusi di dalam kerangka kerja di luar lingkungan alamiahnya. Dengan kata lain, unit tersebut dieksekusi di luar produk atau konteks tempat unit tersebut diciptakan. Pengujian dengan cara terisolasi seperti ini mengungkapkan ketergantungan antara kode dalam unit yang sedang diuji dan unit-unit lain atau ruang data dalam produk. Ketergantungan ini kemudian dapat dihilangkan.
Dengan menggunakan kerangka kerja otomatisasi, pengembang mengkodekan kriteria, atau hasil yang diketahui benar, ke dalam uji untuk memeriksa kebenaran unit tersebut. Dalam eksekusi kasus uji, kerangka kerja mencatat uji yang gagal melewati kriteria apa pun. Banyak kerangka kerja juga akan menandai secara otomatis kasus uji yang gagal ini dan melaporkannya dalam rangkuman. Tergantung kepada parahnya kegagalan yang terjadi, kerangka kerja mungkin akan berhenti melakukan uji selanjutnya.
Karena itu, pengujian unit secara tradisional adalah pemotivasi pemrogram untuk menciptakan kode yang independen dan kohesif. Praktik ini mempromosikan kebiasaan yang sehat dalam pengembangan perangkat lunak. Pola desain, pengujian unit, dan refaktorisasi sering berjalan bersama-sama sehingga akhirnya muncul solusi terbaik.

Kerangka kerja unit pengujian

Kerangka kerja unit pengujian biasanya adalah produk pihak ketiga yang tidak didistribusikan sebagai bagian dari rangkaian pengkompilasi (compiler suite). Kerangka kerja ini membantu menyederhanakan proses pengujian unit, dan telah dikembangkan untuk berbagai macam bahasa pemrograman. Contoh kerangka kerja pengujian mencakup solusi sumber terbuka seperti berbagai kerangka kerja yang dikenal secara kolektif sebagai xUnit, dan solusi komersial seperti Typemock Isolator.NET/Isolator++, TBrun, JustMock, Parasoft Development Testing (Jtest, Parasoft C/C++tes, dotTEST), Testwell CTA++ dan VectorCAST/C++.
Biasanya kita dapat melakukan pengujian unit tanpa dukungan dari kerangka kerja tertentu dengan menulis kode klien yang menjalankan unit yang diuji test dan menggunakan penegasan, penanganan eksepsi, atau mekanisme pengendali aliran lainnya untuk memberi tanda kegagalan. Pengujian unit tanpa kerangka kerja adalah berharga karena ada halangan adopsi pengujian unit; memiliki sedikit pengujian unit tidak lebih baik daripada tidak ada sama sekali, padahal sekali kerangka kerja sudah diterapkan, menambahkan pengujian unit menjadi relatif mudah. Pada beberapa kerangka kerja yang banyak fitur pengujian unit yang tidak ada atau harus ditulis secara manual.