Topik ini membahas unit testing, yaitu praktik menguji fungsi dan kelas secara terpisah melalui kode pengujian yang dijalankan berulang kali. Pembahasan mencakup harness pengujian buatan sendiri berbasis assert yang benar-benar dijalankan, siklus TDD (test-driven development) berupa merah, hijau, dan refaktor, serta dua framework pengujian yang paling banyak dipakai di C++, yaitu GoogleTest dan Catch2. Setelah membaca topik ini, pembaca dapat menulis kasus uji yang melaporkan jumlah kegagalan dan mengetahui apa saja yang ditambahkan framework di atas harness sederhana.
Harness pengujian buatan sendiri#
Harness pengujian (test harness) adalah kode yang memanggil fungsi yang diuji dengan masukan tertentu, membandingkan hasilnya dengan nilai yang diharapkan, lalu mencatat lulus atau gagalnya setiap kasus. Bentuk paling kecilnya tidak membutuhkan pustaka apa pun, melainkan cukup satu fungsi bantu dan dua penghitung. Harness berikut menguji dua fungsi konversi satuan, yaitu celsius ke fahrenheit dan kilometer ke mil.
#include <iostream>
#include <string>
int jumlahLulus = 0;
int jumlahGagal = 0;
void periksa(bool kondisi, const std::string& pesan) {
if (kondisi) {
++jumlahLulus;
} else {
++jumlahGagal;
std::cout << "GAGAL: " << pesan << '\n';
}
}
// fungsi yang diuji: konversi satuan suhu dan jarak
double celsiusKeFahrenheit(double celsius) {
return celsius * 1.8; // rumus masih belum lengkap
}
double kilometerKeMil(double kilometer) {
return kilometer * 0.6213712;
}
int main() {
periksa(celsiusKeFahrenheit(100.0) == 212.0, "100 C menjadi 212 F");
periksa(celsiusKeFahrenheit(0.0) == 32.0, "0 C menjadi 32 F");
periksa(kilometerKeMil(10.0) > 6.21 && kilometerKeMil(10.0) < 6.22,
"10 km menjadi sekitar 6,21 mil");
periksa(kilometerKeMil(0.0) == 0.0, "0 km menjadi 0 mil");
std::cout << jumlahLulus << " lulus, " << jumlahGagal << " gagal\n";
return jumlahGagal == 0 ? 0 : 1;
}GAGAL: 100 C menjadi 212 F
GAGAL: 0 C menjadi 32 F
2 lulus, 2 gagalFungsi periksa menaikkan penghitung lulus apabila kondisinya benar. Apabila kondisinya salah, fungsi ini mencetak pesan kasus yang gagal dan menaikkan penghitung gagal. Baris akhir merangkum hasil seluruh kasus. Nilai kembali main bernilai bukan nol ketika ada kegagalan dan nol apabila seluruh kasus lulus. Nilai kembali inilah yang dibaca alat continuous integration untuk menandai build yang pengujinya tidak lulus, sehingga harness sekecil apa pun sudah bekerja dalam alur profesional.
Dua kasus celsius gagal karena rumus pada celsiusKeFahrenheit masih mengabaikan penambah 32 derajat. Perbaikannya berupa return celsius * 1.8 + 32;, dan setelah perubahan itu dijalankan ulang, keluarannya menjadi 4 lulus, 0 gagal dengan nilai kembali nol. Keseluruhan proses itu merupakan inti dari siklus pengembangan berikut.
Siklus TDD: merah, hijau, refaktor#
Test-driven development (TDD) membalik urutan kerja biasa, yaitu kasus uji ditulis lebih dahulu sebelum implementasinya. Siklusnya terdiri atas tiga langkah yang diulang untuk setiap perilaku baru. Langkah merah menulis satu kasus uji dan menjalankannya untuk memastikan kasus itu gagal, karena implementasinya memang belum ada atau masih salah. Langkah hijau menulis implementasi sekecil mungkin hingga kasus tersebut lulus, tanpa menambah perilaku yang belum diujikan. Langkah refaktor merapikan kode yang sudah hijau, baik penamaan, struktur, maupun penghilangan duplikasi, dengan jaminan kasus uji tetap lulus setelah selesai.
Dua kegagalan pada keluaran di atas adalah keadaan merah yang sehat, yaitu kasus uji yang menggambarkan keinginan, bukan implementasi. Gagal pada keadaan merah bukan kegagalan pekerjaan, melainkan bukti bahwa kasus uji benar-benar memeriksa sesuatu. Perbaikan satu baris membawa keadaan ke hijau. Apabila rumus konversi kemudian dirapikan, misalnya dengan memberi nama konstanta untuk pengali 1.8, langkah refaktor terlindungi oleh kasus uji yang sama, sehingga kesalahan yang muncul kembali akan langsung terdeteksi. Jaminan inilah yang membuat pengujian berulang terasa mahal di awal tetapi murah dalam jangka panjang.
GoogleTest dan Catch2#
Harness buatan sendiri berhenti pada pesan teks sederhana, sedangkan framework pengujian menambahkan laporan kegagalan yang rinci, sekumpulan makro perbandingan, dan program utama yang menjalankan seluruh kasus secara otomatis. GoogleTest dan Catch2 adalah dua pilihan yang paling lazim untuk C++, dan keduanya dipasang melalui manajer package maupun integrasi CMake pada proyek nyata.
Pada GoogleTest, setiap kasus uji ditulis dengan makro TEST yang menerima nama kelompok dan nama kasus, sedangkan makro EXPECT_* membandingkan dua nilai dan melaporkan keduanya ketika hasilnya tidak cocok.
// tidak-dijalankan (GoogleTest belum terpasang pada lingkungan contoh)
#include <gtest/gtest.h>
double celsiusKeFahrenheit(double celsius);
TEST(KonversiSuhu, NolCelsiusMenjadiTigaPuluhDuaFahrenheit) {
EXPECT_DOUBLE_EQ(celsiusKeFahrenheit(0.0), 32.0);
}
TEST(KonversiSuhu, SeratusCelsiusMenjadiDuaRatusDuaBelasFahrenheit) {
EXPECT_DOUBLE_EQ(celsiusKeFahrenheit(100.0), 212.0);
}Catch2 memakai pola yang serupa melalui TEST_CASE beserta tanda kurung siku sebagai tag kategorinya.
// tidak-dijalankan (Catch2 belum terpasang pada lingkungan contoh)
#include <catch2/catch_test_macros.hpp>
double celsiusKeFahrenheit(double celsius);
TEST_CASE("konversi suhu", "[konversi]") {
REQUIRE(celsiusKeFahrenheit(0.0) == 32.0);
REQUIRE(celsiusKeFahrenheit(100.0) == 212.0);
}Keduanya menyediakan fungsi main bawaan sehingga penulis kasus uji tidak perlu menulisnya. Ketika sebuah perbandingan gagal, laporannya menampilkan ekspresi yang gagal beserta nilai kedua sisinya, misalnya 212.0 terhadap 180.0. Kemampuan ini menjelaskan mengapa harness buatan sendiri cukup untuk belajar, sedangkan proyek nyata segera berpindah ke framework.
Kesalahan umum#
- Bilangan pecahan dibandingkan dengan persamaan penuh. Kasus uji
==padadoublesering gagal atau justru lolos secara kebetulan karena representasi biner pecahan. Penyebabnya adalah pengabaian toleransi perbandingan. Perbaikannya berupa pengujian rentang seperti pada contoh kilometer ke mil, atau makroEXPECT_DOUBLE_EQmilik GoogleTest. - Kasus uji bergantung pada urutan eksekusi. Kasus kedua hanya lulus apabila kasus pertama mengubah variabel global terlebih dahulu. Penyebabnya adalah keadaan yang dibagi antarkasus tanpa disetel ulang. Perbaikannya berupa setiap kasus menyiapkan data miliknya sendiri, sehingga urutan eksekusi tidak berpengaruh.
- Pengujian hanya mencetak tanpa membandingkan. Program yang mencetak hasil tanpa pernyataan
periksamaupun assert memaksa pembaca keluarannya menilai benar atau salahnya sendiri. Penyebabnya adalah hasil uji yang tidak dikodekan. Perbaikannya berupa setiap hasil dibandingkan dengan nilai yang diharapkan, sehingga program dapat menilai sendiri.
Ringkasan#
- Harness pengujian minimal terdiri atas fungsi perbandingan, penghitung lulus dan gagal, serta nilai kembali bukan nol ketika ada kegagalan.
- Siklus TDD bergerak dari merah, hijau, lalu refaktor, dan kasus uji yang ditulis lebih dahulu berlaku sebagai jaring pengaman selama refaktor.
- GoogleTest dan Catch2 melengkapi harness dengan makro perbandingan, laporan kegagalan yang menampilkan nilai kedua sisi, dan fungsi
mainbawaan.
Referensi#
- GoogleTest — Google — Repository resmi beserta dokumentasi primer framework GoogleTest.
- Catch2 — Catchorg — Repository resmi dan dokumentasi framework pengujian Catch2.
- assert — cppreference.com — Makro assert pustaka standar sebagai bentuk pemeriksaan paling sederhana.