Sesi 05 dari 16

Pengujian Lanjut: Teknik & Automasi

Dari strategi black-box hingga menulis test otomatis dengan Python — belajar menguji software seperti seorang insinyur QA profesional.

Black-Box Testing
White-Box Testing
🤖 pytest + Selenium
⏱️ 3 × 50 menit
🎯 Bagian 1 — Peta Teknik Pengujian

Dua Sudut Pandang Menguji Software

💡 ANALOGI — Menguji Mesin ATM

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.

⬛ Bagian 2 — Black-Box Testing

Uji Dari Perspektif Pengguna

1. Equivalence Partitioning (EP)

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.

ANALOGI

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 EkuivalensiRangeContoh Nilai TestExpected Result
Valid (dalam range)17 ≤ usia ≤ 6025, 17, 60✅ Diterima
Invalid (terlalu kecil)usia < 175, 16, 0❌ Error: "Usia terlalu kecil"
Invalid (terlalu besar)usia > 6061, 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!

2. Boundary Value Analysis (BVA)

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.

ANALOGI

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:

16 ❌ 17 ✅ 18 ✅ ... nominal ... 59 ✅ 60 ✅ 61 ❌

Uji 6 nilai kritis ini (batas bawah-1, batas bawah, batas bawah+1, batas atas-1, batas atas, batas atas+1)

3. Decision Table Testing

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-1TC-2TC-3TC-4
Member Premium?TTFF
Total > Rp 500rb?TFTF
Ada voucher?TFFT
→ 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.

⬜ Bagian 3 — White-Box Testing

Uji Dari Dalam Kode

Coverage Testing — Berapa % Kode Sudah Diuji?

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.

S
Statement Coverage
Setiap baris kode harus dieksekusi minimal 1×. Level paling dasar.
B
Branch Coverage
Setiap cabang (if/else) harus dieksekusi — baik yang True maupun False.
P
Path Coverage
Setiap kombinasi jalur eksekusi. Sangat menyeluruh tapi butuh banyak test case.
MC/DC
Modified Condition/ Decision
Standar avionik DO-178C. Setiap kondisi harus berpengaruh independen.
ANALOGI

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.

⚡ Bagian 4 — Non-Functional Testing

Uji "Seberapa Baik" Software Bekerja

Performance Testing — Uji Beban & Stres

Sebuah sistem mungkin berfungsi dengan sempurna saat digunakan 10 orang — tapi bagaimana saat 10.000 orang mengaksesnya secara bersamaan?

🏋️ Load Testing

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.

💥 Stress Testing

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.

⏱️ Endurance Testing

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?

🔍 Profiling

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.

🤖 Bagian 5 — Test Automation: Kapan dan Bagaimana?

Otomasi Bukan Solusi untuk Segalanya

💡 ANALOGI — Mesin Cuci vs Cuci Tangan

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.

📊 ROI Automasi — Kapan Layak Diotomasi?

KriteriaOtomasi ✅Manual ✅
Frekuensi eksekusiSangat sering (setiap commit, harian)Jarang (sekali per sprint)
Stabilitas fiturSudah stabil, jarang berubahMasih dalam pengembangan aktif
Jenis pengujianRegression, smoke, unit testExploratory, UX, edge cases baru
Data drivenBanyak kombinasi data berbedaSkenario unik dan kontekstual
Setup awalInvestasi waktu besar di awalLangsung bisa dijalankan

Formula ROI Automasi: ROI = (Waktu hemat × Frekuensi × Gaji tester) − Biaya pembuatan & maintenance script. Jika ROI positif dalam 3 bulan → layak diotomasi!

🛠️ Bagian 6 — Framework Automasi Populer

Ekosistem Tools Test Automation

🌐
Selenium
UI Testing · Web · Multi-browser
🌲
Cypress
E2E Testing · Modern Web · Real-time
🎭
Playwright
Cross-browser · Chromium/Firefox/WebKit
🐍
pytest
Unit & Integration · Python
JUnit 5
Unit Testing · Java · Spring
⚙️
Jenkins
CI/CD · Pipeline · Self-hosted
🐙
GitHub Actions
CI/CD · Cloud · Gratis untuk open source
🔄 Bagian 7 — CI/CD Pipeline dengan Automated Testing

Testing Berjalan Otomatis Setiap Commit

⚙️ Contoh GitHub Actions Workflow (.github/workflows/test.yml)

GitHub Actions CI PipelineYAML
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
🐍 Bagian 8 — Praktik Python: pytest Lengkap

Menulis Test Profesional dengan pytest

Kode yang Akan Diuji — kalkulator_diskon.py Python
# ================================================================
# 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 — test_kalkulator_diskon.py (pytest) Python · pytest
# ================================================================
# 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

▶️ Cara Menjalankan

TerminalBash
# 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%
✏️ Latihan Mandiri
📝 KUIS SESI 5

Uji Pemahaman Anda

Soal 1 — Black-Box: EP + BVA

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!

Soal 2 — Decision Table

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.

Soal 3 — White-Box Coverage

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.

Soal 4 — Python Challenge

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.

Soal 5 — Automasi Strategy

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.

💡 Rangkuman Kunci Sesi 5

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