Melakukan Unit Test Di Laravel


Satu hal yang membuat saya sulit beradaptasi dengan Eloquent (data mapper milik Laravel) adalah relasi seperti one-to-many di-hard code langsung dalam bentuk pemanggilan function. Pada JPA di dunia Java, relasi one-to-many tetap sebuah Collection biasa yang memiliki getter dan setter. Implementasi JPA akan menggunakan proxy (method yang dipanggil secara transparan tanpa sepengetahuan pengguna) atau bytecode generation untuk melakukan fetching. Dengan demikian, model di JPA (tepatnya entity) akan terlihat seperti sebuah class normal yang dapat diuji secara biasa. Tapi, pada Eloquent, pengujian model yang memiliki relasi dengan model lainnya (seperti one-to-many) akan melibatkan database.

Sebagai contoh, berikut ini adalah contoh definisi model yang diwakili class Faktur:

<?php

class Faktur extends Eloquent {

    protected $dates = array('tanggal');

}

?>

Class Pelanggan akan memiliki relasi one-to-many dengan Faktur dimana faktur yang belum dilunasi oleh pelanggan bisa diakses melalui Pelanggan. Oleh sebab itu, saya membuat class Pelanggan yang isinya seperti berikut ini:

<?php

class Pelanggan extends Eloquent {

    public function fakturBelumLunas() {
        return $this->hasMany('Faktur');
    }

    public function sisaPiutang() {
        $sisa = 0;
        foreach($this->fakturBelumLunas as $faktur) {
            $sisa += $faktur->total;
        }
        return $sisa;
    }

}

?>

Berikut ini adalah contoh test case yang valid untuk melakukan pengujian yang harus terhubung ke database:

<?php

use CarbonCarbon;

class PelangganTest extends TestCase {

    public function testSisaPiutang() {
        $pelanggan = new Pelanggan();
        $pelanggan->nama = 'Test';
        $pelanggan->save();

        $faktur1 = new Faktur();
        $faktur1->nomor = 'FA-01';
        $faktur1->tanggal = Carbon::createFromDate(2014,05,01);
        $faktur1->total = 100000;
        $pelanggan->fakturBelumLunas()->save($faktur1);

        $faktur2 = new Faktur();
        $faktur2->nomor = 'FA-02';
        $faktur2->tanggal = Carbon::createFromDate(2014,05,02);      
        $faktur2->total = 200000;
        $pelanggan->fakturBelumLunas()->save($faktur2);

        $this->assertEquals(300000, $pelanggan->sisaPiutang());       

    }

}

?>

Masalah timbul karena fakturBelumLunas() mengembalikan Illuminate\Database\Eloquent\Relations\HasMany yang mewakili sebuah query. Domain class sudah tercemari oleh database! Itu adalah resiko memakai data mapper yang menerapkan active record pattern seperti Eloquent, Grails, Active Record (Ruby on Rails), dan sejenisnya. Pola active record, walaupun mudah dipakai, secara tidak langsung sudah mencemari domain class dengan method infrastruktur yang bukan bagian dari business logic (termasuk save(), delete(), dan sebagainya). Saya lebih senang memakai pendekatan seperti yang saya lakukan pada simple-jpa dimana saya menambahkan ‘method ajaib’ hanya pada repository bukan pada domain class sehingga domain class benar-benar ‘polos’.

Untuk membuktikan bahwa pengujian ini harus terhubung ke database, saya akan menjalankan test case setelah mematikan database. Saya akan menjumpai kesalahan seperti yang terlihat pada gambar berikut ini:

Pengujian gagal bila tidak terkoneksi ke database

Pengujian gagal bila tidak terkoneksi ke database

Salah satu masalah dalam pengujian yang saya lakukan adalah isi tabel di database akan selalu bertambah akibat pemanggilan save(). Isi database bisa tercemar. Untuk mengatasi hal tersebut, saya perlu memanggil perintah Artisan migrate:refresh. Agar lebih otomatis, saya dapat memanggil perintah Artisan melalui Artisan::call(). Pada PHPUnit, saya dapat meletakkan kode program yang akan selalu dikerjakan sebelum test dimulai pada method setUp() sehingga kode program saya terlihat seperti:

<?php

use CarbonCarbon;

class PelangganTest extends TestCase {

    public function setUp() {
        parent::setUp();
        Artisan::call('migrate:refresh');
    }

    public function testSisaPiutang() {                 
        $pelanggan = new Pelanggan();
        $pelanggan->nama = 'Test';
        $pelanggan->save();

        $faktur1 = new Faktur();
        $faktur1->nomor = 'FA-01';
        $faktur1->tanggal = Carbon::createFromDate(2014,05,01);
        $faktur1->total = 100000;
        $pelanggan->fakturBelumLunas()->save($faktur1);

        $faktur2 = new Faktur();
        $faktur2->nomor = 'FA-02';
        $faktur2->tanggal = Carbon::createFromDate(2014,05,02);
        $faktur2->total = 200000;
        $pelanggan->fakturBelumLunas()->save($faktur2);

        $this->assertEquals(300000, $pelanggan->sisaPiutang());       
    }


}

?>

Sekarang, setiap kali menjalankan pengujian, isi database akan selalu dikosongkan terlebih dahulu sehingga pengujian akan dilakukan pada kondisi database yang konsisten.

Dalam banyak kasus, test case sebaiknya tidak menyentuh database production secara langsung, melainkan database lokal atau database in-memory. Salah satu alasan untuk memakai database yang sama di lokal (misalnya sama-sama MySQL) adalah bisa dipastikan bahwa environment-nya sama persis. Dengan demikian, tidak mungkin ada masalah SQL yang jalan di lokal tapi tidak jalan di production. Kelemahannya adalah penggunaan database seperti MySQL membuat pengujian menjadi lambat. Padahal pengujian adalah sesuatu yang sangat sering dilakukan. Untuk mengatasi hal tersebut, saya dapat menggunakan database in-memory. Sebagai contoh, database SQLite dapat dipakai sebagai database in-memory.

Untuk memakai database SQLite sebagai database in-memory hanya pada pengujian, saya perlu men-copy file database.php pada lokasi app/config/testing seperti yang terlihat pada gambar berikut ini:

Konfigurasi untuk enviroment testing

Konfigurasi untuk enviroment testing

Sama seperti di Griffon dan framework populer lainnya, Laravel juga memiliki konsep environment pada konfigurasinya. Masing-masing subdirektori pada direktori config akan mewakili sebuah environment. Laravel akan membaca file konfigurasi sesuai dengan environment yang sedang aktif. Salah satu environment bawaan Laravel yang hanya akan aktif pada saat pengujian adalah testing. Dengan demikian, saat melakukan koneksi database di pengujian, file konfigurasi yang dibaca bukan app/config/database.php melainkan app/config/testing/database.php. Saya akan mengubah isi file tersebut menjadi:

<?php

return array(

    'fetch' => PDO::FETCH_CLASS,

    'default' => 'sqlite',

    'connections' => array(

        'sqlite' => array(
            'driver'   => 'sqlite',
            'database' => ':memory:',
            'prefix'   => '',
        ),

    ),

);

?>

Selain itu, saya juga mengubah Artisan::call('migrate:refresh') menjadi Artisan::call('migrate') di PelangganTest.php. Saya tidak perlu menghapus tabel secara manual karena pada database in-memory, setelah selesai dikerjakan, semuanya akan hilang.

Sampai disini, bila saya menjalankan pengujian, maka database yang dipakai adalah database SQLite sebagai database in-memory. Tapi sebenarnya, untuk kasus saya, karena pada kode program di atas saya memakai dynamic properties dalam bentuk Illuminate\Database\Eloquent\Collection, saya bisa menguji tanpa harus terhubung ke database. Cara ini tidak bisa diterapkan bila saya memanggil method secara langsung seperti $this->fakturBelumLunas()->getResults() di kode program diuji. Sebagai contoh, saya bisa mengubah test case menjadi seperti berikut ini:

<?php

use IlluminateDatabaseEloquentCollection;

class PelangganTest extends TestCase {

    public function testSisaPiutang() {     

        $faktur1 = new Faktur();
        $faktur1->nomor = 'FA-01';     
        $faktur1->total = 100000;        

        $faktur2 = new Faktur();
        $faktur2->nomor = 'FA-02';         
        $faktur2->total = 200000;

        $fakturs = new Collection([$faktur1, $faktur2]);    
        $pelanggan = new Pelanggan();   
        $pelanggan->fakturBelumLunas = $fakturs;                         

        $this->assertEquals(300000, $pelanggan->sisaPiutang());               

    }

}

?>

Bila saya menjalankan pengujian di atas, saya akan memperoleh hasil sukses tanpa harus memakai database sama sekali seperti yang terlihat pada gambar berikut ini:

Pengujian sukses tanpa harus terhubung ke database

Pengujian sukses tanpa harus terhubung ke database

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: