Dari strategi black-box hingga menulis test otomatis dengan Python — belajar menguji software seperti seorang insinyur QA profesional.
Black-Box (dari luar): Anda hanya melihat layar dan tombol ATM. Anda tidak peduli bagaimana sirkuit di dalamnya bekerja — Anda cukup memasukkan kartu, tekan tombol, dan periksa apakah uang yang keluar benar. Anda menguji perilaku sistem dari perspektif pengguna.
White-Box (dari dalam): Anda adalah teknisi yang membuka mesin ATM dan menelusuri setiap jalur kabel, setiap sensor, setiap kondisi di kode firmware-nya. Anda memastikan setiap jalur logika dieksekusi dan benar.
Membagi semua kemungkinan input menjadi kelas-kelas yang setara. Logikanya: jika satu nilai dari suatu kelas lolos (atau gagal), semua nilai dalam kelas tersebut kemungkinan besar berperilaku sama. Tidak perlu menguji semua nilai — cukup satu perwakilan per kelas.
Seperti menguji produk dari lini produksi. Jika 1 produk dari 1000 produk identik rusak, kemungkinan besar semuanya punya masalah yang sama. Anda tidak perlu mengecek semua 1000 produk — cukup ambil sampel perwakilan dari setiap "kelompok produksi".
Contoh: Form input usia mahasiswa (valid: 17–60 tahun)
| Kelas Ekuivalensi | Range | Contoh Nilai Test | Expected Result |
|---|---|---|---|
| Valid (dalam range) | 17 ≤ usia ≤ 60 | 25, 17, 60 | ✅ Diterima |
| Invalid (terlalu kecil) | usia < 17 | 5, 16, 0 | ❌ Error: "Usia terlalu kecil" |
| Invalid (terlalu besar) | usia > 60 | 61, 100, 200 | ❌ Error: "Usia terlalu besar" |
| Invalid (bukan angka) | non-integer | "dua puluh", -5, 25.5 | ❌ Error: "Format tidak valid" |
Efisiensi EP: Tanpa EP kita perlu ratusan test case. Dengan EP cukup 4 test case yang mewakili semua kemungkinan perilaku sistem!
Bug paling sering bersembunyi di batas (boundary) nilai input — off-by-one errors adalah penyebab bug terbanyak dalam sejarah. BVA fokus menguji tepat di batas, satu di bawah batas, dan satu di atas batas.
Seperti tes kelulusan ujian. Nilai 70 = lulus, nilai 69 = tidak lulus. Kemungkinan error ada tepat di nilai 69, 70, dan 71 — bukan di nilai 10 atau 100. Tester yang cerdas selalu menguji "zona perbatasan" ini.
Untuk range usia 17–60, nilai BVA yang harus diuji:
Uji 6 nilai kritis ini (batas bawah-1, batas bawah, batas bawah+1, batas atas-1, batas atas, batas atas+1)
Untuk sistem dengan banyak kombinasi kondisi yang menghasilkan aksi berbeda. Tabel keputusan memastikan semua kombinasi diuji secara sistematis.
Contoh: Sistem Diskon E-Commerce
| Kondisi / Aksi | TC-1 | TC-2 | TC-3 | TC-4 |
|---|---|---|---|---|
| Member Premium? | T | T | F | F |
| Total > Rp 500rb? | T | F | T | F |
| Ada voucher? | T | F | F | T |
| → Diskon 30% | ✓ | — | — | — |
| → Diskon 15% | — | ✓ | ✓ | — |
| → Diskon voucher | — | — | — | ✓ |
| → Tanpa diskon | — | — | — | — |
Setiap kolom (TC-1 s/d TC-4) adalah satu test case yang harus dibuat dan dieksekusi.
White-box testing mengukur seberapa banyak kode yang benar-benar dieksekusi oleh test suite kita. Semakin tinggi coverage, semakin kecil kemungkinan ada bug yang tersembunyi di jalur yang belum diuji.
Statement Coverage = Memastikan setiap ruangan di gedung pernah dimasuki (tapi belum tentu semua pintu). Branch Coverage = Memastikan setiap pintu (if-true dan if-false) pernah dibuka. Path Coverage = Menelusuri setiap kemungkinan rute dari pintu masuk ke pintu keluar. MC/DC = Standar tertinggi untuk sistem safety-critical seperti pesawat.
Sebuah sistem mungkin berfungsi dengan sempurna saat digunakan 10 orang — tapi bagaimana saat 10.000 orang mengaksesnya secara bersamaan?
Menguji sistem pada beban yang diharapkan (normal peak load). Contoh: simulasi 5.000 pengguna bersamaan pada jam puncak belanja online. Tujuan: pastikan response time < 2 detik.
Menguji sistem melampaui batas kapasitas normal untuk menemukan titik breaknya. Contoh: tambah terus pengguna sampai sistem crash — lalu lihat bagaimana sistem pulih. Tujuan: temukan batas dan pastikan graceful degradation.
Menguji sistem dalam beban normal selama waktu panjang (jam, hari). Mencari memory leak atau degradasi performa yang hanya muncul setelah sistem berjalan lama. Analogi: apakah mesin panas dan melambat setelah 24 jam beroperasi?
Mengidentifikasi bottleneck performa — fungsi mana yang paling banyak memakai CPU/memori. Seperti X-ray untuk sistem: menunjukkan bagian mana yang "sakit" dan paling butuh optimasi.
Mesin cuci (otomasi) bagus untuk pakaian sehari-hari, cepat, konsisten. Cuci tangan (manual) lebih baik untuk pakaian halus, ternoda khusus, atau situasi tidak terduga. Pilih berdasarkan konteks — bukan otomasi semua hal.
| Kriteria | Otomasi ✅ | Manual ✅ |
|---|---|---|
| Frekuensi eksekusi | Sangat sering (setiap commit, harian) | Jarang (sekali per sprint) |
| Stabilitas fitur | Sudah stabil, jarang berubah | Masih dalam pengembangan aktif |
| Jenis pengujian | Regression, smoke, unit test | Exploratory, UX, edge cases baru |
| Data driven | Banyak kombinasi data berbeda | Skenario unik dan kontekstual |
| Setup awal | Investasi waktu besar di awal | Langsung bisa dijalankan |
Formula ROI Automasi: ROI = (Waktu hemat × Frekuensi × Gaji tester) − Biaya pembuatan & maintenance script. Jika ROI positif dalam 3 bulan → layak diotomasi!
name: QA Pipeline — S11409 Demo on: push: branches: [main, develop] pull_request: branches: [main] jobs: quality-gate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: pip install -r requirements.txt # GATE 1: Static Analysis - name: 🔍 Static Analysis (pylint) run: pylint src/ --fail-under=8.0 # GATE 2: Unit Tests + Coverage - name: 🧪 Unit Tests + Coverage run: pytest tests/unit/ --cov=src --cov-report=xml --cov-fail-under=80 # GATE 3: Integration Tests - name: 🔗 Integration Tests run: pytest tests/integration/ -v # GATE 4: Security Scan - name: 🔐 Security Scan (bandit) run: bandit -r src/ -ll # Upload coverage report - name: Upload coverage to Codecov uses: codecov/codecov-action@v4
# ================================================================ # FILE: kalkulator_diskon.py # Sistem perhitungan diskon e-commerce "TokoKita" # ================================================================ class KalkulatorDiskon: """Menghitung harga akhir setelah diskon.""" BATAS_DISKON_MEMBER = 500_000 # Rp 500.000 DISKON_PREMIUM_BESAR = 0.30 # 30% DISKON_PREMIUM_KECIL = 0.15 # 15% DISKON_REGULER = 0.10 # 10% def hitung_diskon(self, total_belanja, is_premium, kode_voucher=None): """ Hitung diskon berdasarkan status member dan total belanja. Returns: (harga_final, persen_diskon, alasan) """ if total_belanja < 0: raise ValueError("Total belanja tidak boleh negatif") if kode_voucher == "HEMAT20": diskon = 0.20 alasan = "Voucher HEMAT20 (20%)" elif is_premium and total_belanja >= self.BATAS_DISKON_MEMBER: diskon = self.DISKON_PREMIUM_BESAR alasan = "Member Premium + Belanja ≥ 500rb (30%)" elif is_premium: diskon = self.DISKON_PREMIUM_KECIL alasan = "Member Premium (15%)" elif total_belanja >= self.BATAS_DISKON_MEMBER: diskon = self.DISKON_REGULER alasan = "Belanja ≥ 500rb (10%)" else: diskon = 0 alasan = "Tidak ada diskon" harga_final = total_belanja * (1 - diskon) return harga_final, diskon, alasan
# ================================================================ # FILE: test_kalkulator_diskon.py # Unit Tests menggunakan pytest # Teknik: Equivalence Partitioning + Boundary Value Analysis # Dosen: Riadi Marta Dinata, S.Ti., M.Kom. | ISTN Jakarta # ================================================================ import pytest from kalkulator_diskon import KalkulatorDiskon @pytest.fixture def kalkulator(): """Fixture: buat instance KalkulatorDiskon sebelum setiap test.""" return KalkulatorDiskon() # ── KELAS 1: Member Premium + Belanja Besar ── class TestMemberPremiumBelanjaБesar: def test_diskon_30_persen_normal(self, kalkulator): """TC-01: Premium + total 600rb → diskon 30%""" final, diskon, _ = kalkulator.hitung_diskon(600_000, is_premium=True) assert diskon == 0.30 assert final == pytest.approx(420_000) def test_boundary_tepat_500rb(self, kalkulator): """TC-02: BVA — tepat di boundary 500rb → dapat 30%""" final, diskon, _ = kalkulator.hitung_diskon(500_000, is_premium=True) assert diskon == 0.30, "Tepat 500rb harus dapat diskon 30%" def test_boundary_499rb_premium(self, kalkulator): """TC-03: BVA — satu di bawah boundary (499.999) → 15%""" _, diskon, _ = kalkulator.hitung_diskon(499_999, is_premium=True) assert diskon == 0.15, "499.999 hanya dapat diskon 15%" # ── KELAS 2: Member Premium + Belanja Kecil ── class TestMemberPremiumBelanjaKecil: def test_diskon_15_persen(self, kalkulator): """TC-04: Premium + total 200rb → diskon 15%""" final, diskon, _ = kalkulator.hitung_diskon(200_000, is_premium=True) assert diskon == 0.15 assert final == pytest.approx(170_000) # ── KELAS 3: Reguler + Belanja Besar ── class TestRegulerBelanjaБesar: def test_diskon_10_persen(self, kalkulator): """TC-05: Reguler + 700rb → diskon 10%""" final, diskon, _ = kalkulator.hitung_diskon(700_000, is_premium=False) assert diskon == 0.10 assert final == pytest.approx(630_000) # ── KELAS 4: Tanpa Diskon ── class TestTanpaDiskon: def test_no_discount_reguler_kecil(self, kalkulator): """TC-06: Reguler + 100rb → tidak ada diskon""" final, diskon, alasan = kalkulator.hitung_diskon(100_000, is_premium=False) assert diskon == 0 assert final == 100_000 assert "Tidak ada" in alasan # ── KELAS 5: Voucher ── class TestVoucher: def test_voucher_hemat20(self, kalkulator): """TC-07: Voucher HEMAT20 → prioritas di atas status member""" _, diskon, alasan = kalkulator.hitung_diskon( 300_000, is_premium=True, kode_voucher="HEMAT20") assert diskon == 0.20 assert "HEMAT20" in alasan def test_voucher_invalid(self, kalkulator): """TC-08: Voucher tidak valid → diabaikan""" _, diskon, _ = kalkulator.hitung_diskon( 100_000, is_premium=False, kode_voucher="SALAH999") assert diskon == 0 # ── KELAS 6: Error Handling ── class TestErrorHandling: def test_total_negatif_raises_error(self, kalkulator): """TC-09: Input negatif → harus raise ValueError""" with pytest.raises(ValueError, match="tidak boleh negatif"): kalkulator.hitung_diskon(-100_000, is_premium=True) def test_total_nol(self, kalkulator): """TC-10: BVA — total = 0 → valid, tanpa diskon""" final, diskon, _ = kalkulator.hitung_diskon(0, is_premium=False) assert final == 0 assert diskon == 0 # ── PARAMETRIZED TEST (Data-Driven) ── @pytest.mark.parametrize("total,premium,expected_disc", [ (1_000_000, True, 0.30), # Premium + besar (200_000, True, 0.15), # Premium + kecil (750_000, False, 0.10), # Reguler + besar (50_000, False, 0 ), # Reguler + kecil (500_000, True, 0.30), # BVA: tepat boundary (499_999, True, 0.15), # BVA: satu di bawah ]) def test_parametrized_diskon(kalkulator, total, premium, expected_disc): """TC Data-Driven: otomatis menguji 6 kombinasi sekaligus""" _, diskon, _ = kalkulator.hitung_diskon(total, is_premium=premium) assert diskon == expected_disc
# Install pytest pip install pytest pytest-cov # Jalankan semua test pytest test_kalkulator_diskon.py -v # Dengan laporan coverage pytest test_kalkulator_diskon.py -v --cov=kalkulator_diskon --cov-report=term-missing # Output yang diharapkan: PASSED TestMemberPremiumBelanjaБesar::test_diskon_30_persen_normal PASSED TestMemberPremiumBelanjaБesar::test_boundary_tepat_500rb ... 10 passed in 0.12s — Coverage: 100%
Form registrasi menerima input "Nomor Telepon" dengan aturan: harus diawali 08, panjang 10-13 digit, hanya angka. Tentukan semua kelas ekuivalensi (valid & invalid) dan 8 nilai BVA yang harus diuji!
Sistem login memiliki kondisi: (a) username benar/salah, (b) password benar/salah, (c) akun terkunci/tidak. Buat decision table lengkap dengan semua kombinasi dan aksi yang dihasilkan.
Jelaskan perbedaan Statement Coverage dan Branch Coverage. Berikan contoh kode Python 10 baris yang memiliki Statement Coverage 100% tapi Branch Coverage hanya 50% — dan test case apa yang perlu ditambahkan.
Buat fungsi validasi_nim(nim: str) -> bool yang memvalidasi NIM mahasiswa (8 digit angka, tidak dimulai 0). Kemudian tulis test lengkap dengan pytest mencakup EP, BVA, dan parametrized test minimal 10 test case.
Anda QA Lead di startup dengan 3 developer. Sprint 2 mingguan, 50 user stories. Buat strategi automasi: fitur mana yang diotomasi, tools apa yang digunakan, bagaimana mengintegrasikan ke CI/CD. Justifikasi ROI pilihan Anda.
EP membagi input menjadi kelas setara — uji 1 perwakilan per kelas, hemat ratusan test case
BVA fokus di batas (min-1, min, min+1, max-1, max, max+1) — di situ bug paling sering bersembunyi
Branch Coverage lebih ketat dari Statement Coverage — pastikan setiap if/else diuji dua arahnya
Otomasi layak jika frekuensi tinggi, fitur stabil, dan ROI positif dalam 3 bulan
pytest + fixture + parametrize = cara profesional menulis unit test di Python
GitHub Actions menjalankan test otomatis setiap ada perubahan kode — quality gate tanpa campur tangan manual