Test pertama kita pada chapter Menulis PHPUnit Tests
sudah berjalan hijau, tetapi kualitas sebuah test sepenuhnya ditentukan oleh assertion — pernyataan yang membandingkan hasil aktual dengan hasil yang diharapkan. Test tanpa assertion hanyalah eksekusi kode tanpa verdict: kegagalan bisa lolos diam-diam. Chapter ini memperlajari assertion secara sistematis: katalog yang paling sering dipakai sehari-hari, kebijakan strict dulu yang memilih assertSame sebelum assertEquals, cara menguji exception dengan expectException, penanganan angka float dengan toleransi delta, pesan kegagalan yang membantu diagnosis, dan assertion komposit buatan sendiri untuk memangkas pengulangan.
Katalog Assertion yang Sering Dipakai #
Delapan assertion berikut menutup sebagian besar kebutuhan test harian. Semuanya adalah method pada PHPUnit\Framework\TestCase yang dipanggil lewat $this->:
| Assertion | Memeriksa | Gagal ketika |
|---|---|---|
assertSame($expected, $actual) | nilai dan tipe identik (===) | nilai berbeda, atau tipe berbeda |
assertEquals($expected, $actual) | nilai setara (== longgar) | nilai tidak dianggap sama |
assertTrue($actual) | nilai adalah true | nilai false atau bukan true |
assertFalse($actual) | nilai adalah false | nilai true atau bukan false |
assertEmpty($actual) | string/array/countable kosong | ada isi |
assertCount($n, $array) | jumlah elemen array/Countable | jumlah berbeda |
assertStringContainsString($needle, $haystack) | string memuat potongan teks | potongan tidak ditemukan |
assertInstanceOf($class, $object) | objek adalah instance class | bukan instance class tersebut |
Berikut satu test yang memakai seluruh katalog tersebut terhadap satu objek keranjang belanja:
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class ItemKeranjang
{
/**
* @param list<string> $kataKunci
* @param list<string> $diskonAktif
*/
public function __construct(
public readonly string $produk,
public readonly int $harga,
public readonly array $kataKunci,
public readonly array $diskonAktif,
) {
}
}
final class KatalogAssertionTest extends TestCase
{
public function testKatalogAssertionHarian(): void
{
$item = new ItemKeranjang(
"Belajar PHP Dasar",
150000,
["php", "pemula"],
[],
);
$this->assertInstanceOf(ItemKeranjang::class, $item);
$this->assertSame("Belajar PHP Dasar", $item->produk);
$this->assertSame(150000, $item->harga);
$this->assertTrue($item->harga > 100000);
$this->assertFalse($item->harga > 200000);
$this->assertCount(2, $item->kataKunci);
$this->assertStringContainsString("PHP", $item->produk);
$this->assertEmpty($item->diskonAktif);
}
}php /home/z/my-project/bin/phpunit.phar tests/KatalogAssertionTest.phpBerikut output nyata dari perintah tersebut:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
. 1 / 1 (100%)
Time: 00:00, Memory: 28.05 MB
OK (1 test, 8 assertions)Angka 8 assertions berasal dari delapan baris $this->assert*() di dalam satu test. Mengelompokkan beberapa assertion tentang satu objek yang sama dalam satu test masih sehat, karena semuanya menguji satu perilaku: “item keranjang terbentuk dengan benar”. Yang perlu dihindari adalah menggabungkan assertion dari beberapa perilaku tak berhubungan — saat satu baris gagal, Anda tidak bisa tahu fitur mana yang rusak tanpa membaca ulang seluruh test.
Kebijakan Strict Dulu: assertSame Sebelum assertEquals #
Perbedaan assertSame dan assertEquals adalah perbedaan === dan ==. PDO, json_decode(), dan banyak API eksternal mengembalikan angka sebagai string; assertEquals meloloskan "150000" sebagai 150000, sementara assertSame menolaknya. Dua test berikut sengaja memperlihatkan bedanya:
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class PerbandinganKetatTest extends TestCase
{
public function testAssertEqualsMeloloskanTipeBerbeda(): void
{
$totalDariDatabase = "150000";
$this->assertEquals(150000, $totalDariDatabase);
}
public function testAssertSameMenolakTipeBerbeda(): void
{
$totalDariDatabase = "150000";
$this->assertSame(150000, $totalDariDatabase);
}
}php /home/z/my-project/bin/phpunit.phar tests-fail/PerbandinganKetatTest.phpBerikut output nyata dari perintah tersebut:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
.F 2 / 2 (100%)
Time: 00:00.001, Memory: 28.05 MB
There was 1 failure:
1) PerbandinganKetatTest::testAssertSameMenolakTipeBerbeda
Failed asserting that '150000' is identical to 150000.
/tmp/a97/assertions/tests-fail/PerbandinganKetatTest.php:20
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.Baca output tersebut baris demi baris:
- Progress
.Fmenandakan test pertama lulus (titik) dan test kedua gagal (huruf F) — persis urutan deklarasinya. Failed asserting that '150000' is identical to 150000.menampilkan nilai aktual dengan tanda kutip dan nilai harapan tanpa tanda kutip. Kutip itulah petunjuk tipenya: aktual adalah string, harapan adalah integer.- Baris berkas
:20menunjuk persis baris assertion yang gagal, danFAILURES!diakhiri ringkasanTests: 2, Assertions: 2, Failures: 1.
Menguji Exception dengan expectException #
Kode yang benar tidak hanya mengembalikan nilai — kode juga harus menolak input yang salah, dan penolakan itu biasanya berbentuk exception. PHPUnit menguji jalur ini dengan expectException() dan expectExceptionMessage() yang dideklarasikan sebelum baris pemicunya:
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
function normalisasiUmur(int $umur): int
{
if ($umur < 0) {
throw new InvalidArgumentException("umur tidak boleh negatif: {$umur}");
}
return min($umur, 150);
}
final class ValidasiUmurTest extends TestCase
{
public function testUmurNegatifDitolak(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("umur tidak boleh negatif");
normalisasiUmur(-1);
}
public function testUmurValidDikembalikanApaAdanya(): void
{
$this->assertSame(25, normalisasiUmur(25));
}
}php /home/z/my-project/bin/phpunit.phar tests/ValidasiUmurTest.phpBerikut output nyata dari perintah tersebut:
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, 3 assertions)Perhitungan 3 assertions dari dua test tersebut menarik: expectException() dan expectExceptionMessage() di test pertama masing-masing dihitung sebagai satu assertion, ditambah satu assertSame() di test kedua. expectExceptionMessage() memeriksa substring — pesan exception cukup memuat potongan yang ditulis, sehingga bagian yang berubah-ubah (seperti nilai {$umur}) tidak perlu disalin harfiah.
Angka Float dan Toleransi Delta #
Bilangan float disimpan dalam representasi biner sehingga 0.1 + 0.2 tidak pernah tepat sama dengan 0.3. Perilaku ini menipu karena echo membulatkan tampilannya:
<?php
$total = 0.1 + 0.2;
echo "echo menampilkan: {$total}\n";
var_dump($total === 0.3);Output yang Dihasilkan:
echo menampilkan: 0.3
bool(false)Tampilan 0.3 di baris pertama kontras dengan bool(false) di baris kedua: nilai sebenarnya adalah 0.30000000000000004, hanya saja echo membulatkannya saat mencetak. Akibatnya, assertSame(0.3, $hasilFloat) hampir selalu gagal meski perhitungannya benar. PHPUnit menyediakan assertEqualsWithDelta() untuk membandingkan float dalam toleransi:
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class HitungPajakTest extends TestCase
{
public function testTotalDihitungDalamToleransiDelta(): void
{
$hargaSatuan = 0.1;
$ongkosKirim = 0.2;
$this->assertEqualsWithDelta(0.3, $hargaSatuan + $ongkosKirim, 0.000001);
}
}php /home/z/my-project/bin/phpunit.phar tests/HitungPajakTest.phpBerikut output nyata dari perintah tersebut:
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)Argumen ketiga 0.000001 adalah delta — selisih maksimum yang masih diterima. Pilih delta seperlunya: terlalu besar bisa meloloskan bug pembulatan nyata, terlalu kecil membuat test rapuh terhadap perbedaan platform. Untuk uang, strategi yang lebih tahan banting tetap menyimpan nilai sebagai integer satuan terkecil (rupiah penuh, atau sen) seperti yang kita latih di section-section sebelumnya.
Pesan Assertion yang Membantu Diagnosis #
Setiap assertion menerima argumen tambahan terakhir: pesan kustom yang muncul saat gagal. Pesan ini bernilai ketika nilai yang dibandingkan tidak cukup bercerita sendiri — misalnya saat dihasilkan dari perhitungan panjang atau data eksternal. Lihat perbedaannya pada kegagalan berikut:
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class PesanKustomTest extends TestCase
{
public function testTotalBelanjaSesuaiStruk(): void
{
$totalDariStruk = 150000;
$totalDariKode = 150750;
$this->assertSame(
$totalDariStruk,
$totalDariKode,
"Total belanja kasir tidak cocok dengan perhitungan kode",
);
}
}php /home/z/my-project/bin/phpunit.phar tests-fail/PesanKustomTest.phpBerikut output nyata dari perintah tersebut:
PHPUnit 12.5.39 by Sebastian Bergmann and contributors.
Runtime: PHP 8.4.23
F 1 / 1 (100%)
Time: 00:00.001, Memory: 28.05 MB
There was 1 failure:
1) PesanKustomTest::testTotalBelanjaSesuaiStruk
Total belanja kasir tidak cocok dengan perhitungan kode
Failed asserting that 150750 is identical to 150000.
/tmp/a97/assertions/tests-fail/PesanKustomTest.php:14
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.Pesan kustom tampil sebagai baris pertama kegagalan, diikuti perbandingan nilai bawaan PHPUnit. Dalam suite ratusan test, baris kontekstual seperti ini menghemat menit-menit investigation: pembaca langsung tahu domain apa yang rusak (totals kasir vs kode) tanpa harus membuka berkasnya. Tulis pesan kustom secukupnya — untuk perbandingan yang sudah jelas (string literal dua baris di atas), pesan bawaan PHPUnit sudah lebih dari cukup.
Assertion Komposit Buatan Sendiri #
Ketika rangkaian assertion yang sama muncul berulang di banyak test, ekstrak menjadi method bantu bernama assert...() di dalam class test Anda. Method komposit hanyalah method biasa yang memanggil assertion lain lewat $this->, sehingga kegagalannya tetap menikmati pesan dan nomor baris yang informatif:
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class NomorPesananTest extends TestCase
{
public function testNomorPesananBaruSesuaiFormat(): void
{
$this->assertNomorPesananValid("ORD-1001");
}
private function assertNomorPesananValid(mixed $nomor): void
{
$this->assertIsString($nomor, "nomor pesanan harus berupa string");
$this->assertMatchesRegularExpression(
'/^ORD-\d{4}$/',
$nomor,
"format nomor pesanan harus ORD-NNNN, contoh: ORD-1001",
);
}
}php /home/z/my-project/bin/phpunit.phar tests/NomorPesananTest.phpBerikut output nyata dari perintah tersebut:
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, 2 assertions)Method assertNomorPesananValid() membungkus dua pemeriksaan — tipe dan format — dengan pesan yang menyebutkan contoh yang benar. Setiap test pemakainya kini satu baris, dan ketika aturan format berubah (misalnya menjadi tujuh digit), yang diubah satu tempat saja. Di proyek yang lebih besar, kumpulan method komposit seperti ini layak dipindahkan ke class TestCase dasar milik proyek yang diwarisi semua test class. Aturan praktisnya: komposit untuk kontrak yang berulang, bukan untuk perbandingan sekali pakai.
Ringkasan #
Assertion adalah verdict dari sebuah test: assertSame menjaga tipe dengan perbandingan ketat, katalog harian menutup kasus umum, expectException menguji jalur penolakan, assertEqualsWithDelta menangani float, pesan kustom mempercepat diagnosis, dan method komposit memangkas pengulangan. Semua pilihan tadi menyervis satu tujuan: kegagalan yang jelas dan cepat dibaca. Satu pola yang sering menggoda setelah ini adalah menyalin-tempel test yang sama dengan input berbeda — PHPUnit punya alat yang lebih elegan untuk itu, dan kita bahas pada chapter Data Providers
.