Garasi Techblog

Articles on Garasi.id’s engineering, culture, and technology. Written and curated by the very people who build Garasi.id. The views expressed are those of the authors and don’t necessarily reflect those of Garasi.id.

Mempercepat Unit Test Pada Laravel

--

Photo by RKTKN on Unsplash

Seperti yang sudah kita tau, selain menulis kode program untuk solve suatu problem salah satu tahap dari pembuatan aplikasi adalah menulis kode program test untuk mengetest kode program itu sendiri, apakah sudah sesuai dengan yang diharapkan atau belum.

Selain itu, program test yang sudah dibuat sebelumnya bisa membantu kita ketika ingin menambahkan suatu fungsionalitas atau melakukan refactor pada code base itu sendiri. Semakin baik testnya maka rasa cemas kita terhadap kode itu akan mengalami sesuatu ketika sampai di production akan berkurang karena sudah ke ter-cover dengan test yang sudah ada sebelumnya.

Di Garasi.id sendiri, Saya bersama tim merasakan salah satu manfaat tersebut ketika kami melakukan refactoring pada Toko Garasi. Kami hanya perlu merubah beberapa flow dari test case tersebut, kemudian test kembali menjadi hijau setelah kami jalankan kembali atau menjadi merah kalau ada logic yang salah ketika proses refactor kode yang bersangkutan.

Tapi, semakin besar aplikasinya maka akan semakin banyak test case yang harus dijalankan ketika proses build. Yang mana sejalan dengan semakin bertambahnya waktu yang diperlukan untuk menjalankan seluruh rangkaian test dari awal sampai akhir (bergantung kepada CPU komputer dimana test tersebut dijalankan). Terutama pada aplikasi monolithic.

Source bti360

Karena demikian, setidaknya kita perlu mempercepat proses unit test semaksimal mungkin dikarenakan hal ini sangat berdampak langsung ke produktivitas kita sebagai engineer dalam mengembangkan aplikasi.

Untuk mempercepat jalannya unit test ada beberapa cara yang dapat kita lakukan diantaranya sebagai berikut:

1. Parallel Testing

Setiap bahasa pemrograman setidaknya memiliki testing frameworknya tersendiri, dalam hal ini salah satu testing framework untuk bahasa pemrograman PHP yang umum dan banyak digunakan adalah PHPUnit. Pada dasarnya PHPUnit sudah termasuk dalam package bawaan pada Laravel, jadi tinggal jalankan PHPUnit dengan command di bawah ini untuk menjalankan unit test dari project kita masing-masing.

$ vendor/bin/phpunit

dan outputnya seperti di bawah ini.

Test menggunakan PHPUnit

Berdasarkan hasil PHPUnit di atas bisa kita lihat total test cases yang dijalankan sebanyak 79 test cases dengan waktu yang diperlukan sebanyak 3.15 detik dan konsumsi memori sebesar 48 MB dari awal sampai selesai. Perlu diingat PHPUnit mengeksekusi test case satu persatu dengan menggunakan 1 CPU. Hal ini bisa kita lihat dari usage CPU ketika kita menjalankan PHPUnit.

Penggunaan CPU pada PHPUnit

Dari gambar diatas, hanya CPU ke-4 yang usagenya sampai 100% sedangkan CPU lainnya bisa kita katakan idle atau menjalankan proses lain pada komputer kita.

Alih-alih hanya menggunakan satu CPU untuk menjalankan serangkaian test yang telah dibuat, kita dapat memanfaatkan jumlah seluruh CPU yang ada dan menjalankan serangkaian test cases tersebut secara paralel. Untuk mencapai hal itu kita bisa memanfaatkan Paratest.

Paratest merupakan library PHP yang digunakan untuk menjalankan test secara paralel pada PHPUnit. Untuk instalasinya kita hanya perlu menginstall melalui composer tanpa perlu melakukan konfigurasi apapun terhadap konfigurasi phpunit kita. Untuk instalasinya, kita hanya perlu menjalankan command di bawah ini pada project laravel kita.

$ composer require --dev brianium/paratest

Kemudian untuk menjalankan test dengan menggunakan Paratest kita hanya perlu menjalankan command di bawah ini.

$ vendor/bin/paratest

dan outputnya seperti di bawah ini.

Test menggunakan Paratest

Berdasarkan hasil Paratest di atas bisa kita lihat untuk menjalankan test cases yang sama sebelumnya dari awal hingga akhir, waktu yang diperlukan adalah 2.37 detik, konsumsi memori sebesar 8 MB dan proses testnya sendiri menggunakan 4 CPU. Hal ini bisa kita lihat dari usage CPU ketika menjalankan serangkaian test menggunakan Paratest seperti di bawah ini.

Penggunaan CPU pada Paratest

Secara default Paratest akan menjalankan rangkaian test dengan menggunakan total maksimum dari jumlah CPU. Dalam kasus ini Saya menggunakan mesin dengan total CPU sebanyak 4 buah, maka secara otomatis Paratest akan menjalankan test dengan 4 CPU.

Jumlah CPU yang digunakan dapat kita atur sendiri dengan menggunakan parameter -p ketika kita ingin mengeksekusi rangkaian test. Sehingga commandnya menjadi sebagai berikut.

$ vendor/bin/paratest -p X

dimana X merupakan jumlah CPU yang kita inginkan.

Kemudian kita dapat memanfaatkan runner WrapperRunner yang telah disediakan oleh Paratest untuk membuat waktu eksekusi unit test kita menjadi lebih cepat.

Pada dasarnya default runner untuk PHPUnit membuat proses baru untuk setiap testcase-nya yang mana setiap proses barunya akan melakukan bootstrapping. Hal ini akan membuat proses test menjadi lambat khususnya ketika melakukan bootstrapping untuk database setup atau sejenisnya.

Hal ini berbeda dengan apa yang dilakukan oleh runner WrapperRunner yang melakukan bootstrapping terlebih dahulu kemudian dapat digunakan kembali untuk proses test berikutnya tanpa perlu melakukan bootstrapping ulang.

Untuk menggunakan runner kita dapat menambahkan parameter --runner kemudian nilainya dapat kita isi dengan WrapperRunner.

$ vendor/bin/paratest --runner WrapperRunner
Test menggunakan Paratest dengan runner WrapperRunner

Pada akhirnya kita bisa mempercepat proses testing dari awal sampai akhir yang mana awalnya 3.15 detik dengan konsumsi memori sebesar 48 MB menjadi 1.71 detik dengan konsumsi memori sebesar 8 MB dengan memanfaatkan Paratest dan menggunakan runner WrapperRunner.

2. In-memory Database

Memanfaatkan in-memory database merupakan salah satu pendekatan yang dapat meningkatkan performa unit test yang cukup signifikan tanpa melakukan banyak konfigurasi. Alih-alih menyimpan record pada database yang sesungguhnya (MySQL / PostgreSQL atau lainnya) yang menggunakan disk drive sebagai media penyimpanannya. Kita dapat memanfaatkan in-memory database seperti sqlite3 atau lainnya yang mana memanfaatkan memori sebagai media penyimpanannya.

Seperti yang sudah disebutkan sebelumnya, konfigurasinya sangatlah mudah, kita hanya perlu menginstall sqlite3 pada mesin yang kita gunakan. Sebagai contoh apabila menggunakan Ubuntu kita hanya perlu menjalankan command sebagai berikut ikut melakukan instalasinya:

$ sudo apt install sqlite3

kemudian kita tinggal melakukan un-comment 2 baris konfigurasi env phpunit kita yang terdapat di file phpunit.xml sehingga secara default akan menggunakan sqlite sebagai database setiap kali kita menjalankan unit test

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>

Berikut adalah hasil ketika kita menjalankan unit test tanpa menggunakan in-memory database. Dalam hal ini menggunakan MySQL sebagai databasenya.

MySQL sebagai test database

Berdasarkan hasil diatas, untuk menjalankan 79 test case memakan waktu sebanyak 5 menit 72 detik. Dan ketika kita telah menggunakan in-memory database hasilnya sebagai berikut:

SQLite sebagai test database

Pada akhirnya ketika kita menggunakan in-memory database sebagai database testing waktu yang dibutuhkan untuk test case serupa turun drastis menjadi 2.96 detik.

Perlu diingat bahwa pendekatan seperti ini hanya dapat digunakan ketika kita menggunakan SQL Database sebagai database untuk aplikasi kita. Kondisinya akan berbeda apabila kita menggunakan No-SQL sebagai database untuk aplikasi kita. Karena SQL dan No-SQL merupakan dua hal yang berbeda dan akan membuat seluruh test case kita error.

3. Mock Object

Melakukan mock object merupakan salah satu hal yang umum dilakukan pada saat membuat unit test. Selain mempermudah membuat skenario test untuk kondisi tertentu, melakukan mock juga mengurangi waktu eksekusi skrip test itu sendiri.

Ketika kita melakukan mock pada suatu object kita dapat mengontrol input dan output apa yang akan diterima dan dikeluarkan oleh object yang kita mock. Dalam hal ini sebenarnya object tersebut tidak tereksekusi secara nyata melainkan diambil alih oleh object tiruan yang kita definisikan sebelumnya.

Hal ini sangat membantu terutama ketika terdapat proses bisnis yang cukup kompleks di belakang layar, mengirim request ke 3rd party API, proses generate file cukup berat dan proses-proses lainnya yang cukup memakan waktu.

Laravel sendiri sudah menggunakan Mockery sebagai library default untuk mock framework-nya. Sebagai contoh kita akan menggunakan API lirik lagu dari http://lyrics.ovh/ yang nantinya akan kita mock pada unit test-nya.

Pertama kita akan membuat client file yang bernama LyricsHttpClient.php sebagai konfigurasi dasar client untuk terhubung dengan API tersebut.

LyricsHttpClient.php

Kemudian membuat file repository yang kita beri nama LyricsRepostiry.php dimana kita menangani request yang kita kirim, response yang kita terima dan log dari proses tersebut. File ini juga yang nantinya akan kita mock ketika proses testing.

LyricsRepository.php

Selanjutnya membuat file test untuk LyricsRepository.php yang telah kita buat sebelumnya dan kita beri nama LyricsRepositoryTest.php. Untuk saat ini kita biarkan tanpa melakukan mock pada object LyricsRepository.

LyricsRepositoryTest.php (Non mock)

Setelah skrip test dibuat, kita jalankan PHPUnit untuk mengeksekusi skrip test yang kita buat sebelumnya.

Test LyricsRepository tanpa Mock

Berdasarkan hasil di atas. waktu yang diperlukan untuk menjalankan skrip test yang kita buat sebelumnya adalah 1.91 detik. Kemudian kita kembali ke file LyricsRepositoryTest.php dan mengubah skrip test kita dengan melakukan mock pada LyricsRepository.

LyricsRepositoryTest.php (Mock)

Setelah melakukan perubahan pada LyricsRepositoryTest.php selanjutnya kita kembali eksekusi skrip test tersebut dengan PHPUnit dan didapat hasil sebagai berikut:

Test LyricRepository dengan mock

Berdasarkan hasil di atas, waktu yang diperlukan untuk mengeksekusi skrip LyricsRepositoryTest menjadi 124ms yang mana sebelumnya 1,91 detik. Hal ini dikarenakan kita melakukan mock pada LyricsRepository sehingga kita yang tidak mengirim request sungguhan ke API lyrics.ovh.

Kesimpulan

Berdasarkan 3 point diatas beberapa kesimpulannya sebagai berikut:

  • Sebisa mungkin jalankan unit test dengan paralel menggunakan Paratest dengan runner WrapperRunner untuk mempercepat proses testing.
  • Manfaatkan In-memory database sebagai database untuk testing jika memungkinkan
  • Mock object-object yang berhubungan dengan 3rd party atau prosesnya cukup berat sehingga dapat mempercepat proses eksekusi unit test dan pastikan object yang di-mock tersebut sudah ter-cover oleh test yang lainnya.

Demikian beberapa cara untuk mempercepat Unit Test pada Laravel. Semoga tulisan ini bisa memberi gambaran kepada yang membaca ataupun menjadi media pengingat hal-hal yang berkaitan dengan ini untuk pembaca yang telah memahami sebelumnya. Terimakasih !

--

--

Garasi Techblog
Garasi Techblog

Published in Garasi Techblog

Articles on Garasi.id’s engineering, culture, and technology. Written and curated by the very people who build Garasi.id. The views expressed are those of the authors and don’t necessarily reflect those of Garasi.id.

Azio Azravi
Azio Azravi

Written by Azio Azravi

Software Engineer at Ecommerce Company in Indonesia 🇮🇩

Responses (1)