Nafies Luthfi

Life will always feel wonderful if we always think positively.

Testing Laravel: Tips untuk Workflow TDD

Bismillahirrahmanirrahim.

Ketika mengerjakan project Laravel dengan metode TDD, bisa jadi ada kejenuhan. Misalnya kita “terpaksa” harus mengetik perintah vendor/bin/phpunit di terminal setiap kali kita ingin menjalankan PHPUnit. Well, jenuh juga mengetik perintah itu berulang-ulang, atau kita bolak-balik ke terminal untuk menekan tombol panah atas (history terminal), kemudian tekan tombol Enter untuk menjalankan PHPUnit.

Atau ketika kita menjalankan testing, kita menunggu testing berjalan hingga sampai titik yang sedang dikerjakan, padahal (sebenarnya) hanya perlu menjalankan testing untuk satu fitur yang sedang dikerjakan saja.

Atau saat testing berjalan, prosesnya ada yang lambat, tetapi kita tidak tahu proses yang lambat itu pada Test Case atau Test method yang mana?

Problem

Nah, problem seperti ini saya temukan di 1-6 bulan pertama saya belajar TDD :

  1. Menjalankan perintah PHPUnit berulang-ulang (ini melanggar DRY, kan?).
  2. Menunggu jalannya testing hingga sampai titik yang sedang dikerjakan (menunggu error muncul).
  3. Semua testing passed, tetapi prosesnya lambat, dan tidak tahu di titik mana testing yang lambat itu terjadi (karena yang tampil di terminal hanya titik-titik saja).
  4. Problem di database, di mana data yang di-generate oleh model factory (saat jalannya testing) dapat menjadikan hasil test yang berbeda.

Dari beberapa problem tersebut, saya mencari tips dari artikel-artikel di internet untuk menemukan solusinya.

Solusi dan Tips

Berikut ini beberapa tips dan solusi yang bisa saya bagi ke teman-teman, mungkin ada yang belum tahu dan bisa menjadi referensi.

Run Test on file change

Solusi untuk problem pertama kita adalah PHPUnit Watcher. Ketika kita menjalankan perintah phpunit-watcher watch pada project Laravel kita, maka dia akan menjalankan PHPUnit, kemudian menunggu perubahan file .php yang ada di dalam direktori app dan tests dalam project Laravel kita.

Yap, ketika phpunit-watcher ini “standby”, setiap ada file php yang di-save pada kedua direktori tersebut, PHPUnit langsung dijalankan, kemudian memberikan hasilnya (passed atau failed) dengan notifikasi desktop.

Dampak positifnya, kita tidak perlu repot bolak balik terminal untuk menjalankan vendor/bin/phpunit. Kita bisa fokus “ngoding” di text-editor atau IDE kita. Ini sangat efektif dalam memangkas waktu development kita dalam workflow TDD. Jika teman-teman ingin mencobanya, silakan langsung ikuti petunjuk instalasinya.

https://github.com/spatie/phpunit-watcher

Ini screenshot phpunit-watcher saat “standby” menunggu perubahan file.

phpunit-watcher in action

Referensi:
Saya dapat tips tentang tool ini dari video Laracon EU Amo Chohan. Kalau teman-teman ada waktu, silakan lihat videonya dari awal sampai selesai, tips-tipsnya sangat bermanfaat.

Menggunakan trait DatabaseTransaction

Trait DatabaseTransaction pada Test Case class Laravel adalah alat bantu yang akan mengembalikan kondisi database kita setiap sebuah test method selesai dijalankan. Dengan bantuan trait ini, kita tinggal melakukan persiapan data pada database khusus untuk testing (pada project yang bersangkutan).

Script-script testing kita dapat menggunakan record-record pada database tersebut saat menjalankan test. Setiap satu testing method selesai, database dikembalikan seperti semula. Trait ini sangat berguna saat kita ingin mulai membuat testing untuk project Laravel existing yang belum ada testingnya.

Tips:
Jika ingin mulai membuat testing untuk project Laravel existing, jangan lupa memisahkan database yang digunakan untuk testing dengan database aplikasi.

Mendeteksi test method yang lambat

Seperti yang kita bahas pada paragraf pembuka di atas, ada kalanya testing berjalan lambat, tapi kita tidak mengetahui pada test method mana yang prosesnya lambat. Solusi yang bisa kita gunakan adalah sebuah paket bernama phpunit-speedtrap.

https://github.com/johnkary/phpunit-speedtrap

“SpeedTrap helps you identify slow tests but cannot tell you why those tests are slow.”

Jadi tool ini hanya memberi tahu kita jika ada testing yang berjalan lambat (satu test jalan lebih dari 500ms), tetapi dia tidak dapat memberitahu kenapa testingnya lambat. Tugas kita lah sebagai developer untuk menemukannya.

Beberapa hal yang saya temukan dapat membuat testing lambat :

  1. Terlalu banyak meng-generate record yang “tidak perlu” melalui model factory. Karena itu kita perlu memperhatikan ini pada saat membuat model factory :
    Using Factories -> Relationships -> Relations & Attribute Closures
  2. Terdapat N+1 problem pada aplikasi controller/view yang sedang di-test. Apa itu N+1? Yaitu ada query ke database dalam sebuah loop. Referensinya lihat di sini atau di sini.
  3. Ada batch insert record ke dalam database yang terlalu banyak dalam satu test.
  4. Terdapat API Request ke pihak ketiga, di mana request tersebut bergantung kepada koneksi internet PC/laptop kita.

Jadi ketika sudah menggunakan phpunit-speedtrap ini, kita mendapat kesempatan untuk me-refactor testing kita agar lebih optimal. Contoh penggunaannya? Silakan coba install project Free PMO di localhost teman-teman, kemudian jalankan testingnya.

PHPUnit SpeedTrap in action

Menggunakan command option ‘–filter’

Ketika testing kita sudah mulai banyak, pastinya kita akan menunggu jalannya testing lebih lama. Bandingkan testing untuk dua project berikut :

Many Test Methods

dengan

Few Test Methods

Jika kita memiliki banyak test method (yang dilambangkan dengan satu tanda titik), maka semakin lama waktu yang diperlukan oleh PHPUnit untuk menyelesaikan seluruh test.

Jika kita ingin membatasi agar PHPUnit menjalankan test hanya pada bagian tertentu saja, maka kita bisa gunakan option command --filter. Contoh :

$ vendor/bin/phpunit --filter ReceiptCollectionTest

Filter Tests

“ReceiptCollectionTest” adalah (semacam) kata kunci. Kata kunci ini dapat berupa (bagian dari) nama class TestCase atau (bagian dari) nama test method. Jadi hanya class test case atau test method itu saja yang akan dijalankan oleh PHPUnit (disesuaikan dengan fitur yang sedang kita kerjakan).

Menggunakan command option ‘–stop-on-failure’

Langsung pada contohnya :

$ vendor/bin/phpunit --filter ReceiptCollectionTest --stop-on-failure

Tests Stop on Failure

Dari situ kita bisa lihat, command option --stop-on-failure adalah perintah kepada PHPUnit agar segera menghentikan jalannya test ketika menemukan test yang gagal (ada pesan error). Tujuannya agar kita tidak perlu menunggu sampai testing selesai dijalankan untuk melihat pesan error yang tampil pada terminal.

Btw, kita juga dapat memberikan command option ini pada saat menggunakan phpunit-watcher.

Menggunakan script ‘Hash::setRounds(5)’

Nah, script Hash::setRounds(5) ini benar-benar mengurangi durasi waktu test, utamanya jika script-script testing ada yang melibatkan function bcrypt(). Umumnya jika dalam testing kita menggunakan fitur autentikasi atau login user, kemungkinan besar menggunakan bcrypt() ini, yaitu pada factory untuk model User.

Kita bisa menempatkan script Hash::setRounds(5) ini pada class Base TestCase kita. Contoh penggunannya, kita bisa cek script ini digunakan pada trait CreatesApplication.php di project Free PMO.

Jika kita jalankan testnya:

Tests with Hash::setRounds

Kemudian uncomment Hash::setRounds(5) trait CreatesApplication.php.

<?php
// tests/createApplication.php

public function createApplication()
{
    $app = require __DIR__.'/../bootstrap/app.php';

    $app->make(Kernel::class)->bootstrap();
    // \Hash::setRounds(5);

    return $app;
}

Jika kita jalankan testing lagi :

Tests with out Hash::setRounds

Nah kelihatan, kan? Lumayan selisih waktunya 2x lipat lebih cepat. Artinya script Hash::setRounds(5) ini sangat berpengaruh pada kecepatan jalannya testing kita.

Saya belum tahu persis apa makna/fungsi dari script ini. Silakan teman-teman cek artikel ini Two tips to speedup your Laravel tests.

Memanfaatkan (Bash) Aliases

Saya menggunakan OS Ubuntu 16.04 untuk bekerja. Saya mendaftarkan alias perintah-perintah yang sering digunakan untuk mempercepat mengetik perintah di terminal. Beberapa yan saya gunakan untuk mempercepat workflow TDD Laravel :

# phpunit
alias punit='vendor/bin/phpunit'
alias punitf='vendor/bin/phpunit --filter'
alias punits='vendor/bin/phpunit --stop-on-failure'
alias punitsf='vendor/bin/phpunit --stop-on-failure --filter'

# phpunit-watcher
alias wpunit='phpunit-watcher watch'
alias wpunitf='phpunit-watcher watch --filter'
alias wpunits='phpunit-watcher watch --stop-on-failure'
alias wpunitsf='phpunit-watcher watch --stop-on-failure --filter'

Kesimpulan

Nah demikian teman-teman, kita sudah membahas beberapa tips yang biasa saya gunakan dan manfaatkan untuk mempermudah, mempercepat, dan mengurangi kejenuhan dalam menerapkan workflow Test-Driven Development dalam project Laravel. Mudah-mudahan bermanfaat menjadi referensi bagi teman-teman yang membutuhkan.

Jika ada masukan terkait tips-tips ini, atau ada tips lain yang bisa teman-teman share, silakan disampaikan di komentar.

Terima kasih atas waktunya.