Laravel menyediakan dua direktori bawaan untuk tes, yaitu tests/Unit dan tests/Feature, dan phpunit.xml mendaftarkan keduanya sebagai dua suite yang berbeda. Pembagian itu bukan sekadar penataan berkas, melainkan pembagian tingkat bukti yang dihasilkan. Topik ini membahas apa yang dibuktikan pada setiap tingkat, menunjukkan satu kelas logika murni yang diuji sebagai unit, satu perilaku penyimpanan yang diuji sebagai fitur, lalu diakhiri dengan panduan memilih tingkat untuk perilaku tertentu. Seluruh contoh dijalankan pada aplikasi catatan.
Dua direktori dan dua tingkat bukti#
Tes unit membuktikan bahwa satu kelas berperilaku benar dalam isolasi, sedangkan tes fitur membuktikan bahwa beberapa komponen bekerja bersama sebagaimana pada permintaan nyata. Perbedaan itu berakar pada kelas dasar yang diwarisi. Kelas tes unit umumnya mewarisi PHPUnit\Framework\TestCase sehingga aplikasi tidak di-boot, tidak ada container yang disiapkan, dan tidak ada database yang disentuh. Kelas tes fitur mewarisi Tests\TestCase bawaan aplikasi yang mem-boot Laravel penuh, sehingga semua fasilitas framework tersedia untuk kasusnya.
Tabel berikut merangkum konsekuensi dari perbedaan kelas dasar tersebut.
| Aspek | Pengujian unit | Pengujian fitur |
|---|---|---|
| Kelas dasar | PHPUnit\Framework\TestCase | Tests\TestCase aplikasi |
| Boot framework | Tidak dilakukan | Dilakukan pada setiap kasus |
| Database | Umumnya tidak disentuh | Dapat digunakan, biasanya melalui RefreshDatabase |
| Kecepatan per kasus | Paling cepat di antara keduanya | Lebih lambat karena boot dan migrasi |
| Yang dibuktikan | Logika kelas itu sendiri | Kerja sama komponen dengan framework |
| Contoh kasus | Pemformatan, perhitungan, aturan kelas | Penyimpanan model, relasi, alur permintaan |
Pengujian unit: logika murni tanpa framework#
Tes unit yang ideal menguji kelas yang tidak membutuhkan apa pun dari framework. Pada aplikasi catatan, kelas semacam itu berguna untuk pekerjaan teks, misalnya memangkas isi catatan menjadi pratinjau singkat. Kelas berikut disimpan pada app/Support, beserta dua kasus unit yang membuktikan kedua cabang logikanya, yaitu teks yang pendek dikembalikan utuh dan teks yang panjang dipotong dan diberi tanda elipsis.
# menyiapkan kelas support beserta tes unitnya, lalu menjalankannya
cd ~/aplikasi-catatan
mkdir -p app/Support
cat > app/Support/PemformatTeks.php <<'PHP'
<?php
namespace App\Support;
class PemformatTeks
{
public static function singkat(string $teks, int $maksimal): string
{
if (mb_strlen($teks) <= $maksimal) {
return $teks;
}
return mb_substr($teks, 0, $maksimal - 1) . '…';
}
}
PHP
cat > tests/Unit/PemformatTeksTest.php <<'PHP'
<?php
namespace Tests\Unit;
use App\Support\PemformatTeks;
use PHPUnit\Framework\TestCase;
class PemformatTeksTest extends TestCase
{
public function test_teks_yang_pendek_dikembalikan_utuh(): void
{
$this->assertSame('Halo', PemformatTeks::singkat('Halo', 10));
}
public function test_teks_yang_panjang_dipotong_bersama_ellipsis(): void
{
$this->assertSame('Laporan h…', PemformatTeks::singkat('Laporan harian', 10));
}
}
PHP
php artisan test --testsuite=Unit --filter=PemformatTeksTest | grep -E "PASS|Tests:" PASS Tests\Unit\PemformatTeksTest
Tests: 2 passed (2 assertions)Kedua kasus lulus tanpa memuat konfigurasi, service provider, maupun database, karena kelas dasar PHPUnit\Framework\TestCase tidak menyiapkan salah satunya. Angka pada baris Tests: menunjukkan dua asersi, satu per kasus, yang masing-masing membandingkan keluaran metode singkat() dengan nilai yang diharapkan. Karena tidak ada keadaan luar yang dilibatkan, hasil ini berlaku kapan pun kasusnya dijalankan, termasuk ketika database aplikasi sedang kosong atau sedang berisi data lain.
Pengujian fitur: kerja sama komponen#
Tes fitur menaikkan tingkat buktinya dengan mem-boot aplikasi penuh. Contoh berikut membuktikan perilaku yang melibatkan banyak komponen sekaligus, yaitu catatan baru yang tersimpan dan terhubung ke kategorinya. Trait RefreshDatabase menjalankan seluruh migrasi pada database SQLite dalam memori sebelum kasus berjalan, sehingga setiap kasus dimulai dengan skema yang bersih tanpa menyentuh database pengembangan.
# menyiapkan tes fitur penyimpanan catatan lalu menjalankannya
cd ~/aplikasi-catatan
cat > tests/Feature/PencatatanFiturTest.php <<'PHP'
<?php
namespace Tests\Feature;
use App\Models\Category;
use App\Models\Note;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PencatatanFiturTest extends TestCase
{
use RefreshDatabase;
public function test_catatan_baru_terhubung_ke_kategorinya(): void
{
$kategori = Category::factory()->create(['nama' => 'Kerja']);
$catatan = Note::factory()->create([
'judul' => 'Laporan harian',
'category_id' => $kategori->id,
]);
$this->assertSame($kategori->id, $catatan->category->id);
$this->assertSame(1, Note::count());
}
}
PHP
php artisan test --testsuite=Feature --filter=PencatatanFiturTest | grep -E "PASS|Tests:" PASS Tests\Feature\PencatatanFiturTest
Tests: 1 passed (2 assertions)Dua asersi pada kasus tersebut membawa dua bukti yang berbeda. Asersi pertama membaca relasi category dan memastikan id kategorinya cocok, yang berarti definisi relasi pada model, kolom category_id, dan kunci asingnya bekerja sesuai migrasi. Asersi kedua menghitung baris tabel notes dan memastikan jumlahnya satu, yang berarti penyimpanan benar-benar terjadi pada database. Kegagalan pada salah satu bagian itu, misalnya kolom yang tidak tercantum pada $fillable, akan menggagalkan kasus ini, padahal kegagalan semacam itu tidak mungkin dideteksi oleh tes unit atas kelas PemformatTeks.
Cakupan komponen pada kedua tingkat dibandingkan pada diagram berikut.
Memilih tingkat pengujian#
Aturan praktisnya mengikuti sifat pernyataan yang ingin dibuktikan. Perilaku yang dapat dibuktikan tanpa fasilitas framework, seperti perhitungan, pemformatan, dan aturan kelas, diuji sebagai unit. Perilaku yang menyentuh database, HTTP, session, atau event framework diuji sebagai fitur, karena kegagalan paling sering muncul pada perpindahan antar komponen. Pada aplikasi catatan, pemangkasan pratinjau termasuk golongan pertama, sedangkan penyimpanan catatan beserta relasinya termasuk golongan kedua.
Dari sisi proporsi, suite yang sehat biasanya memuat lebih banyak tes unit daripada tes fitur karena tes unit lebih mudah ditulis dan lebih cepat dijalankan. Tes fitur tetap tidak tergantikan karena tingkat kepercayaannya lebih tinggi, sehingga jalur penting aplikasi layak ditemani setidaknya satu kasus fitur masing-masing. Kombinasi keduanya bekerja apabila satu perilaku diuji pada tingkat termurah yang masih membuktikan pernyataannya, lalu kasus fitur disiapkan untuk alur yang menghubungkan beberapa komponen.
Kesalahan umum#
Kesalahan pertama adalah menggunakan fasilitas framework di dalam tes unit murni. Kelas yang mewarisi PHPUnit\Framework\TestCase tidak menyiapkan database, sehingga pemanggilan seperti Note::factory()->create() berhenti dengan error Call to a member function connection() on null karena koneksi database memang belum ada. Perbaikannya adalah memindahkan kasus tersebut ke tests/Feature dengan kelas dasar Tests\TestCase, atau menulis ulang kasusnya agar tidak menyentuh database sama sekali.
Kesalahan kedua adalah mengira penempatan berkas yang menentukan tingkatnya. Berkas di tests/Unit yang mewarisi Tests\TestCase tetap mem-boot framework penuh, sehingga kasusnya tidak lebih cepat dan tidak lebih terisolasi daripada tes fitur. Yang menentukan tingkatnya adalah kelas dasar dan fasilitas yang digunakan, bukan nama direktorinya.
Kesalahan ketiga adalah menulis tes fitur tanpa menata keadaan database. Tanpa RefreshDatabase atau penataan serupa, kasus membaca data sisa dari kasus lain, sehingga asersi seperti hitungan baris bergantung pada urutan eksekusi dan dapat berubah nilainya. Selalu mulai kasus fitur dari keadaan yang diketahui, dan trait RefreshDatabase merupakan cara paling lazim untuk itu.
Ringkasan#
- Tes unit membuktikan logika satu kelas dalam isolasi melalui kelas dasar
PHPUnit\Framework\TestCasetanpa boot framework, sedangkan tes fitur membuktikan kerja sama komponen melaluiTests\TestCaseyang mem-boot aplikasi penuh. - Kasus fitur pada contoh membuktikan migrasi, model, relasi, dan persistensi bekerja bersama, bukti yang tidak mungkin diberikan tes unit atas kelas terpisah.
- Pilih tingkat termurah yang masih membuktikan pernyataannya, jaga proporsi lebih banyak unit daripada fitur, dan mulai setiap kasus fitur dari keadaan database yang bersih melalui
RefreshDatabase.
Referensi#
- Testing: Getting Started — Laravel
— Dokumentasi pengujian Laravel, mencakup kelas dasar
TestCasedan traitRefreshDatabase. - PHPUnit 12.1 Manual — PHPUnit — Manual PHPUnit untuk penulisan tes yang tidak membutuhkan framework.