UI Testing

Should we just give the user chance to test our UI?

illustration by awesome folks from Icons8

Testing untuk sebuah User Interface terlihat menghabiskan waktu, mahal, dan sulit untuk diterapkan. Bayangkan ketika kita memiliki state untuk sebuah component Login Form seperti ini:

  • Tombol login harus ter-disable bila state shouldLoginButtonDisabled bernilai true
  • Jika kolom email belum memiliki nilai, maka state shouldLoginButtonDisabled tersebut bernilai true
  • Jika kolom password belum memiliki nilai, maka state shouldLoginButtonDisabled tersebut bernilai true
  • Jika email memiliki format yang tidak valid, maka state shouldLoginButtonDisabled tersebut menjadi true
  • Jika user menekan tombol Login, dan browser sedang mengirim POST request ke server, maka state shouldLoginButtonDisabled tersebut bernilai true
  • Jika server memberikan response selain 200, maka state shouldLoginButtonDisabled tersebut bernilai false
  • Jika request via Promise kita gagal, maka state shouldLoginButtonDisabled tersebut bernilai false

Ada 6 skenario diatas, yang biasanya akan kita testing secara manual menggunakan browser kesayangan kita sendiri, and that’s not a problem.

Lalu misalnya ada fitur baru. Untuk menghindari bruteforce, bot, dll kita harus memasang Captcha di form tersebut untuk sebuah IP yang telah melakukan kegagalan login selama lebih dari 3x.

Kita buatlah perubahannya, dan sekarang kita menambah skenario lagi, kan?

  • Login gagal 1x, muncul captcha ga?
  • Login gagal 2x, muncul captcha ga?
  • Login gagal 3x, muncul captcha ga nih?
  • Login gagal 4x, oke semoga captcha masih muncul

Semakin bertambah skenario yang ada, maka semakin banyak pula UAT yang harus diuji. Jika satu skenario menghabiskan total waktu 1 menit, 10 skenario akan menghabiskan waktu 10 menit. Belum ditambah latency dari faktor lain seperti menunggu response dari server.

This is so wasting time, I hate it. Itu masih diskop fungsionalitas, bagaimana bila sudah ke UI, seperti:

  • Loh kok ada overflow di box ini?
  • Ini notif salah password napa ga muncul dah?
  • KENAPA ADA TULISAN KUWA KUWI DI TOMBOL INI?

Disamping kita menyukai ke-rasa-penasaran-an kita, juga kita membenci suatu perubahan yang tidak terduga. Dengan state machine, kita bisa melakukan time-travel (munculkan box captcha meskipun belum gagal login beneran selama 3x) dan itu membuat kita senyum.

Juga ketika kita mencoba untuk me-refactor code yang ada, dan ternyata terdapat kesalahan, kita kesel kenapa terjadi perubahan. Tidak menutup kemungkinan juga bila ternyata yang seharusnya error, malah tidak terjadi error.

PROGRAMMING IS COOL

Visual Regression Testing

For the visual nerd.

Ini yang menurut gue lumayan efektif, tapi harus membayar biaya yang mahal (except you are an angel investor of technical debt). Kalau gak salah gue pernah membahas tentang testing di UI, tapi tidak terlalu menjurus tentang topik mana yang sedang gue bahas.

Oke lanjut, dengan Visual Regression Testing, setidaknya kita membutuhkan: Headless browser. Konfigurasinya. Dan berikut kodenya. Jika tidak ingin ribet, bisa menggunakan layanan pihak ketiga. shut up and take my money.

Dengan Visual Regression Testing, kita bisa langsung melihat perubahan yang terjadi; Perbedaannya dengan yang sebelumnya, dan letak perbedaannya ada dimana.

Jadi, bila melihat contoh gambar diatas, kita bisa berdalih oh cool, someone made a mistake. I will fire him berdasarkan data yang ada, karena terlihat jelas.

Snapshot Testing

Let me tell you a short story about this.

Sebelumnya gue belum mengetahui keuntungan melakukan snapshot testing, terlebih karena dulu gue ngerasanya ah yang ngerjain ini UI gue doang, napa harus ribet-ribet buat snapshot testing dah

Lalu masalah terjadi ketika gue sudah tidak menyentuh kode itu lagi. Gue enggak tau ketika gue membuat perubahan akan berdampak ke UI mana aja. Misal, membuat perubahan frontend-fix-sidebar . Lalu ternyata misal ada perubahan yang berdampak component yang bergantung dengan sidebar. Tanpa snapshot testing, gue cuma bisa bismillah, ini pasti fix.

Dengan snapshot testing, kita bisa diberitahu, misal seperti: Hmm… Lu ngelakuin perubahan ke sidebar nih? Sayangnya perubahan yang lu buat beda nih dengan snapshot yang sebelumnya. Update jangan?

Ketika kita setuju, maka snapshot akan diubah ke snapshot yang terbaru. Jika ternyata ketika kita melihat ke perbedaan snapshotnya, dan mendapatkan moment eureka seperti: Lah, kok sidebar mobile juga ikut-ikutan keubah? Gue harus buat kondisi lagi untuk mencegah perubahan ini nih. Simple, kita jangan update snapshotnya, lihat perubahannya lagi, ketika okay, langsung kita update snapshotnya.

Asik ya, gak perlu menggunakan skill penalaran untuk sesuatu yang tidak ter-prediksi.

Production Testing

Sebelum kita menyelam lebih jauh dengan snapshot testing, izinkan gue untuk membuat ilustrasi seputar production testing. Gue enggak tau ini namanya apa, intinya, testing in production.

Perubahan yang gue buat 2 minggu yang lalu, baru terlihat kesalahannya ketika seorang user melaporkannya ke email support@yourcompany.com . Dengan isi: Maaf kak, aku kan klik tombol “expand” sidebar, tapi kok malah gak muncul apa-apa? Kemarin-kemarin muncul koook :(

Dilanjutkan dengan tiket baru di jira dengan label BUG.

Sesuatu yang seharusnya sudah kita lupakan (terlebih sudah 2 minggu), malah harus kita kerjakan lagi. Dan ini bisa mengurangi produktivitas & kefokusan kita sebagai developer. Dan ya, this is our fault. Dan ilmu penalaran kita belum cukup hebat untuk bisa memprediksi bug tersebut, thanks to user.

Start writing snapshot testing

Gak mau kan kejadian tersebut dialami oleh kalian? Aku juga. Jadi, yuk kita bareng-bareng mulai menulis snapshot testing. Imut sekali aku.

Gue baru tau tentang snapshot testing ketika masa-masa JavaScript Fatigue/Renaissance/whatever you name it terjadi. Dulu paling kalau kagak unit, ya integration. Kenapa harus peduli dengan UI?

Berkat adanya snapshot testing, waktu kita menjadi lebih berharga. Terlebih 10000000000% user hanya peduli dengan UI, persetan business-logic react vue typescript megalodon netflix. Adanya bug di UI ternyata menjadi mimpi buruk untuk bisnis, dan juga untuk developer pastinya.

Disini kita akan menggunakan Jest sebagai Testing Framework. Sebelumnya gue menggunakan Ava, tapi jatuh cinta dengan Jest karena ke-fullstack-annya. Jest give all of your needs.

Testing Framework vs Library?

Tadi gue bilang kalau Jest adalah Testing Framework. Dan mungkin kalian sudah familiar dengan Chai yang testing/assertion library. Perbedaannya apa? Jest sudah built-in assertion library, dan Chai membutuhkan sebuah Testing Framework untuk menjalankan assertionnya.

Kalau assertion api yang disediakan tidak memenuhi kebutuhan anda, maka anda harus menggunakan assertion library yang memang benar-benar cocok dengan kebutuhan anda. Asiknya, Jest bisa meng-extend dengan external library yang ada

Instalasi Jest

yarn add jest --dev

You know it, obviously.

Ok cool, kita bisa langsung menulis unit test kita. Tapi karena kita inginnya menulis Snapshot Testing, jadi kita perlu menggunakan beberapa library untuk melakukannya.

Shallow Rendering

Shallow rendering merupakan sebuah proses rendering yang mana hanya merender “One Level Deeper” component saja. Misal, kita ingin melakukan testing component ya.js :

// src/ya.js
render () {
return (
<div>
<div className='ya__yaya'>
<h2>Ok</h2>
<Cta />
</div>
</div>
)
}
// src/Cta.js
return (
<div className='o-hero'>
<p>okokokokok</p>
<CtaButton />
</div>
)
// src/CtaButton.js
return (
<button onClick={someSideEffect}>
cool
</button>
)

Ada berapa level component ya.js tersebut? Dengan shallow rendering, maka yang kita harapkan, sesuai ekspektasi kita:

render () {
return (
<div>
<div className='ya__yaya'>
<h2>Ok</h2>
<Cta />
</div>
</div>
)
}

Bukan seperti ini:

render () {
return (
<div>
<div className='ya__yaya'>
<h2>Ok</h2>
<div className='o-hero'>
<p>okokokokok</p>
<button onClick={someSideEffect}>
cool
</button>
</div>
</div>
</div>
)
}

Testing menjadi predictable, isolated, dan fast.

Belum mulai menulis kode testing

Nah, karena kita menginginkan shallow rendering, kita akan menggunakan methods ShallowRenderer dari react-test-renderer . Tapi disini gue akan menggunakan enzyme nya Airbnb, karena terdokumentasi dengan rapih dan API nya yang intuitif.

yarn add enzyme enzyme-adapter-react-16 --dev

Mengapa kita harus meng-install enzyme-adapter-react-16 ? Karena kita akan menggunakan Enzyme untuk lingkungan React versi 16. Beda adapter lagi bila kalian menggunakan di React v15, Preact, atau UI library lainnya.

Lalu kalian bisa mulai mengkonfigurasi Enzyme kalian, dengan membuat file src/setupTests.js

// src/setupTests.js
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
configure({ adapter: new Adapter() })

Let’s write a test

Ok cool. Kita sudah siap menulis snapshot testing kita. Seperti di TDD pada umumnya, yang perlu kita tulis terlebih dahulu adalah testnya, bukan code nya. Just do it

Berdasarkan skenario yang sudah gue buat ditulisan yang diatas itu.

First Scenario

Di skenario pertama gue akan menulis test bahwa state shouldLoginButtonDisabled by default bernilai true .

It fails as expected

Mari kita buat component Login tersebut agar tidak undefined

Ok cool it works

Ini baru permulaan dan belum snapshot testing. Anggap office hours sudah selesai, kita akan buat snapshot tersebut dan meng-commit nya. Dan pekerjaan akan kita lanjutkan diesok hari.

Let’s go home

Snapshot untuk Login component kita:

Second Scenario

Hari ini akan ngerjain 2 task, kolom email dan kolom password. Kita sudah memiliki skenarionya untuk masing-masing state yang dimiliki per-element, mari kita buat test nya.

Mari kita buat code nya.

Oke kode sudah berhasil, tapi snapshot test kita gagal. Karena snapshot sebelumnya berbeda dengan snapshot yang ada saat ini.

Karena kita rasa perubahan tersebut adalah yang kita harapkan, maka bisa update snapshot yang ada dengan menekan huruf u dikeyboard.

Yuhuu

Silahkan bisa dilanjutkan sendiri.

Dimana snapshot testing bisa membantu kita?

Berdasarkan skenario diatas hanya kita tau component mana yang berubah. Di snapshot sebelumnya, yang ada hanyalah tombol gak jelas yang kesepian. Sekarang ada input component, cool.

It is helps?

Bayangkan ketika component kita sudah kompleks. Business-logic dimana-mana. Lalu, ada perubahan yang terjadi: initial state dari shouldLoginButtonDisabled kita berbeda dari snapshot yang sebelum-sebelumnya: false

Mungkin karena ada penambahan logic, seperti fitur Remember Me misalnya. Dengan remember me, kita tidak perlu mengetik email kita secara manual, karena browser sudah mengingatnya via localStorage atau apapun itu. Yang harusnya expected nya <button disabled={true}> , malah jadi <button disabled={false}> . Apakah perubahan tersebut ter-prediksi sebab akibatnya? If yes, update it jika memang rasional untuk update snapshot.

Dan berbagai case lainnya seputar perubahan UI yang tidak ter-prediksi.

Are u writing unit test, riz?

Ya. Dan disini, gue membuat Unit & Snapshot test hanya menggunakan 1 framework. Thanks, Jest.

Links related