Test dalam Software Development

Norman Bintang
LEARNFAZZ
Published in
4 min readFeb 27, 2019

Test merupakan hal yang penting dalam software development. Tetapi kenyataannya, banyak developer yang malas membuat test. Pembuatan test dianggap hanya membuang-buang waktu karena tidak menambahkan fungsionalitas dari software yang dikembangkan.

Test sebagai investasi. Sumber: https://www.pexels.com/royalty-free-images/

Walaupun berat, sebenarnya pembuatan test dapat dianggap sebagai sebuah investasi jangka panjang yang sangat menguntungkan. Seiring dengan perkembangan codebase, kemungkinan kemunculan bug juga bertambah. Dan apabila memang benar terdapat bug, developer dapat kesulitan dalam menemukannya karena tidak mengetahui bagian mana yang sudah pasti benar. Selain itu, test juga dapat membantu dalam refactoring. Dengan adanya test yang telah dibuat sebelumnya, developer dapat memastikan bahwa behavior dari bagian yang di-refactor tidak berubah. Oleh karena itu, pembuatan test merupakan bagian penting dalam pengembangan sebuah software.

Karena pentingnya test, terdapat sebuah software development process yang bernama Test Driven Development (TDD). Artinya, pengembangan dilakukan dengan membuat test terlebih dahulu. Pengembangan dengan TDD dilakukan dalam siklus yang terdiri dari tiga tahapan, yaitu Red, Green, Refactor.

Red adalah tahapan pembuatan test. Karena belum ada implementasi yang dibuat, test yang dijalankan akan gagal, yang memang identik dengan warna merah.

Green adalah tahapan untuk membuat implementasi agar test yang telah dibuat pada tahap selanjutnya dapat berjalan sukses.

Refactor merupakan lanjutan dari tahapan Green. Pada tahap ini, test telah berjalan sukses tetapi masih terdapat ruang untuk meningkatkan kualitas code. Peningkatan yang dapat dilakukan seperti apakah algoritma yang digunakan sudah cukup cepat, apakah test yang dibuat sudah cukup, dan sebagainya.

Setelah melalui Refactor, code yang telah dibuat bisa di-release. Jika ingin mengembangkan fitur baru, pengembangan dimulai dari tahap Red lagi, lalu diikuti Green dan Refactor. Diharapkan dengan melakukan test di awal, tidak ada code yang tidak ditest sehingga dapat meminimalkan bug yang mungkin dibuat

Manfaat yang dapat dirasakan jika menggunakan TDD:

  • Kode lebih modular. Dengan membuat test terlebih dahulu, kita dituntut untuk berpikir apa saja yang akan dibuat. Ini membuat kita berpikir bagaimana cara menulis kode yang modular.
  • Mendeteksi kesalahan lebih cepat. Adanya test membuat kesalahan bisa dideteksi. Dengan TDD, kesalahan akan dideteksi lebih cepat. Diharapkan kode yang bermasalah akan terdeteksi sebelum sampai di production.
  • Mendeteksi apakah perubahan kode yang baru dilakukan merusak fungsionalitas kode sebelumnya. Test membuat kita dapat memastikan apakah kode yang dibuat sesuai dengan tujuannya.
  • Test sebagai dokumentasi. Test dapat dilihat sebagai dokumentasi, karena menjelaskan apa expected output dari input yang diberikan.

Penerapan TDD dalam backend LEARNFAZZ

Pada backend kami yang menggunakan bahasa go, kami membuat test dengan struktur Table Driven Test. Table Driven Test memungkinkan kita melihat test case lebih jelas dan mengurangi repetisi.

Berikut adalah contoh kode penerapan Table Driven Test.

package persistenceimport (
"errors"
"reflect"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2019/PPLB4/back-end/domain/entity"
"gitlab.cs.ui.ac.id/ppl-fasilkom-ui/2019/PPLB4/back-end/infrastructure/database"
)
func TestCategoryRepositoryImpl_List(t *testing.T) {
mock := database.NewMockDatabase()
type args struct {
limit int
offset int
}
tests := []struct {
name string
r *CategoryRepositoryImpl
args args
want []*entity.Category
want1 int
wantErr bool
mock func()
}{
{
name: "query categories failed",
args: args{
limit: 5,
offset: 0,
},
want: nil,
want1: 0,
wantErr: true,
mock: func() {
mock.ExpectQuery("SELECT (.+) FROM categories (.+)").WillReturnError(errors.New("test"))
},
},
{
name: "query count failed",
args: args{
limit: 5,
offset: 0,
},
want: nil,
want1: 0,
wantErr: true,
mock: func() {
rows := sqlmock.NewRows([]string{
"id",
"name",
"photo_url",
})
rows.AddRow(1, "name", "https://image.com")
mock.ExpectQuery("SELECT (.+) FROM categories (.+)").WillReturnRows(rows)
mock.ExpectQuery("SELECT COUNT(.+)").WillReturnError(errors.New("test"))
},
},
{
name: "success",
args: args{
limit: 5,
offset: 0,
},
want: []*entity.Category{
{
ID: 1,
Name: "Category 1",
PhotoURL: "https://image1.com",
},
},
want1: 1,
wantErr: false,
mock: func() {
rows := sqlmock.NewRows([]string{
"id",
"name",
"photo_url",
})
rows.AddRow(1, "Category 1", "https://image1.com")
mock.ExpectQuery("SELECT (.+) FROM categories (.+)").WillReturnRows(rows)
rows = sqlmock.NewRows([]string{
"count",
})
rows.AddRow(1)
mock.ExpectQuery("SELECT COUNT(.+)").WillReturnRows(rows)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock()
r := &CategoryRepositoryImpl{}
got, got1, err := r.List(tt.args.limit, tt.args.offset)
if (err != nil) != tt.wantErr {
t.Errorf("CategoryRepositoryImpl.List() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CategoryRepositoryImpl.List() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("CategoryRepositoryImpl.List() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

Test Case didefinisikan oleh variable tests, yang berupa array of struct yang memiliki atribut name sebagai judul dari test case, r sebagai object yang akan ditest, args sebagai input dari fungsi yang akan ditest, want sebagai objek yang diharapkan akan dikembalikan, want1 sebagai objek lain yang diharapkan akan dikembalikan, wantErr sebagai penanda apakah kita mengharapkan error atau tidak, dan mock sebagai fungsi yang berisi perintah-perintah apa saja yang akan dimock.

Kode test di atas memiliki 3 test case, yaitu case success, case query count failed, dan case query categories failed. Jumlah case yang harus dibuat disesuaikan dengan seberapa banyak percabangan pada kode yang ingin ditest agar coverage bisa 100%.

Dalam pembuatan test berbentuk Table Driven, kami menggunakan gotests https://github.com/cweill/gotests yang menyiapkan template seperti kode di atas, dengan bagian test cases masih belum didefinisikan. Programmer lah yang memiliki tugas untuk mengisi variable tests karena hanya programmer yang mengerti tujuan dari test tersebut, bukan komputer.

Referensi:

Red, Green, Refactor. Web. 27 Februari 2019. https://www.codecademy.com/articles/tdd-red-green-refactor

--

--