Aplikasi yang belum diuji hanyalah aplikasi yang kita harapkan benar — dan harapan tidak membantu pengguna yang CSV-nya rusak. Bagian ini menutup proyek dengan bukti: lapisan logika diuji otomatis berjalan (18 kasus, termasuk delapan kasus galat), antarmuka diuji manual mengikuti rencana tertulis, dan hasil analisis dilindungi dari regresi. Cakupannya:
- strategi uji dua lapis: otomatis untuk logika, manual terencana untuk UI,
tests/uji_logika.m: pola tabel kasus, kasus galat, dan hasil lulus nyata,- rencana uji manual: langkah → aksi → hasil diharapkan,
- uji regresi, packaging, checklist akhir, dan ide pengembangan.
Strategi Uji Dua Lapis #
Arsitektur dua lapis dari Bagian 1 menentukan strategi ujinya: setiap lapisan diuji dengan alat yang paling cocok untuknya. Logika murni tidak butuh jendela — cukup assert di berkas uji yang berjalan di Octave maupun MATLAB. Antarmuka sebaliknya hanya hidup di desktop MATLAB dan membutuhkan mata manusia:
| Lapisan | Alat uji | Kapan dijalankan |
|---|---|---|
Logika (src/ kecuali app) | tests/uji_logika.m, otomatis | setiap perubahan kode |
Antarmuka (aplikasiAnalisis.m) | rencana uji manual | sebelum rilis dan setelah ubah UI |
Pembagian ini bukan kompromi karena GUI tidak bisa diuji otomatis sama sekali — MATLAB menyediakan kerangka uji app — melainkan pilihan sadar untuk proyek ini: melindungi bagian yang paling berisiko salah (hitungan dan parsing) dengan uji murah, lalu memeriksa bagian visual dengan daftar periksa yang tertulis sehingga tidak bergantung ingatan.
Uji Otomatis Lapisan Logika #
Berkas tests/uji_logika.m dibuka dengan pola addpath yang membuatnya bisa dijalankan dari folder mana pun:
% Pastikan folder src/ dapat diakses dari lokasi berkas ini,
% entah dijalankan dari tests/ maupun dari lokasi lain.
folderIni = fileparts(mfilename('fullpath'));
addpath(fullfile(folderIni, '..', 'src'));Mari kita bedah baris-baris kode di atas:
- Baris 2 (
fileparts(mfilename('fullpath'))): lokasi berkas uji itu sendiri — bukan folder aktif — menjadi patokan, sehingga runner bisa memanggilnya dari mana pun. - Baris 3 (
addpath(fullfile(...))): naik satu tingkat kesrc/memasang seluruh fungsi logika ke path.
Berikutnya, persiapan yang khas proyek GUI ini: berkas CSV uji ditulis ke folder sementara yang membersihkan dirinya sendiri, dan korelasi diverifikasi dengan rumus yang ditulis ulang — bukan memanggil fungsi yang sama dua kali:
% Folder sementara untuk berkas CSV uji, dibuang otomatis saat skrip
% selesai (termasuk saat skrip berakhir karena error).
folderUji = tempname();
mkdir(folderUji);
pembersih = onCleanup(@() rmdir(folderUji, 's')); %#ok<NASGU>
% Korelasi Pearson dihitung ulang dari rumus untuk memverifikasi hasil
% analisisInti — bukan membandingkan output fungsi dengan dirinya sendiri.
korelasiManual = @(x, y) sum((x - mean(x)) .* (y - mean(y))) ...
/ sqrt(sum((x - mean(x)) .^ 2) * sum((y - mean(y)) .^ 2));Tabel kasus nilainya memeriksa statistik dihitung ulang oleh test sendiri — mean, std, min, maks dari data yang dibaca, bukan angka yang diketik tangan:
kasus = { ...
'buatDataContoh mengembalikan nama berkas', ...
@() assert(strcmp(berkasKembali, berkasContoh)); ...
'buatDataContoh menulis berkas di folder uji', ...
@() assert(exist(berkasContoh, 'file') == 2); ...
'muatCSV membaca 12 baris 3 kolom', ...
@() assert(isequal(size(d.data), [12 3])); ...
'muatCSV membaca nama kolom header', ...
@() assert(isequal(d.namaKolom, {'bulan', 'penjualan', 'suhu'})); ...
'muatCSV kolom bulan utuh 1..12', ...
@() assert(isequal(d.data(:, 1), (1:12)')); ...
'analisisInti mean/std/min/maks per kolom', ...
@() assert(isequal(h.mean, mean(d.data)) ...
&& isequal(h.stdDev, std(d.data, 0, 1)) ...
&& isequal(h.minNilai, min(d.data, [], 1)) ...
&& isequal(h.maksNilai, max(d.data, [], 1))); ...
'analisisInti jumlah baris dan kolom', ...
@() assert(h.jumlahBaris == 12 && h.jumlahKolom == 3); ...
'analisisInti korelasi sesuai rumus', ...
@() assert(abs(h.korelasi(1) ...
- korelasiManual(d.data(:, 1), d.data(:, 2))) < 1e-12 ...
&& abs(h.korelasi(2) ...
- korelasiManual(d.data(:, 1), d.data(:, 3))) < 1e-12); ...
'laporan memuat kata kunci', ...
@() assert(all(cellfun(@(kata) ~isempty(strfind(h.laporan, kata)), ...
{'Laporan', 'Mean', 'Std', 'Korelasi', 'bulan', ...
'penjualan', 'suhu'}))); ...
'analisisInti nama kolom bawaan', ...
@() assert(isequal(analisisInti(struct('data', [1 2; 3 4])) ...
.namaKolom, {'kolom1', 'kolom2'})) ...
};Mesin pengujinya satu loop try/catch yang menghitung LULUS/GAGAL — pola yang sama dengan proyek kalkulator matriks. Kasus galatnya membalik logika: panggilan wajib melempar galat, dan pesannya harus memuat fragmen yang dijanjikan:
for k = 1:size(galat, 1)
lulus = false;
try
galat{k, 2}(); % wajib melempar galat
catch err
lulus = ~isempty(strfind(err.message, galat{k, 3}));
end
if lulus
nLulus = nLulus + 1;
fprintf('LULUS: %s\n', galat{k, 1});
else
nGagal = nGagal + 1;
fprintf('GAGAL: %s\n', galat{k, 1});
end
endDelapan kasus galat menutup seluruh guard yang dibangun di Bagian 3:
| Kondisi cacat | Fragmen pesan yang diharapkan |
|---|---|
| berkas tidak ada | Berkas tidak ditemukan: ... |
| CSV hanya satu kolom | CSV memerlukan minimal 2 kolom, ditemukan 1. |
| token bukan angka | bukan angka |
| jumlah kolom baris berubah | kolom, header |
| header tanpa baris data | Tidak ada baris data setelah header. |
data satu kolom ke analisisInti | minimal 2 kolom |
| data kosong | tidak boleh kosong |
namaKolom salah panjang | namaKolom |
Menjalankan berkas dari folder tests/ (Octave 10.3, mode batch) memberi hasil:
Output yang Dihasilkan:
== uji_logika.m ==
LULUS: buatDataContoh mengembalikan nama berkas
LULUS: buatDataContoh menulis berkas di folder uji
LULUS: muatCSV membaca 12 baris 3 kolom
LULUS: muatCSV membaca nama kolom header
LULUS: muatCSV kolom bulan utuh 1..12
LULUS: analisisInti mean/std/min/maks per kolom
LULUS: analisisInti jumlah baris dan kolom
LULUS: analisisInti korelasi sesuai rumus
LULUS: laporan memuat kata kunci
LULUS: analisisInti nama kolom bawaan
LULUS: muatCSV berkas tidak ada
LULUS: muatCSV CSV satu kolom
LULUS: muatCSV token bukan angka
LULUS: muatCSV jumlah kolom berubah
LULUS: muatCSV header tanpa baris data
LULUS: analisisInti data satu kolom
LULUS: analisisInti data kosong
LULUS: analisisInti namaKolom salah panjang
Ringkasan: 18 lulus, 0 gagal (total 18)| Berkas | Cakupan | Kasus | Hasil |
|---|---|---|---|
uji_logika.m | seluruh lapisan logika | 18 | 18 lulus, 0 gagal, kode keluar 0 |
Kode keluar 0 penting: berkas uji sengaja melempar error hanya bila ada kegagalan, sehingga runner otomatis mendeteksi masalah tanpa membaca layar.
Rencana Uji Manual Antarmuka #
Lapisan UI diuji dengan rencana tertulis — setiap langkah punya hasil yang bisa dicek. Rencana ini dijalankan di desktop MATLAB, sekali sebelum rilis dan setiap kali UI berubah:
| # | Aksi | Hasil yang diharapkan |
|---|---|---|
| 1 | jalankan aplikasiAnalisis | jendela 720 x 520 terbuka; dropdown pucat dan tidak bisa diklik; label menampilkan panduan |
| 2 | klik Muat Data (berkas belum ada) | data_contoh.csv dibuat; grafik bulan-penjualan tampil dengan penanda titik; ringkasan tiga baris; dropdown aktif |
| 3 | ganti Kolom Y ke suhu | grafik digambar ulang; ringkasan berganti (mean 26.9, korelasi bulan-suhu: 0.012); berkas tidak dibaca ulang |
| 4 | sunting CSV dengan editor teks, klik Muat Data lagi | ringkasan memuat nilai baru |
| 5 | rusak CSV (ganti angka dengan abc), klik Muat Data | dialog galat uialert berisi Baris 1 kolom 2 ("abc") bukan angka.; aplikasi tetap hidup |
| 6 | klik Muat Data dua kali dengan cepat | tombol terkunci selama proses; hanya satu pemuatan |
| 7 | perbesar jendela | grafik ikut membesar; kontrol dan ringkasan tetap 'fit' |
| 8 | navigasi dengan Tab | fokus mencapai tombol dan dropdown tanpa mouse |
Uji Regresi #
Refactor adalah momen paling rawan: kode berubah, hasil harus tidak. Uji regresi melindunginya dengan perbandingan nilai — isequal terhadap hasil yang diketahui benar. Contoh terkecilnya, data contoh harus selalu terbaca kembali utuh:
% regresi: data contoh harus selalu terbaca kembali utuh
buatDataContoh();
d = muatCSV('data_contoh.csv');
M = [ (1:12)', [200 215 230 255 270 290 305 320 330 345 360 380]', ...
[26.0 26.5 25.5 27.0 28.0 28.5 28.5 27.5 27.0 26.5 26.0 26.0]' ];
fprintf('round-trip utuh: %d\n', isequal(d.data, M));Output yang Dihasilkan:
round-trip utuh: 1Prinsip kerjanya: bila suatu saat muatCSV dirombak (misalnya pindah ke textscan), satu baris isequal ini langsung melaporkan apakah hasil pembacaan masih identik. Kasus round-trip semacam ini — tulis lalu baca — adalah uji regresi termurah untuk semua fungsi I/O.
Packaging dan Distribusi #
Cara termudah membagikan aplikasi: kirim folder src/ (empat berkas .m) — data_contoh.csv tidak perlu disertakan karena dibuat ulang otomatis saat klik pertama. Penerima menjalankan aplikasiAnalisis setelah berada di folder itu.
| Cara | Penerima perlu | Cocok untuk |
|---|---|---|
Kirim src/ | lisensi MATLAB | sesama pengguna MATLAB |
| Kompilasi dengan MATLAB Compiler | hanya MATLAB Runtime (gratis) | pengguna tanpa MATLAB |
Opsi kedua mengemas aplikasi menjadi program mandiri; penerima memasang MATLAB Runtime — pustaka gratis dari MathWorks berukuran ratusan MB hingga beberapa GB — tanpa perlu lisensi MATLAB sama sekali.
Checklist Akhir Proyek #
Audit hasil kerja terhadap janji-janji Bagian 1:
| Janji Bagian 1 | Bukti |
|---|---|
| Muat CSV tanpa satu perintah pun | tombol Muat Data + callback; uji manual #1-2 |
| Statistik dan korelasi benar | kasus uji 6-8 menghitung ulang nilainya; 18/18 lulus |
| Grafik mengikuti pilihan kolom | plot(axGrafik, ...) target eksplisit; uji manual #3 |
| Ringkasan tiga baris | sprintf multi-baris; output driver nyata di Bagian 3 |
| Galat ditolak dengan pesan jelas | 8 kasus galat + uialert di uji manual #5 |
| Data hidup setelah dimuat | ganti Kolom Y tanpa membaca ulang berkas |
| Logika teruji otomatis | uji_logika.m: 18 lulus, kode keluar 0 |
| Tanpa berkas tersisa | berkas uji di folder tempname + onCleanup |
Ide Pengembangan Lanjutan #
Struktur dua lapis membuat pengembangan berikutnya murah — UI dan logika tumbuh tanpa saling mengganggu:
- multi-berkas — ganti nama berkas tetap dengan dialog
uigetfile()di callback Muat Data; logikamuatCSVtidak berubah sama sekali, - export hasil — tombol Simpan yang memanggil
exportgraphics(axGrafik, ...)untuk grafik dan menulishasil.laporanke berkas teks, - halaman bertab —
uitabgroupmemisahkan tab Grafik, Tabel, dan Ringkasan pada jendela yang sama, - tabel data mentah —
uitablemenampilkan matriks hasilmuatCSVlangsung di app, - kolom X bebas — dropdown kedua sehingga korelasi bisa dihitung pasangan kolom mana pun.
Setiap kandidat menuntut pola yang sama: sedikit kode UI di aplikasiAnalisis.m, sedikit logika murni di src/, dan beberapa baris kasus uji baru — arsitektur yang sudah terbukti.
Proyek aplikasi analisis data tuntas: antarmuka dari Bagian 2, logika teruji dari Bagian 3, dan bukti dari Bagian 4 membentuk aplikasi yang bisa dipakai orang lain tanpa satu baris kode. Cetak birunya layak dibawa ke proyek berikutnya — pisahkan logika dari tampilan, uji logika secara otomatis, rencanakan uji tampilan secara tertulis, dan biarkan galat diterima dengan sopan.