PHPUnit

Pengujian dasar, assertion, dan data provider.

Daftar isi

PHPUnit adalah kerangka pengujian unit paling mapan untuk PHP. Topik ini membangun kelas uji pertama untuk sebuah kelas kalkulator dan menjelaskan assertion yang paling sering dipakai. Data provider ditambahkan agar satu pengujian mencakup banyak kasus, sedangkan bagian akhir menunjukkan cara membaca keluaran ketika pengujian gagal. Semua perintah dijalankan pada PHPUnit 11 dengan PHP 8.3 melalui phar, sehingga urutan perintahnya dapat diikuti apa adanya.

Kelas uji pertama#

Pengujian di PHPUnit berupa kelas yang mewarisi PHPUnit\Framework\TestCase, dengan satu metode publik untuk setiap perilaku yang diuji. Nama berkasnya diakhiri Test.php dan nama kelasnya diakhiri Test, sehingga PHPUnit dapat menemukan keduanya. Konvensi ini menjaga agar pasangan berkas kode dan berkas uji tetap mudah ditemukan berdampingan. Kelas yang diuji di bawah ini sengaja dibuat kecil dan hanya berisi penjumlahan serta pembagian bilangan bulat.

php Kalkulator.php
<?php

declare(strict_types=1);

final class Kalkulator
{
    public function tambah(int $a, int $b): int
    {
        return $a + $b;
    }

    public function bagi(int $a, int $b): int
    {
        if ($b === 0) {
            throw new InvalidArgumentException('Pembagi tidak boleh nol');
        }

        return intdiv($a, $b);
    }
}

Berkas uji berikut menguji kedua metode tersebut. Perhatikan require_once di bagian atas, karena contoh pada topik ini berjalan tanpa autoloader Composer. Pada proyek yang memakai Composer, baris itu digantikan oleh penyertaan vendor/autoload.php sekali di awal. Berkas uji tidak dijalankan dengan perintah php karena mewarisi TestCase milik PHPUnit, sehingga eksekusinya dilakukan oleh perintah phpunit.

php Berkas KalkulatorTest.php
// tidak-dijalankan
// berkas uji dimuat oleh perintah phpunit, bukan dijalankan langsung dengan php
<?php

declare(strict_types=1);

require_once __DIR__ . '/Kalkulator.php';

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class KalkulatorTest extends TestCase
{
    public function testTambahDuaBilanganPositif(): void
    {
        $kalkulator = new Kalkulator();

        $this->assertSame(8, $kalkulator->tambah(3, 5));
    }

    public function testBagiDenganPembagiNolMelemparPengecualian(): void
    {
        $kalkulator = new Kalkulator();

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('Pembagi tidak boleh nol');

        $kalkulator->bagi(10, 0);
    }

    #[DataProvider('tambahData')]
    public function testTambahDenganBanyakKasus(int $a, int $b, int $diharapkan): void
    {
        $kalkulator = new Kalkulator();

        $this->assertSame($diharapkan, $kalkulator->tambah($a, $b));
    }

    public static function tambahData(): array
    {
        return [
            'dua bilangan positif' => [3, 5, 8],
            'bilangan negatif' => [-3, -5, -8],
            'dengan nol' => [0, 7, 7],
        ];
    }
}

Eksekusi seluruh uji dilakukan dengan menyerahkan berkas uji ke perintah phpunit. Baris yang berisi waktu dan penggunaan memori dibuang dari keluaran berikut karena nilainya bergantung pada kecepatan mesin. Bagian pentingnya adalah ringkasan hasil pada baris akhir.

bash
# jalankan seluruh uji pada berkas KalkulatorTest
phpunit --no-progress KalkulatorTest.php 2>&1 | grep -v '^Time:' | cat -s
text Keluaran
PHPUnit 11.5.57 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.27

OK (5 tests, 6 assertions)

Ringkasan OK (5 tests, 6 assertions) menghitung lima pengujian dari tiga sumber, yaitu dua metode uji biasa dan tiga kasus dari data provider. Jumlah assertion bertambah karena expectException juga terhitung. Satu pengujian yang berhasil tidak mencetak apa pun, sehingga keberhasilan hanya terlihat dari ringkasan tersebut.

Assertion dan pengecualian#

Assertion adalah pernyataan ekspektasi yang diperiksa PHPUnit saat pengujian berjalan. Dua assertion yang paling sering dibandingkan adalah assertSame dan assertEquals. Metode assertSame membandingkan nilai beserta tipenya secara ketat, sedangkan assertEquals membandingkan nilai setelah konversi tipe. Karena jalur ini mengajarkan strict_types sejak awal, gunakan assertSame sebagai pilihan utama agar perilaku pengujian sama ketatnya dengan perilaku kode produksi.

Pengecualian dites dengan expectException() sebelum baris yang memicunya. PHPUnit menandai pengujian berhasil apabila pengecualian yang tepat terlempar, dan menganggapnya gagal apabila kode berjalan tanpa melempar apa pun. Pemanggilan expectExceptionMessage() menambahkan pemeriksaan isi pesan, sehingga pengecualian yang salah sumber tetap terdeteksi. Sejumlah assertion lain juga sering berguna, seperti assertCount untuk jumlah elemen array dan assertStringContainsString untuk isi string.

Satu metode uji sebaiknya membuktikan satu perilaku. Pengujian yang menumpuk banyak ekspektasi dalam satu metode membuat pesan kegagalan kurang spesifik, karena pembaca hanya tahu salah satu assertion gagal tanpa tahu perilaku mana yang benar-benar rusak. Nama metode yang menyebut perilakunya secara eksplisit, seperti testBagiDenganPembagiNolMelemparPengecualian, membuat daftar kegagalan terbaca sebagai daftar gejala yang langsung dapat ditindaklanjuti.

Data provider#

Data provider mengalirkan banyak kasus ke satu metode uji, sehingga logika pengujian tidak disalin untuk setiap kasus. Provider dideklarasikan sebagai metode static publik yang mengembalikan array, lalu ditautkan lewat atribut #[DataProvider('nama')].

Setiap baris provider menghasilkan satu eksekusi pengujian yang berdiri sendiri. Satu kasus gagal tidak menghentikan kasus lainnya, dan ringkasan akhir menghitung semuanya sebagai pengujian terpisah. Pola ini juga memisahkan data dari logika, sehingga menambah kasus baru hanya berarti menambah satu baris array.

Pada proyek nyata, seluruh berkas uji biasanya berkumpul di folder tests/ dan PHPUnit dijalankan tanpa argumen berkas, sehingga seluruh rangkaian dieksekusi sekaligus. Konfigurasinya ditampung pada berkas phpunit.xml di akar proyek, berisi direktori uji, skrip bootstrap yang memuat autoloader Composer, dan daftar variabel lingkungan khusus pengujian. Dengan konfigurasi itu, perintah phpunit tanpa argumen apa pun menjadi pintasan yang menjalankan seluruh rangkaian, dan inilah bentuk yang dipakai topik Penguatan dan Tinjauan ketika aplikasi mini diuji.

Membaca keluaran kegagalan#

Bentuk keluaran ketika pengujian gagal perlu dikenali sejak awal. Berkas berikut berisi satu assertion yang ekspektasinya sengaja keliru, yaitu mengharapkan 10 padahal hasil yang benar adalah 5.

php Pengurang.php
<?php

declare(strict_types=1);

final class Pengurang
{
    public function kurang(int $a, int $b): int
    {
        return $a - $b;
    }
}
php Berkas PengurangTest.php
// tidak-dijalankan
// berkas uji dimuat oleh perintah phpunit, bukan dijalankan langsung dengan php
<?php

declare(strict_types=1);

require_once __DIR__ . '/Pengurang.php';

use PHPUnit\Framework\TestCase;

final class PengurangTest extends TestCase
{
    public function testKurangDuaBilangan(): void
    {
        $pengurang = new Pengurang();

        // ekspektasi sengaja keliru untuk menunjukkan bentuk keluaran kegagalan
        $this->assertSame(10, $pengurang->kurang(7, 2));
    }
}
bash
# jalankan uji yang ekspektasinya sengaja keliru untuk melihat bentuk kegagalan
phpunit --no-progress PengurangTest.php 2>&1 | grep -v '^Time:' | cat -s
text Keluaran
PHPUnit 11.5.57 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.27

There was 1 failure:

1) PengurangTest::testKurangDuaBilangan
Failed asserting that 5 is identical to 10.

/home/kal/my-project/site/content/php/pengujian-api/phpunit/PengurangTest.php:16

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Keluaran kegagalan dibaca dari atas ke bawah. Setelah baris There was 1 failure, bagian penjelasan memuat nama metode uji, pesan perbandingan, dan baris persis tempat assertion gagal. Ringkasan FAILURES dengan rincian jumlahnya berada di baris akhir. Pesan perbandingan sudah menyebut kedua nilai, sehingga langkah pertama perbaikan biasanya jelas, yaitu memastikan apakah kode yang diuji atau ekspektasinya yang keliru.

Kesalahan umum#

  • Menyalin logika yang diuji ke dalam pengujian. Menghitung ulang hasil dengan rumus yang sama membuat pengujian hanya mengulang kode, bukan memeriksanya. Tetapkan nilai ekspektasi secara eksplisit seperti 8 pada tambah(3, 5).
  • Memakai assertEquals untuk tipe yang sensitif. Perbandingan longgar menganggap "5" sama dengan 5, sehingga bug konversi tipe lolos dari pengujian. Pilih assertSame kecuali ada alasan khusus.
  • Menulis metode provider tanpa static. PHPUnit 11 menuntut data provider berupa metode statis, dan pemanggilan metode nonstatis berakhir dengan error saat pengujian dimuat.
  • Meletakkan expectException() setelah baris pemicunya. Pengecualian terlempar lebih dulu dan assertion tidak pernah terjangkau, sehingga pengujian gagal dengan alasan yang menyesatkan.

Ringkasan#

  • Pengujian PHPUnit berupa kelas yang mewarisi TestCase, dengan satu metode test...() untuk tiap perilaku dan berkas uji bernama sama dengan kelasnya.
  • assertSame membandingkan nilai beserta tipe, expectException() dites dengan menunjuk pengecualian sebelum baris pemicunya, dan assertion lain seperti assertCount melengkapi kebutuhan sehari-hari.
  • Data provider berupa metode statis dengan atribut #[DataProvider] mengalirkan banyak kasus ke satu metode uji, dan setiap kasus dihitung sebagai pengujian terpisah.
  • Keluaran kegagalan memuat nama metode, pesan perbandingan, dan baris sumbernya, sehingga lokasi perbaikan langsung terlihat.

Referensi#