Testing Laravel: Mendapatkan Passed Test
Bismillahirrahmanirrahim
Pada artikel terdahulu, kita sudah membahas dan memahami konsep penerapan Test-Driven Development, 5 tahap yang kita kerjakan :
- Buat Kode Pengujian (Add Test).
- Jalankan test, hasilnya pasti fail (Watch Test Fail).
- Tulis Kode Fitur Sistem (Write Code).
- Jalankan test (Run Tests) hingga testing passed.
- Refactor Kode Fitur Sistem (Refactor).
Empat Langkah Perulangan
Berdasarkan konsep itu, pada artikel sebelumnya kita mengerjakan tahap pertama, yaitu membuat Kode Pengujian (Failing Test). Kali ini kita akan mengerjakan tahap ke 2 dan ke 3, dengan melakukan 4 langkah berikut secara berulang-ulang :
- Jalankan
vendor/bin/phpunit
- Error tampil
- Temukan Penyebab
- Tentukan Solusi
Keempat langkah ini dilakukan setiap kita menemukan error baru yang ditampilkan oleh PHPUnit. Setiap ekseksusi PHPUnit pada artikel ini kita berikan hashtag (#) angka. Saya menghitung ada 19 proses yang dilakukan dari awal sampai kita mendapatkan “Hijau”
.
Oke, kita mulai
# 1
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks] failed. Received status code [404].
Pada artikel sebelumnya kita sampai di sini.
Penyebab
Belum ada route untuk melayani request ke URL /tasks
.
Solusi
Buat route untuk melayani request ke /tasks
pada file routes/web.php
. Tambahkan route get
ke url tasks
dengan controller
dan method
yang melayani requestnya.
<?php
// routes/web.php
Route::get('/', function () {
return view('welcome');
});
// Tambahkan route dan actionnya (controller dan method)
Route::get('tasks', 'TasksController@index');
# 2
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks] failed. Received status code [500].
Caused by
ReflectionException: Class App\Http\Controllers\TasksController does not exist
Penyebab
TasksController
belum ada.
Solusi
$ php artisan make:controller TasksController
# 3
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks] failed. Received status code [500].
Caused by
BadMethodCallException: Method [index] does not exist.
Penyebab
Pada TasksController
belum ada method index
.
Solusi
Tambahkan method index
pada app/Http/Controllers/TasksController.php
.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TasksController extends Controller
{
public function index()
{
}
}
# 4
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Could not find a form that has submit button [Create Task].
Penyebab
Ini karena pada TasksController
method index
, kita tidak return view
, sehingga hanya tampil halaman web kosong.
Solusi
Kita return view
pada method index
.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TasksController extends Controller
{
public function index()
{
return view('tasks.index');
}
}
# 5
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: View [tasks.index] not found.
Penyebab
File index.blade.php
tidak ditemukan pada direktori resources/views/tasks
.
Solusi
Kita buat file index.blade.php
pada direktori resources/views/tasks
.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Tasks Management</title>
</head>
<body>
</body>
</html>
# 6
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Could not find a form that has submit button [Create Task].
Penyebab
Pada file resources/views/tasks/index.blade.php
atau disebut juga view tasks.index
tidak ditemukan Tombol Submit Create Task
.
Solusi
Kita buat file form
dengan tombol submit Create Task
pada view tasks.index
.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Tasks Management</title>
</head>
<body>
<form action="{% raw" >}}{{ url('tasks') }}{% endraw" >}}" method="post">
<input type="submit" value="Create Task">
</form>
</body>
</html>
# 7
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Unreachable field "name"
Penyebab
Pada view tasks.index
tidak ditemukan input text field dengan nama name
.
Solusi
Dalam form pada view tasks.index
tambahkan input text dengan nama name
.
<!-- body -->
<form action="{% raw" >}}{{ url('tasks') }}{% endraw" >}}" method="post">
<input type="text" name="name">
<input type="submit" value="Create Task">
</form>
<!-- /body -->
# 8
$ vendor/bin/phpunit
Hasil: Error
InvalidArgumentException: Unreachable field "description"
Penyebab
Pada view tasks.index
tidak ditemukan input textarea field dengan nama description
.
Solusi
Pada view tasks.index
kita buat input textarea dengan nama description
.
<!-- body -->
<form action="{% raw" >}}{{ url('tasks') }}{% endraw" >}}" method="post">
<input type="text" name="name">
<textarea name="description"></textarea>
<input type="submit" value="Create Task">
</form>
<!-- /body -->
# 9
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks] failed. Received status code [405].
Caused by
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
Penyebab
Pada route sudah ada route tasks
yang kita buat, tetapi ini untuk method get
, sedangkan saat kita submit form, kita mengirimkan method POST
artinnya kita perlu route juga untuk melayani method POST
ini.
Solusi
Pada routes/web.php
, buat route untuk melayani method POST
.
<?php
// routes/web.php
Route::get('/', function () {
return view('welcome');
});
// Tambahkan route dan actionnya (controller dan method)
Route::get('tasks', 'TasksController@index');
Route::post('tasks', 'TasksController@store');
# 10
$ vendor/bin/phpunit
Hasil: Error
A request to [http://localhost/tasks] failed. Received status code [500].
Caused by
BadMethodCallException: Method [store] does not exist.
Penyebab
Pada TasksController
belum ada method store
.
Solusi
Kita buat methodnya, buka app/Http/Controllers/TasksController.php
. Tambahkan method store
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TasksController extends Controller
{
public function index()
{
return view('tasks.index');
}
public function store()
{
}
}
# 11
$ vendor/bin/phpunit
Hasil: Error
PDOException: SQLSTATE[HY000]: General error: 1 no such table: tasks
Penyebab
Belum ada tabel tasks
di database.
Solusi
Nah sekarang kita berurusan dengan database, kita buat model Task
dengan file migrations
untuk tabel tasks
.
# Membuat model `Task` beserta file database migrationnya
$ php artisan make:model Task -m
# 12
$ vendor/bin/phpunit
Hasil: Error
PDOException: SQLSTATE[HY000]: General error: 1 no such table: tasks
Penyebab
Kok Errornya masih sama? Yap, PHPUnit belum tahu file migration
yang barusan dibuat bersama model Task
itu harus diapakan.
Solusi
Solusinya kita gunakan Trait RefreshDatabase
pada kelas tests\Feature\ManageTasksTest.php
agar setiap PHPUnit jalan, dia melakukan database migration.
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ManageTasksTest extends TestCase
{
use RefreshDatabase; // Tambahkan baris ini
/** @test */
public function user_can_create_a_task()
{
// ...
}
}
# 13
$ vendor/bin/phpunit
Hasil: Error
Unable to find row in database table [tasks] that matched attributes [{"name":"My First Task", ...
Penyebab
Belum ada action yang dilakukan pada method store
untuk mengisi database dengan data yang telah diinput melalui form.
Solusi
Kita kerjakan kode pembuatan record task yang diproses dari form pada method store
.
<?php
namespace App\Http\Controllers;
use App\Task; // Tambahkan ini
use Illuminate\Http\Request;
class TasksController extends Controller
{
// ... method index
public function store()
{
Task::create(request()->only('name', 'description'));
}
}
Jangan lupa
use App\Task;
di atas class.
# 14
$ vendor/bin/phpunit
Hasil: Error
Illuminate\Database\Eloquent\MassAssignmentException: name
Penyebab
MassAssignmentException
Solusi
Kita tambahkan protected property $fillable
pada model Task
untuk kolom name
dan description
.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = ['name', 'description'];
}
# 15
$ vendor/bin/phpunit
Hasil: Error
PDOException: SQLSTATE[HY000]: General error: 1 table tasks has no column named name
Penyebab
Tidak ditemukan kolom name
pada tabel task
di database.
Solusi
Edit file xxxxx_create_tasks_table
kemudian tambahkan attribute name
, description
, dan is_done
;
<?php
class CreateTasksTable extends Migration
{
// ...
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description');
$table->boolean('is_done')->default(0);
$table->timestamps();
});
}
# 16
$ vendor/bin/phpunit
Hasil: Error
1) Tests\Feature\ManageTasksTest::user_can_create_a_task
InvalidArgumentException: The current node list is empty.
...
...
/home/nafies/www/lv/riset/laravel-tdd/tests/Feature/ManageTasksTest.php:35
ERRORS!
Tests: 2, Assertions: 7, Errors: 1.
Penyebab
The current node list is empty
, artinya PHPUnit tidak menemukan apa-apa di halaman webnya, hanya berupa halaman kosong. Ini karena kita belum memberikan return apa-apa pada method store
nya.
Solusi
Kita berikan kode return back();
pada method store, agar Laravel mengembalikan kita ke halaman sebelumnya (index tasks).
<?php
// ... TasksController
public function store()
{
Task::create(request()->only('name', 'description'));
return back();
}
# 17
$ vendor/bin/phpunit
Hasil: Error
Failed asserting that the page contains the HTML [My First Task].
Penyebab
Pada halaman view tasks.index
kita belum menampilkan daftar Task yang ada di dalam tabel tasks
di database.
Solusi
Kita edit TasksController
agar mengambil record task dari database, kemudian kita tampilkan di view tasks.index
.
<?php
namespace App\Http\Controllers;
use App\Task; // Tambahkan ini
use Illuminate\Http\Request;
class TasksController extends Controller
{
public function index()
{
$tasks = Task::all();
return view('tasks.index', compact('tasks'));
}
// ... method store
}
Jangan lupa
use App\Task;
di atas class.
Kemudian pada view tasks.index
:
<!-- body -->
<form action="{% raw" >}}{{ url('tasks') }}{% endraw" >}}" method="post">
<input type="text" name="name">
<textarea name="description"></textarea>
<input type="submit" value="Create Task">
</form>
<h1>Tasks Management</h1>
<ul>
@foreach ($tasks as $task)
<li>
{% raw" >}}{{ $task->name }}{% endraw" >}} <br>
{% raw" >}}{{ $task->description }}{% endraw" >}}
</li>
@endforeach
</ul>
<!-- /body -->
# 18
$ vendor/bin/phpunit
Hasil: Passed
{:style=“width:100%”}
Yeeyyy… akhirnya setelah perjalanan panjang, kita dapat “Hijau”
:)
Ini artinya testing kita pada ManageTasksTest
method user_can_create_a_task()
sudah berhasil.
Mencoba Fitur Input Task pada Browser
Asumsinya, kita belum mencoba untuk membuka aplikasi ini dengan browser. Sekarang kita coba aplikasinya melalui browser, kira-kira seperti apa bentuknya?
$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>
Buka URL di browser : http://127.0.0.1:8000/tasks
.
Kok error, ya? Iya, karena kita belum melakukan konfigurasi database di sisi aplikasi, kita baru setting database di . Silakan teman-teman buat database pada MySQL atau SQLite, kemudian konfigurasikan databasenya pada file .env
. Di PC saya konfigurasinya menggunakan SQLite :
...
APP_LOG_LEVEL=debug
APP_URL=http://localhost
DB_CONNECTION=sqlite
BROADCAST_DRIVER=log
CACHE_DRIVER=file
...
Matikan development server (artisan serve) tadi. Kemudian migrasikan database :
$ php artisan migrate
Migrating: 2017_10_14_051259_create_tasks_table
Migrated: 2017_10_14_051259_create_tasks_table
Kemudian
$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>
Hasil di browser :
Hehe.. masih jelek ya, tidak apa-apa sekarang silakan coba input data task nya. Tapi ternyata belum bisa …
Masalahnya adalah hidden field CSRF Token belum ada pada form. Sekarang kita tambahkan {% raw" >}}{{ csrf_field() }}{% endraw" >}}
persis di bawah tag form
:
<!-- body -->
<form action="{% raw" >}}{{ url('tasks') }}{% endraw" >}}" method="post">
{% raw" >}}{{ csrf_field() }}{% endraw" >}}
<input type="text" name="name">
<textarea name="description"></textarea>
<input type="submit" value="Create Task">
</form>
<!-- /body -->
Jalankan lagi phpunit.
# 19
$ vendor/bin/phpunit
{:style=“width:100%”}
Sip, no problem, kita masih dapat “Hijau”
. Kita coba input Task dan Deskripsi lagi, seharusnya sudah bisa berhasil.
Oke berhasil input task. Sampai di sini proses TDD untuk pembangunan sub-fitur Input Task sudah selesai. Silakan teman-teman berkreasi untuk menambakan validasi form, atau mempercantik tampilan pada browser.
Kesimpulan
- Dalam penerapan TDD, testing memandu kita membangun fitur (atau sub-fitur) dengan cara memperbaiki error yang tampil.
- Empat langkah berulang dalam TDD Laravel :
- Jalankan
vendor/bin/phpunit
- Error tampil
- Temukan Penyebab
- Tentukan Solusi
- Jalankan
- Jika setiap perubahan yang kita lakukan di kode aplikasi membuat error yang berbeda pada PHPUnit, berarti progress kita ada kemajuan.
- Proses TDD hanya berfokus untuk pembangunan fitur sistem, tidak terpengaruh bagaimana tampilan yang muncul pada browser.
- Selama perubahan yang dilakukan tidak “merusak” fitur pada kode aplikasi, testing akan tetap menghasilkan
“Hijau”
. Salah satunya telah kita buktikan saat kita menambahkancsrf_field()
pada form tadi.
Pada artikel berikutnya insyaallah kita akan mempercantik tampilan view tasks.index
, agar ketika melanjutkan proses TDD pada fitur berikutnya, kita sudah memiliki tampilan yang bagus.
Jika teman-teman ada yang mencoba praktek dan menemui kendala atau stuck di salah satu langkah, atau mendapatkan pesan error yang berbeda dengan proses di atas, silakan tuliskan di kolom komentar, kita bahas sama-sama.
Terima kasih atas waktunya.