Rabu, 03 Desember 2008

Unit Testing di PHP (Bagian 3 Test Exception)

Tulisan sebelumnya membahas mengenai cara membuat Unit Test pertama anda. Telah diterangkan mengenai struktur dasar dari class test dan juga melakukan pembuatan sebuah class secara bertahap. Masing-masing tahap ditujukan untuk melewati test yang telah dibuat. Sekarang kita akan melanjutkan untuk membuat unit test dengan PHPUnit yang melakukan test terhadap exception.

Kita akan melakukan percobaan terhadap method divide di class calculator kita. Pertama kita buat dulu method untuk testnya:

public function testDivide() {
$this->assertEquals(3, $this->calculator->divide(6, 2));
}

Jika kita jalankan test kita
> phpunit CalculatorTest

akan muncul error yang sangat panjang dan jika kita lihat lebih teliti akan terdapat kata-kata:
Fatal error: Call to undefined method Calculator::divide()
Tentunya ini terjadi karena kita belum membuat method divide di class calculator kita. Tambahkan bagian berikut di class Calculator:

public function divide($a, $b) {

}

Jika kita jalankan lagi, maka akan keluar:
Failed asserting that <null> matches expected value <integer:3>

Ah.. ternyata kita belum mengisi method divide kita. Kita isi method divide dengan potongan program berikut:

return $a/$b;

Jika kita coba lagi, maka test kita akan berhasil. Berikutnya, kita coba tambahkan dengan test yang lain, method testDivide kita menjadi seperti berikut:

public function testDivide() {
$this->assertEquals(3, $this->calculator->divide(6, 2));
$this->assertEquals(7, $this->calculator->divide(49, 7));
}
Jika kita jalankan test, ternyata hasilnya masih ok. Mari kita tambahkan untuk pembagian lainnya:

public function testDivide() {
$this->assertEquals(3, $this->calculator->divide(6, 2));
$this->assertEquals(7, $this->calculator->divide(49, 7));
$this->assertEquals(0, $this->calculator->divide(32, 0));
}

Ternyata terjadi error "Division by zero". Hal tersebut terjadi karena kita melakukan pembagian dengan angka 0. Bagaimana kita mengatasi error tersebut?

public function testDivide() {
$this->assertEquals(3, $this->calculator->divide(6, 2));
$this->assertEquals(7, $this->calculator->divide(49, 7));
try {
$this->calculator->divide(32, 0);
$this->fail('Expected exception does not occur');
} catch (Exception $e) {
$this->assertEquals('Division by zero', $e->getMessage());
}
}

Yang perlu diperhatikan dari program di atas adalah penggunaan try .. catch. Kita berusaha menangkap kesalahan yang terjadi di dalam program kita. Jika terjadi kesalahan, maka di dalam blok catch kita membandingkan apakah terdapat pesan 'Division by zero' dari kesalahan tersebut. Selain itu, setelah kita melakukan pemanggilan method divide dengan pembagi 0, terdapat pernyataan:

$this->fail('Expected exception does not occur');

Pernyataan tersebut menyebabkan test kita akan gagal jika TIDAK terjadi exception yang diharapkan. Dengan cara ini, kita memastikan bahwa exception yang kita inginkan harus terjadi di program calculator kita. Dan dengan try .. catch kita menghindari terjadinya error dari test yang dilakukan.

Minggu, 30 November 2008

Lakukan Test Terhadap Program Anda

Dapat dikatakan bahwa "melakukan test terhadap kode program" merupakan salah satu kebiasaan yang paling penting dalam melakukan pembuatan program. Dengan testing yang baik, kita akan mendapatkan keleluasaan dalam pembuatan program.

Kedengarannya seperti suatu kesimpulan yang aneh. Kemungkinan kita akan berfikir, bahwa testing merupakan halangan bagi keleluasaan pembuatan program. Tapi jika kita dapat melihat bahwa sebuah test yang baik terhadap method-method public di dalam sebuah class akan membuat kita bebas untuk merubah implementasi dari method public tersebut. Kita tidak perlu takut lagi bahwa perubahan yang dilakukan akan menyebabkan kita harus merubah atau lebih parah merusak keseluruhan program kita.

Selama kita dapat melewati Unit Test yang telah dibuat untuk method-method yang public, artinya method public kita memiliki parameter dan menghasilkan keluaran seperti yang diharapkan dalam test, kita dapat yakin bahwa keseluruhan program kita dapat tetap berjalan dengan lancar.

Memastikan bahwa keseluruhan program kita dapat berjalan dengan baik hanya merupakan tujuan pertama dari dilakukannya test. Beberapa hal lain yang kita bisa harapkan dengan adanya test secara menyeluruh terhadap program kita adalah:
  1. Testing memaksa kita membuat program yang dapat dengan mudah ditest. Ketika kita membuat sebuah method, kita akan selalu berfikir bagaimana kita akan melakukan test dengan menyeluruh terhadap method kita tersebut. Hal ini membawa kita untuk melakukan perubahan-perubahan di method dan tentunya keseluruhan program yang kita buat. Hasilnya adalah bagian-bagian program yang tidak saling terikat ('loose coupling'), fleksible dan lebih modular. Dengan begitu, perubahan terhadap program di kemudian hari dapat dilakukan dengan lebih mudah.
  2. Penulisan test mendorong kita untuk fokus dan mendefinisikan dengan jelas harapan kita terhadap bagaimana seharusnya program kita bekerja. Kita akan mendefinisikan dengan jelas, jika parameter A dimasukan, maka nilai B akan dihasilkan dan seterusnya. Setelah melihat harapan kita tentang keluaran dengan berbagai masukan, selanjutnya kita akan memikirkan mengenai kemungkinan-kemungkinan input yang akan diterima oleh program kita dan kemungkinan-kemungkinan keluarannya, yang mungkin berada di luar nilai sewajarnya. Dari situ kita akan harus mengambil keputusan mengenai bagaimana kita akan menanggulanginya. Hal ini mendorong kita untuk membuat program yang lebih fleksibel terhadap masukan dan menghindari kesalahan yang dapat menghentikan jalannya program secara keseluruhan.
  3. Dengan test case yang kita miliki, orang lain dapat melihat tujuan dibuatnya method yang kita test, berikut pula cara pemakaiannya. Dengan begitu, test dapat dijadikan sebagai dokumentasi yang jelas dari program yang kita bangun. Dan, pada point 1, karena kita akan cendrung membuat method kita mudah untuk ditest dan simple, maka tentunya akan memudahkan orang lain memahami mengenai keseluruhan program yang kita tulis.
Saya berharap dengan argumentasi di atas membuat penulisan Unit Test menjadi suatu hal yang menarik untuk dilakukan karena keuntungannya akan melebihi harapan awal anda. Selain itu, untuk mulai melakukan Unit testing di PHP bukanlah suatu hal yang sulit untuk dilakukan.

Selasa, 25 November 2008

Unit Testing di PHP (Bagian 2 Test Pertama)

Dalam tulisan sebelumnya, saya telah menjelaskan cara untuk melakukan instalasi PHPUnit. Berikutnya kita akan mulai membuat unit test menggunakan PHPUnit untuk aplikasi yang kita bangun. Sebelum kita mulai dengan test, mari kita buat dasar dari class yang akan kita test,

<?php

class Calculator {

public function add($a, $b) {
return 5;
}
}

Simpan ke dalam file dengan nama 'Calculator.php'. Kemudian, kita lanjutkan dengan membuat bentuk dasar dari test class kita. Sebuah test yang menggunakan PHPUnit memiliki bentuk dasar sebagai berikut:

<?php

require_once 'Calculator.php';
require_once 'PHPUnit/Framework.php';

class CalculatorTest extends PHPUnit_Framework_TestCase {

protected function setUp() {

}

protected function tearDown() {

}

public function testAdd() {

}
}

Tentunya kita mengikutsertakan file 'Calculator.php' karena kita akan membuat unit test untuk class tersebut. Anda dapat mengubah bagian tersebut sesuai dengan class yang ingin anda buatkan testnya.

Beberapa hal yang perlu dijelaskan adalah, test class harus melakukan extends terhadap PHPUnit_Framework_TestCase. Selain itu, masing-masing test method yang akan dijalankan, HARUS memiliki nama yang dimulai dengan kata 'test' dan bersifat public. Pada contoh di atas, method 'testAdd' merupakan salah satu method yang akan melakukan test. Tinggal dua method lagi yang belum dijelaskan, yaitu 'setUp' dan 'tearDown'.

Method dengan nama 'setUp' dan 'tearDown' merupakan method yang akan dijalankan setiap kali test method kita dijalankan. 'setUp' dijalankan sebelum masing-masing test method dan 'tearDown' dijalankan setelah masing-masing test method. Jadi, jika kita memiliki dua buah test method, misalnya 'testMethodSatu' dan 'testMethodDua', maka urutan dijalakannya method-method adalah:

  • setUp

  • testMethodSatu

  • tearDown

  • setUp

  • testMethodDua

  • tearDown


  • 'setUp' dapat digunakan untuk melakukan inisialisasi yang diperlukan sebelum sebuah test dijalankan, misalnya mempersiapkan data, membuat object yang dibutuhkan atau koneksi ke database. 'tearDown' dapat digunakan untuk melakukan pembersihan terhadap hasil proses test, misalnya menghapus file yang dibuat dalam test, menghapus data yang dimasukkan ke dalam database dan lain sebagainya. Dengan cara ini kita dapat memastikan method test selalu dijalankan dengan kondisi lingkungan yang tetap.

    Mari kita mulai membuat unit test kita. Saya hanya menampilkan potongan dari file 'CalculatorTest.php' di bawah ini:

    class CalculatorTest extends PHPUnit_Framework_TestCase {

    private $calculator;

    protected function setUp() {
    /* Kita membuat object calculator karena selalu dibutuhkan
    * dalam setiap test kita.
    */
    $this->calculator = new Calculator();
    }

    protected function tearDown() {

    }

    public function testAdd() {
    /* Pastikan hasil method add di calculator sesuai */
    $this->assertEquals(5, $this->calculator->add(2, 3));
    }
    }


    Di method setUp kita mempersiapkan sebuah object dari Calculator karena akan selalu dipergunakan dalam setiap test kita. Kemudian dalam method 'testAdd' kita melakukan pemanggilan terhadap $this->assertEquals . Method ini merupakan method yang disediakan oleh PHPUnit yang melakukan pengetasan apakah parameter pertama, sama dengan parameter kedua. Dalam contoh di atas, apakah 5 sama dengan hasil dari $this->calculator->add(2, 3). PHPUnit menyediakan beberapa fungsi 'assert' yang dapat digunakan untuk keperluan unit test yang berbeda.

    Saya mencoba untuk membuat test terlebih dahulu sebelum melakukan implementasi ke dalam class Calculator sendiri. Setelah test selesai dibuat, implementasi di dalam class dapat dibuat untuk melewati test yang ada. Pada saat ini jika kita menjalankan test, tentunya akan gagal. Coba lakukan perintah berikut:

    phpunit CalculatorTest

    Akan terjadi error karena kita belum memiliki method add di class Calculator. Mari kita lanjutkan dengan membuat method add. Kita membuat method hanya untuk melewati test yang telah dibuat.

    class Calculator {

    public function add($a, $b) {
    return 5;
    }
    }

    jika kita menjalankan phpunit kembali seperti perintah sebelumnya, test kita akan berhasil sekarang. Tetapi method add kita belum selesai. Tambahkan assertion lagi ke dalam unit test untuk memastikannya.

    public function testAdd() {
    /* Pastikan hasil method add di calculator sesuai */
    $this->assertEquals(5, $this->calculator->add(2, 3));

    /* Coba dengan nilai berbeda */
    $this->assertEquals(23, $this->calculator->add(15, 8));
    }

    Jika test dijalankan maka akan gagal kembali dengan pesan:

    'Failed asserting that <integer:5> matches expected value <integer:23>'
    /blog/CalculatorTest.php:20

    Hasil tersebut mengatakan bahwa, pada baris nomor 20 (di program saya nomor 20, lihat hasil dari test anda) terjadi perbedaan nilai, dimana yang diharapkan adalah 23 dan nilai hasil adalah 5. Hal tersebut terjadi karena method add kita selalu mengembalikan nilai 5. Mari kita ubah method tersebut:

    class Calculator {

    public function add($a, $b) {
    return $a + $b;
    }
    }

    Jalankan kembali test untuk melihat hasilnya.

    Coba tambahkan test dengan angka yang berbeda lagi, dan coba lihat hasilnya. Jika sudah berhasil, kita dapat membuat unit test dengan memasukkan nilai yang bukan angka sebagai parameter. Coba masukkan string, null ataupun array. Bagaimana hasilnya? Apakah terjadi error? Apakah hasilnya seperti yang kita inginkan?

    Ternyata membuat unit test menggunakan PHPUnit tidak sesulit yang saya bayangkan sebelumnya. Pada tulisan berikutnya saya akan menerangkan mengenai cara untuk melakukan unit test untuk memastikan bahwa sebuah error telah terjadi.