Seluruh alat pada section ini memeriksa kode dari luar: compiler menilai sintaks, static analysis menelusuri jalur, clang-tidy mengenali pola. Unit testing mengambil arah berbeda — kita menulis kode yang memanggil kode lain dan membandingkan hasilnya dengan ekspektasi yang dinyatakan eksplisit. Satu unit test memeriksa satu unit terkecil yang bisa diuji (biasanya satu function atau satu class) secara terisolasi dari dunia luar: tanpa berkas, tanpa jaringan, tanpa database. Dengan demikian unit test berjalan dalam milidetik, bisa dieksekusi ratusan kali per hari, dan menjadi jaring pengaman permanen: setiap kali kode diubah, seluruh test lama berjalan lagi dan menangkap perilaku yang rusak (regresi) sebelum sampai ke pengguna.
Pola AAA: Arrange-Act-Assert #
Hampir semua unit test punya tiga langkah yang sama, dan kebiasaan menuliskannya secara sadar membuat test mudah dibaca:
- Arrange — siapkan objek dan input yang dibutuhkan: variabel, container, parameter.
- Act — panggil satu unit yang diuji, biasanya satu baris saja.
- Assert — bandingkan hasil dengan ekspektasi; di sinilah test lulus atau gagal.
Pemisahan ini terlihat sepele, tetapi memiliki manfaat nyata: bila assertion gagal, kita langsung tahu mana yang berubah — arrange yang salah berarti masalahnya data uji, act yang salah berarti unit yang diuji, dan assert yang salah berarti ekspektasi kita yang keliru. Test yang menggabungkan ketiganya dalam satu baris panjang memang lebih pendek, tetapi saat gagal, ia tidak memberi tahu di lapisan mana masalahnya berada.
Framework Uji: GoogleTest dan Catch2 #
Dunia C++ punya beberapa framework uji matang, dan dua yang paling sering Anda temui di proyek nyata adalah GoogleTest dan Catch2. Keduanya tidak terinstal di sandbox penulis, sehingga kita menampilkan sketsanya sebagai ilustrasi — bentuk dasarnya sama: kumpulan test bernama yang berisi assertion. GoogleTest mendapat pembahasan utuh pada Chapter Testing dengan GoogleTest, termasuk cara membangun dan menjalankannya sungguhan:
#include <gtest/gtest.h>
TEST(KonversiSuhu, CelsiusKeFahrenheit) {
EXPECT_EQ(konversiCelsiusKeFahrenheit(100.0), 212.0);
}#include <catch2/catch_test_macros.hpp>
TEST_CASE("singkat memotong teks panjang") {
REQUIRE(singkat("helloworld", 7) == "hell...");
}Framework penuh seperti GoogleTest menyediakan banyak fasilitas — assertion kaya, fixture berbagi data antar test, test yang dikelompokkan dan dijalankan selektif — dan menjadi pilihan wajar untuk proyek sungguhan. Namun inti dari semua framework uji ternyata sederhana: jalankan sekumpulan assertion, catat yang gagal, laporkan ringkasan, lalu kembalikan exit code yang mencerminkan hasil. Karena inti sederhana itulah, kita bisa membangun versi mini sendiri dan tetap memahami persis cara kerjanya.
Mini Harness yang Kita Bangun Sendiri #
Program berikut adalah test harness minimal — lengkap dengan macro assertion, penghitung lulus/gagal, tiga test case, dan kontrak exit code. Ia sengaja memuat satu test yang gagal, agar kita bisa melihat dengan mata kepala sendiri bagaimana kegagalan itu dilaporkan:
// harness-uji.cpp — mini test harness: macro CHECK + penghitung lulus/gagal.
// Ini pengganti framework uji (GoogleTest, Catch2, Doctest) yang ditulis
// sendiri agar konsep unit testing bisa dicoba tanpa instalasi apa pun.
#include <iostream>
#include <string>
static int jumlahLulus = 0;
static int jumlahGagal = 0;
static std::string namaUjiAktif;
#define MULAI_UJI(nama) namaUjiAktif = (nama)
#define CHECK_TRUE(kondisi, deskripsi) \
cekBoolean((kondisi), (deskripsi), #kondisi)
#define CHECK_EQ(aktual, diharapkan) \
cekSama((aktual), (diharapkan), #aktual)
void cekBoolean(bool kondisi, const std::string& deskripsi,
const char* ekspresi) {
if (kondisi) {
++jumlahLulus;
std::cout << "[ LULUS ] " << namaUjiAktif << " - " << deskripsi << "\n";
} else {
++jumlahGagal;
std::cout << "[ GAGAL ] " << namaUjiAktif << " - " << deskripsi
<< " (" << ekspresi << " bernilai false)\n";
}
}
template <typename T, typename U>
void cekSama(const T& aktual, const U& diharapkan, const char* ekspresi) {
if (aktual == diharapkan) {
++jumlahLulus;
std::cout << "[ LULUS ] " << namaUjiAktif << " - " << ekspresi << "\n";
} else {
++jumlahGagal;
std::cout << "[ GAGAL ] " << namaUjiAktif << " - " << ekspresi
<< " (aktual " << aktual << ", diharapkan " << diharapkan
<< ")\n";
}
}
// ---------- kode yang diuji (unit under test) ----------
double konversiCelsiusKeFahrenheit(double celsius) {
return celsius * 9.0 / 5.0 + 32.0;
}
std::string singkat(const std::string& teks, std::size_t maksimum) {
if (teks.size() <= maksimum) {
return teks;
}
return teks.substr(0, maksimum - 3) + "...";
}
// ---------- test case, gaya Arrange-Act-Assert ----------
void ujiKonversiSuhu() {
MULAI_UJI("konversiCelsiusKeFahrenheit");
// Arrange: siapkan input
double celsius = 100.0;
// Act: jalankan unit yang diuji
double fahrenheit = konversiCelsiusKeFahrenheit(celsius);
// Assert: bandingkan hasil dengan ekspektasi
CHECK_EQ(fahrenheit, 212.0);
}
void ujiSingkat() {
MULAI_UJI("singkat");
CHECK_EQ(singkat("helloworld", 7), std::string("hell..."));
CHECK_TRUE(singkat("oke", 10) == "oke", "teks pendek tidak dipotong");
}
void ujiSingkatEkspektasiSalah() {
MULAI_UJI("singkat batas tepat");
CHECK_EQ(singkat("abcdef", 3), std::string("abc")); // sengaja salah
}
int main() {
ujiKonversiSuhu();
ujiSingkat();
ujiSingkatEkspektasiSalah();
std::cout << "\nringkasan: " << jumlahLulus << " lulus, " << jumlahGagal
<< " gagal\n";
return jumlahGagal == 0 ? 0 : 1;
}Output yang Dihasilkan:
[ LULUS ] konversiCelsiusKeFahrenheit - fahrenheit
[ LULUS ] singkat - singkat("helloworld", 7)
[ LULUS ] singkat - teks pendek tidak dipotong
[ GAGAL ] singkat batas tepat - singkat("abcdef", 3) (aktual ..., diharapkan abc)
ringkasan: 3 lulus, 1 gagalBedah mekanismenya, bagian demi bagian:
- Penghitung dan nama test aktif — tiga variable global (
jumlahLulus,jumlahGagal,namaUjiAktif) membentuk “papan skor” harness;MULAI_UJI(nama)mengisi nama yang akan dicantumkan pada setiap baris hasil setelahnya. - Macro
CHECK_TRUEdanCHECK_EQ— kekuatan macro di sini ada pada operator#kondisi: stringification, yang mengubah teks argumen menjadi string literal. Berkat itu, baris laporan mencetak ekspresi aslinya —singkat("helloworld", 7)— bukan sekadar “cek gagal”. cekSamasebagai template — satu definisi melayani pembandingan duadouble,std::string, atau tipe lain yang punyaoperator==. Tipeaktualdandiharapkanboleh berbeda, dan keduanya dicetak apa adanya saat tidak sama.- Test case dengan pola AAA —
ujiKonversiSuhumemperlihatkan ketiga langkah dengan komentar eksplisit: arrange satudouble, act satu panggilan, assert satuCHECK_EQ. - Exit code sebagai kontrak —
mainmengembalikan0hanya bila semua test lulus. Baris inilah yang membuat test berguna bagi CI: pipeline tidak perlu membaca teks output, cukup memeriksa kode akhir.
Membaca Kegagalan: Test Menemukan Bug Nyata #
Baris [ GAGAL ] di atas bukan dekorasi — mari kita periksa apakah test-nya yang salah atau kode-nya yang salah. Ekspektasi tertulis: potongan "abcdef" dengan batas 3 seharusnya "abc". Perilaku nyata singkat: teks.size() adalah 6, lebih besar dari maksimum 3, sehingga function mengembalikan teks.substr(0, 0) + "..." — string kosong plus tanda titik tiga, yaitu "...". Test-nya benar dan kode-nya yang salah: function ini belum mendefinisikan perilaku batas maksimum < 3, karena tidak ada cara menghasilkan teks tiga karakter “singkat” bila seluruh budget dipakai tanda titik. Perbaikannya harus diputuskan dulu secara semantik (misalnya: bila maksimum < 3, kembalikan langsung teks.substr(0, maksimum) tanpa tanda titik), lalu test baru ditambahkan untuk menguncinya.
Inilah nilai yang paling sulit digantikan oleh alat manapun: ekspektasi yang tertulis memaksa kita mendefinisikan perilaku tepi sebelum ada pengguna nyata yang menemukannya. Test yang gagal bukan kegagalan pekerjaan — ia adalah informasi yang bekerja tepat pada waktunya.
Test berikutnya naik satu tingkat: jika unit test membuktikan setiap bagian benar secara terpisah, pertanyaan lanjutannya adalah apakah bagian-bagian itu benar ketika dipasangkan — misalnya satu class menulis data ke berkas dan class lain membacanya kembali. Itu wilayah integration testing.
Referensi: GoogleTest — GitHub , GoogleTest Primer .