Tool sudah siap; sekarang giliran menyusun test yang layak dipercaya. Sebuah unit test yang baik punya anatomi tetap: menyiapkan keadaan, memanggil satu perilaku, lalu memeriksa hasilnya dengan assertion. Pada chapter ini kita menulis test pertama untuk kelas production sungguhan, mempelajari konvensi penamaan yang membuat test terbaca seperti spesifikasi, mengatur siklus hidup setUp/tearDown, menjalankan satu test dengan --filter, dan memakai exit code sebagai sinyal otomasi.
Struktur Proyek dan Test Pertama #
Kita akan menguji kelas kecil bernama Kalkulator pada proyek latihan tokobuku. Kode production diletakkan di folder src/, sementara test diletakkan terpisah di folder tests/ agar keduanya tidak tercampur saat deployment. Struktur foldernya seperti ini:
tokobuku/
├── phpunit.xml
├── src/
│ └── Kalkulator.php
└── tests/
└── KalkulatorTest.phpBerikut kelas production yang akan diuji — dua operasi aritmetika sederhana:
<?php
final class Kalkulator
{
public function tambah(int $a, int $b): int
{
return $a + $b;
}
public function kalikan(int $a, int $b): int
{
return $a * $b;
}
}Dan berikut berkas test-nya di tests/KalkulatorTest.php:
<?php
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../src/Kalkulator.php';
final class KalkulatorTest extends TestCase
{
public function testTambahMengembalikanJumlahDuaBilangan(): void
{
$kalkulator = new Kalkulator();
$hasil = $kalkulator->tambah(3, 4);
$this->assertSame(7, $hasil);
}
public function testKalikanMengembalikanHasilPerkalian(): void
{
$kalkulator = new Kalkulator();
$hasil = $kalkulator->kalikan(3, 4);
$this->assertSame(12, $hasil);
}
}Mari kita bedah bagian-bagian pentingnya:
use PHPUnit\Framework\TestCase;: mengimpor kelas dasar yang menyediakan seluruh fasilitas PHPUnit — termasuk method assertion sepertiassertSame().final class KalkulatorTest extends TestCase: nama kelas test wajib diakhiri sufiksTest, dan PHPUnit hanya memuat berkas bernama*Test.phpketika memindai folder. Ketika memindai foldertests, PHPUnit mengabaikan berkas lain.require_once __DIR__ . '/../src/Kalkulator.php';: memuat kelas yang diuji secara manual karena proyek mini ini belum memakai autoloader. Pada proyek Composer nyata, tugas ini diambil alih olehvendor/autoload.phplewat konfigurasibootstrap— topik yang sudah kita bahas pada chapter Menginstal PHPUnit dan dibahas mendalam di Autoloading dengan Composer .public function testTambah...(): void: setiap test adalah method publik yang namanya diawalitest. Eksekusi test dimulai dari baris pertama method, bukan dari konstruktor.$this->assertSame(7, $hasil);: assertion yang membandingkan nilai harapan (argumen pertama) dengan nilai nyata (argumen kedua) secara ketat — nilai dan tipe data harus sama.
Sekarang jalankan PHPUnit pada folder tests:
php phpunit-12.phar testsOutput yang Dihasilkan:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
.. 2 / 2 (100%)
Time: 00:00.001, Memory: 28.05 MB
OK (2 tests, 2 assertions)Dua titik pada baris progres mewakili dua test yang lulus, dan baris OK (2 tests, 2 assertions) merangkum hasilnya: dua method test dijalankan, masing-masing menyelesaikan satu assertion tanpa kegagalan. Baris Time dan Memory hanya informasi performa — angkanya bervariasi antar mesin dan tidak perlu dikhawatirkan.
Konvensi Penamaan yang Terbaca sebagai Spesifikasi #
Nama method test bukan sekadar pengenal; ia adalah kalimat spesifikasi yang dibaca programmer saat test gagal. Pola test + subjek + perilaku seperti testTambahMengembalikanJumlahDuaBilangan langsung menjelaskan apa yang dijanjikan method tambah() tanpa harus membuka isinya. Kalimat yang panjang tidak masalah — justru kalimat pendek seperti testHitung1 tidak menginformasikan apa pun saat merah.
Jika Anda lebih suka nama method bebas awalan test, PHPUnit menyediakan attribute #[Test] sebagai penanda resmi. Berikut keduanya berdampingan dalam satu berkas nyata:
<?php
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class Saldo
{
public function __construct(private int $nilai = 0)
{
}
public function nilai(): int
{
return $this->nilai;
}
}
final class KonvensiNamaTest extends TestCase
{
#[Test]
public function saldoAwalDimulaiDariNol(): void
{
$saldo = new Saldo();
$this->assertSame(0, $saldo->nilai());
}
}Method saldoAwalDimulaiDariNol dijalankan PHPUnit sebagai test karena attribute #[Test] menandainya, meskipun namanya tidak berawalan test. Attribute sendiri adalah fitur bahasa PHP yang sudah kita bahas pada chapter Attributes
. Menjalankan berkas di atas menghasilkan lulus:
php phpunit-12.phar --filter KonvensiNamaTest testsOutput yang Dihasilkan:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
. 1 / 1 (100%)
Time: 00:00.001, Memory: 28.05 MB
OK (1 test, 1 assertion)Dalam praktik, tim biasanya memilih SATU gaya saja lalu menuliskannya di standar kode agar tidak campur aduk. Gaya awalan test paling umum karena tidak butuh import tambahan; gaya #[Test] menonjolkan nama method yang bersih.
assertEquals dan assertSame #
PHPUnit menyediakan dua assertion kembar yang sering membingungkan pemula: assertEquals() dan assertSame(). Perbedaannya meniru dua operator perbandingan PHP — == (longgar) dan === (ketat). Demo singkat berikut menunjukkan perilaku keduanya pada string "1" dan integer 1:
<?php
var_dump("1" == 1);
var_dump("1" === 1);Output yang Dihasilkan:
bool(true)
bool(false)Operator == melakukan koersi tipe sebelum membandingkan sehingga string "1" dianggap sama dengan integer 1, sedangkan === menolak perbedaan tipe. Konsekuensinya di dunia test: assertEquals("1", 1) akan lulus, tetapi assertSame("1", 1) akan gagal. Sebagai aturan praktis, gunakan assertSame() sebagai pilihan baku karena banyak bug nyata justru berpindah tipe secara diam-diam — nomor telepon dari form adalah string, sedangkan dari database bisa integer. Kita akan membedah keduanya lebih dalam beserta katalog assertion lainnya pada chapter Assertions
.
Menjalankan Hanya Test yang Sedang Dikerjakan #
Saat memperbaiki satu method, menjalankan seluruh suite berulang kali membuang waktu. Opsi --filter membatasi eksekusi hanya pada test yang cocok dengan pola nama tertentu:
php phpunit-12.phar --filter testTambah testsOutput yang Dihasilkan:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
. 1 / 1 (100%)
Time: 00:00.001, Memory: 28.05 MB
OK (1 test, 1 assertion)Dari dua test yang tersedia, hanya testTambahMengembalikanJumlahDuaBilangan yang dijalankan karena namanya mengandung pola testTambah. Pola filter dicocokkan terhadap nama method maupun nama kelas, dan Anda bisa memakai ekspresi reguler untuk kasus yang lebih rumit.
Siklus Hidup: setUp dan tearDown #
Test yang baik saling independen — urutan eksekusi tidak boleh mengubah hasil. Untuk itu PHPUnit menjalankan setiap method test pada instance kelas test yang baru, lalu menyediakan dua hook: setUp() dipanggil sebelum tiap test, dan tearDown() sesudahnya. Test berikut membuktikan urutannya dengan echo:
<?php
use PHPUnit\Framework\TestCase;
final class SiklusHidupTest extends TestCase
{
private int $poin = 0;
protected function setUp(): void
{
$this->poin = 10;
echo "setUp() -> poin di-reset menjadi {$this->poin}\n";
}
protected function tearDown(): void
{
echo "tearDown() -> bersih-bersih setelah test selesai\n\n";
}
public function testPertamaMenambahPoin(): void
{
$this->poin += 5;
echo "testPertamaMenambahPoin() berjalan dengan poin={$this->poin}\n";
$this->assertSame(15, $this->poin);
}
public function testKeduaMenerimaPoinSegar(): void
{
echo "testKeduaMenerimaPoinSegar() berjalan dengan poin={$this->poin}\n";
$this->assertSame(10, $this->poin);
}
}Jalankan hanya kelas ini agar urutannya terbaca jelas:
php phpunit-12.phar --filter SiklusHidupTest testsOutput yang Dihasilkan:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
setUp() -> poin di-reset menjadi 10
testPertamaMenambahPoin() berjalan dengan poin=15
tearDown() -> bersih-bersih setelah test selesai
.setUp() -> poin di-reset menjadi 10
testKeduaMenerimaPoinSegar() berjalan dengan poin=10
tearDown() -> bersih-bersih setelah test selesai
. 2 / 2 (100%)
Time: 00:00.001, Memory: 28.05 MB
OK (2 tests, 2 assertions)Urutan yang tercetak membongkar mekanismenya: setUp() → method test → tearDown(), lalu siklus yang sama diulang untuk test berikutnya. Perhatikan bahwa test kedua menerima poin=10 meskipun test pertama sudah menaikkannya menjadi 15 — property $poin kembali segar karena PHPUnit membangun instance baru untuk tiap test. Titik progres muncul setelah tiap test selesai, itulah sebabnya echo dari test tampil mendahului titiknya.
Gunakan setUp() untuk menyiapkan objek yang dipakai banyak test (koneksi tiruan, fixture array, objek layanan), dan tearDown() untuk membersihkan efek samping seperti berkas sementara. PHPUnit juga menyediakan setUpBeforeClass() dan tearDownAfterClass() untuk pekerjaan yang cukup dilakukan sekali per kelas, tetapi gunakan dengan hemat karena state yang dibagikan antar test justru menggerus independensi.
Mengatur Testsuite dengan phpunit.xml #
Sepanjang chapter ini kita mengetik folder tests secara manual. Berkas phpunit.xml menghilangkan kebutuhan itu dengan mendeklarasikan testsuite — kelompok test yang dijalankan secara baku. Untuk proyek mini kita, konfigurasinya minimal:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.5/phpunit.xsd"
colors="true">
<testsuites>
<testsuite name="Unit">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>Elemen <testsuite name="Unit"> mengumpulkan semua berkas *Test.php di dalam <directory>tests</directory> menjadi satu suite bernama Unit. Anatomi lengkap berkas ini — termasuk atribut bootstrap untuk memuat vendor/autoload.php — sudah kita bahas pada chapter Menginstal PHPUnit
. Begitu berkas ini ada di akar proyek, PHPUnit membacanya secara otomatis setiap dijalankan, bahkan saat Anda tetap memberi argumen --filter:
php phpunit-12.phar --filter KalkulatorTestOutput yang Dihasilkan:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
Configuration: /tmp/tokobuku/phpunit.xml
.. 2 / 2 (100%)
Time: 00:00.001, Memory: 28.05 MB
OK (2 tests, 2 assertions)Baris baru Configuration: /tmp/tokobuku/phpunit.xml adalah bukti berkas konfigurasi terbaca — PHPUnit menampilkan lokasi berkasnya secara absolut (pada proyek Anda, path akan menyesuaikan lokasi folder proyek). Sejak titik ini, cukup jalankan php phpunit-12.phar tanpa argumen dan seluruh suite Unit dieksekusi.
Exit Code sebagai Sinyal Otomasi #
PHPUnit tidak hanya melaporkan hasil ke layar — ia juga mengakhiri prosesnya dengan exit code yang bisa dibaca lingkungan sekitarnya. Kode 0 berarti seluruh test lulus; kode 1 berarti ada kegagalan. Shell menyimpan kode keluar perintah terakhir pada variabel $?, jadi Anda bisa memeriksanya langsung. Untuk melihat bentuk kegagalan sekaligus exit code-nya, tambahkan test yang sengaja salah berikut ke folder tests:
<?php
use PHPUnit\Framework\TestCase;
final class SaldoTest extends TestCase
{
public function testSaldoAwalAdalahNol(): void
{
$saldoAwal = 1_000; // sengaja salah: nilai seharusnya 0
$this->assertSame(0, $saldoAwal);
}
}Lalu jalankan test tersebut dan tampilkan exit code-nya:
php phpunit-12.phar --filter SaldoTest
echo "exit=$?"Output yang Dihasilkan:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
Configuration: /tmp/tokobuku/phpunit.xml
F 1 / 1 (100%)
Time: 00:00.001, Memory: 28.05 MB
There was 1 failure:
1) SaldoTest::testSaldoAwalAdalahNol
Failed asserting that 1000 is identical to 0.
/tmp/tokobuku/tests/SaldoTest.php:11
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
exit=1Laporan kegagalannya sangat informatif: titik progres berubah menjadi F, nama test dan kelasnya tercantum (SaldoTest::testSaldoAwalAdalahNol), alasan kegagalan dijelaskan (Failed asserting that 1000 is identical to 0), dan berkas serta nomor baris assertion yang gagal ditunjukkan agar Anda bisa melompat langsung ke lokasinya. Baris terakhir exit=1 menegaskan bahwa proses PHPUnit ditutup dengan kode 1 — bandingkan dengan seluruh run hijau sebelumnya yang selalu berakhir exit=0. Kontrak inilah yang dipakai pipeline CI untuk memutuskan build lanjut atau berhenti, topik yang akan kita bahas tuntas pada chapter Continuous Integration
.
Ringkasan #
Kelas test PHPUnit adalah kelas final berekstensi TestCase di berkas *Test.php, berisi method publik berawalan test atau berattribute #[Test]. Setiap test berjalan pada instance segar dengan siklus setUp()/tearDown() di sekelilingnya, --filter mempersempit eksekusi saat Anda fokus pada satu perilaku, dan phpunit.xml menyimpan konfigurasi testsuite yang dibaca otomatis. Exit code 0/1 menjadikan PHPUnit warga pertama kelas di dunia otomasi. Dengan kerangka ini di tangan, kita siap memperdalam senjata utama pengujian — assertion — pada chapter berikutnya.
Referensi #
- Situs resmi PHPUnit — dokumentasi resmi dan panduan resmi penulisan test
- Attributes di PHP
— dasar sintaks
#[Test]dan#[DataProvider] - Dasar Class dan Object
— konsep
extendsyang dipakai kelas test