Setiap kali kita memperbaiki satu bug, pertanyaan yang menghantui adalah: “apakah perbaikan ini merusak bagian lain yang kemarin sudah benar?”. Menjawabnya dengan menguji manual seluruh fitur lama tidak mungkin dijaga disiplinnya begitu proyek membesar. Unit testing mengotomatiskan jawaban itu: asumsi-asumsi dari chapter Assertions dibungkus menjadi berkas uji kecil yang bisa dijalankan ulang sekejap mata, berkali-kali, tanpa pengawasan manusia. MATLAB menyediakan kerangka resmi matlab.unittest; kita juga membangun versi manual portabel yang berjalan identik di GNU Octave.

Anatomi Sebuah Unit Test #

Unit adalah bagian terkecil yang bisa diuji terpisah — umumnya satu fungsi. Satu unit test melakukan tiga hal: menyiapkan masukan, memanggil unit, lalu membandingkan hasil dengan ekspektasi lewat assertion. Kumpulan test untuk satu proyek disebut test suite. Kualitas suite ditentukan bukan oleh jumlahnya, melainkan oleh tiga sifat: tiap uji berjalan cepat, hasilnya deterministik (masukan sama → hasil sama), dan seluruh suite otomatis — keputusan lulus/gagal diambil oleh assertion, bukan oleh mata manusia yang membaca output.

Diagram di atas adalah siklus satu suite manual: setiap uji dijalankan bergantian, kegagalannya ditangkap agar uji berikutnya tetap berjalan, dan vonis akhir ditentukan oleh hitungan gagal.

Kerangka Resmi: matlab.unittest (MATLAB) #

Kerangka resmi MATLAB memakai gaya class-based test: satu class turunan matlab.unittest.TestCase, dengan setiap metode ber-atribut (Test) menjadi satu uji. Di dalam metode, metode kualifikasi verifyEqual/verifyTrue/verifyLessThan menggantikan assert mentah — bila satu verifikasi gagal, framework mencatat kegagalannya tanpa menghentikan metode berjalan, lalu melanjutkan ke uji berikutnya:

matlab
classdef MatematikaDasarTest < matlab.unittest.TestCase
    % MATEMATIKADASARTEST uji class-based untuk operasi matematika dasar.
    methods (Test)
        function testSum(testCase)
            testCase.verifyEqual(sum([1 2 3 4]), 10)
        end
        function testMean(testCase)
            testCase.verifyEqual(mean([2 4 6]), 4)
        end
        function testSortUrut(testCase)
            testCase.verifyTrue(issorted(sort([5 1 4])))
        end
        function testAkarKuadrat(testCase)
            testCase.verifyLessThan(sqrt(2), 1.42)
        end
    end
end

Mari kita bedah baris-baris kode di atas:

  1. Baris 1 (classdef ... < matlab.unittest.TestCase): class uji mewarisi TestCase — superclass seluruh test class MATLAB yang menyediakan metode kualifikasi.
  2. Baris 3 (methods (Test)): blok metode dengan atribut Test; framework hanya menjalankan metode di dalam blok ini.
  3. Baris 4-5 (testSum + verifyEqual): satu metode = satu uji; verifyEqual(nilaiAktual, nilaiEkspektasi) membandingkan keduanya dan mencatat kegagalan beserta diagnostiknya — bukan crash.
  4. Baris 10-11 (verifyLessThan): kualifikasi lain dari keluarga Verifiable — verifyTrue, verifyFalse, verifyGreaterThan, verifyEmpty, verifyNumElements, sampai verifyError yang menguji fungsi melempar galat yang diharapkan.

Simpan class itu sebagai MatematikaDasarTest.m lalu jalankan lewat runtests('MatematikaDasarTest') — runner mengeksekusi keempat uji dan mencetak ringkasan Totals: 4 Passed, 0 Failed beserta durasinya. runtests tanpa argumen menjalankan seluruh test di current folder; argumen folder runtests('tests') menjalankan seluruh isi subfolder tests.

Dua Gaya Lain di Kerangka Resmi #

Gaya function-based test cocok saat kita belum butuh class: satu berkas fungsi utama yang mendaftarkan uji-uji lokal lewat localfunctions, dan tiap uji menerima objek testCase:

matlab
function tests = matematikaDasarFungsiTest
% MATEMATIKADASARFUNGSITEST uji function-based (MATLAB).
tests = functiontests(localfunctions);
end

function testSum(testCase)
verifyEqual(testCase, sum([1 2 3 4]), 10)
end

function testMean(testCase)
verifyEqual(testCase, mean([2 4 6]), 4)
end

Gaya script-based test paling ringan — berkas script biasa yang disekat section %% Test dan berisi assert; runtests('namaBerkas') menjalankannya:

matlab
% ujiSkripTest.m — uji script-based (MATLAB)
%% Test sum menjumlahkan seluruh elemen
assert(isequal(sum([1 2 3 4]), 10))

%% Test mean menghitung rata-rata
assert(isequal(mean([2 4 6]), 4))

Versi Manual Portabel: uji_matematikaDasar.m #

Tanpa matlab.unittest, pola dasar dari diagram di awal chapter bisa kita bangun sendiri dengan bahan yang sudah dikuasai: assert, try/catch, fprintf, dan function handle. Berkas periksa.m menjadi runner satu-uji:

matlab
function ok = periksa(namaUji, fungsiUji)
% PERIKSA menjalankan satu uji manual berbasis assert.
% ok = periksa(namaUji, fungsiUji)
% namaUji   -- char, deskripsi uji yang dicetak saat lulus/gagal
% fungsiUji -- function handle tanpa argumen, hasil harus logical true

ok = false;
try
    assert(fungsiUji(), 'kondisi uji tidak terpenuhi');
    fprintf('LULUS  %s\n', namaUji);
    ok = true;
catch err
    fprintf('GAGAL  %s\n', namaUji);
    fprintf('       %s\n', err.message);
end
end

Suite-nya, uji_matematikaDasar.m, menyimpan pasangan {deskripsi, handle} dalam cell array lalu mengiterasinya:

matlab
% uji_matematikaDasar.m — inti alurnya
lulus = 0;
gagal = 0;

daftarUji = { ...
    'sum() menjumlahkan seluruh elemen',   @() isequal(sum([1 2 3 4]), 10); ...
    'mean() menghitung rata-rata',          @() isequal(mean([2 4 6]), 4); ...
    'perkalian matriks 2x2 * vektor',       @() isequal([1 2; 3 4] * [5; 6], [17; 39]); ...
    'mean(1:100) tepat 50.5',               @() abs(mean(1:100) - 50.5) < 1e-12};

for i = 1:size(daftarUji, 1)
    if periksa(daftarUji{i, 1}, daftarUji{i, 2})
        lulus = lulus + 1;
    else
        gagal = gagal + 1;
    end
end

fprintf('\nRingkasan: %d lulus, %d gagal (total %d uji)\n', ...
        lulus, gagal, lulus + gagal);
if gagal > 0
    error('uji:adaGagal', '%d uji gagal — periksa daftar GAGAL di atas', gagal);
end

Output yang Dihasilkan:

text
LULUS  sum() menjumlahkan seluruh elemen
LULUS  mean() menghitung rata-rata
LULUS  max() mencari elemen terbesar
LULUS  sort() mengurutkan menaik
LULUS  perkalian matriks 2x2 * vektor
LULUS  transpose mengubah baris-kolom
LULUS  sqrt() mencari akar kuadrat
LULUS  mod() menghitung sisa bagi
LULUS  round() membulatkan ke terdekat
LULUS  mean(1:100) tepat 50.5

Ringkasan: 10 lulus, 0 gagal (total 10 uji)

Mari kita bedah baris-baris kode di atas:

  1. Baris 5-9 (daftarUji = { ... }): setiap baris cell memasangkan deskripsi uji dengan anonymous function @() ... — ekspresi uji tidak dievaluasi saat daftar dibangun, hanya saat handle dipanggil; satu kegagalan evaluasi (misalnya galat runtime di dalam ekspresi) tetap tertangkap periksa.
  2. Baris 11-17 (for ... periksa ...): suite mengiterasi daftar; periksa menjalankan assertion, menangkap kegagalan agar uji berikutnya tetap berjalan, dan mengembalikan status lulus untuk dihitung.
  3. Baris 21-23 (error('uji:adaGagal', ...)): vonis akhir — bila ada satu saja kegagalan, suite keluar dengan galat uji:adaGagal sehingga exit code proses menjadi non-zero; alat CI (continuous integration) cukup memeriksa exit code untuk tahu suite lulus atau gagal.

Menata Suite dalam Proyek #

Praktik industri menyimpan berkas uji terpisah dari kode produksi — folder tests/ berisi seluruh berkas uji_*.m. Dengan penamaan konsisten, seluruh suite dijalankan satu perintah: runtests('tests') di MATLAB, atau pemanggilan berkas satu per satu di Octave. Dua aturan emas penamaan: nama test harus unik di seluruh proyek (dua class bernama sama MatematikaDasarTest di folder berbeda akan bertabrakan saat runner memuat keduanya), dan nama berkas uji sebaiknya menyebut subjeknya — uji_matematikaDasar.m, bukan uji1.m.

Ringkasan #

Unit test mengunci perilaku fungsi lewat assertion yang dijalankan ulang otomatis: cepat, deterministik, tanpa keputusan manusia. Kerangka resmi MATLAB menawarkan tiga gaya — class-based (classdef ... < matlab.unittest.TestCase + verifyEqual/verifyTrue + runtests), function-based (functiontests(localfunctions)), dan script-based (%% Test + assert) — semuanya MATLAB-ONLY. Versi manual portabel berbekal periksa.m + daftarUji cell array menghadirkan alur suite yang sama di MATLAB maupun Octave, dengan vonis akhir lewat exit code. Lanjutan chapter ini — fixtures, parameterized tests, code coverage, dan code analysis — membangun di atas fondasi tersebut.