Skip to content

Day 3: Behavior-Driven Development (BDD)

  • Session 1: 09:00 - 10:15: Overview BDD
  • Session 2: 10:30 - 11:45: Hands-on: A Case study
  • Session 3: 13:00 - 15:00: Hands-on: A Case study
  • Session 4: 15:30 - 16:30: Overview, Discussion, Lesson learned

Functional Tests & BDD

Anda telah diperkenalkan dengan contoh kode test dari tingkat unit test hingga functional test di hari pertama workshop. Unit test menguji komponen software terkecil ("unit", seperti fungsi dan method) secara independen dan, idealnya, terisolasi dari komponen lainnya. Sedangkan functional test menguji software secara utuh dengan menyimulasikan interaksi pengguna ketika menggunakan software.

Hari ini kita akan melihat bagaimana functional test dapat dikembangkan lebih lanjut untuk mendukung proses pengembangan software dengan gaya Behavior-Driven Development (BDD). Sebagai contoh, berikut ini adalah potongak kode sebuah test case dari functional test suite Sitodo PMPL yang menyimulasikan aksi menambahkan todo item baru:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Test
@DisplayName("A user can create a single todo item")
void addTodoItem_single() {
    open("/");
    checkOverallPageLayout();

    // Create a new item
    postNewTodoItem("Buy milk");

    // See the list for the newly inserted item
    checkItemsInList(List.of("Buy milk"));

    // The page can be accessed at the new, unique URL
    String currentUrl = webdriver().driver().url();
    assertTrue(currentUrl.matches(".+/list/\\d+$"), "The URL was: " + currentUrl);
}

Dapat dilihat bahwa alur uji coba dituliskan secara imperatif oleh developer. Developer mengimplementasikan serangkaian instruksi kepada "aktor" (dalam hal ini, browser yang dikendalikan Selenium WebDriver) untuk dapat menyimulasikan alur penggunaan sebuah fitur pada software yang sedang diujicobakan (System Under Test, disingkat SUT). Kumpulan instruksi tersebut seringkali sangat teknis dan sulit untuk dipahami oleh anggota tim selain developer, terutama anggota tim yang fokus pada aspek bisnis (requirements) dan penjaminan mutu.

Walaupun developer bisa meningkatkan readability kode dengan memberikan nama test case secara deskriptif serta menuliskan kode dengan gaya penulisan mengikuti bahasa natural (seringkali disebut sebagai fluent-style), functional test masih terpisah dari kebutuhan (requirements) pengembangan sehingga tidak ada keterkaitan (traceability) antara requirements dengan test.

BDD hadir untuk berusaha menjembatani tiga peran dalam pengembangan: bisnis, tim pengembang, dan penjaminan mutu. Ketiga peran tersebut berkolaborasi dalam proses pengembangan dengan menyusun requirements yang traceable dari deskripsi, implementasi, dan uji cobanya.

Persiapan Workshop

Untuk mengikuti rangkaian kegiatan workshop hari ini, harap persiapkan tools berikut di komputer:

Pastikan anda dapat memanggil program-program berikut dari dalam shell favorit anda:

1
2
3
4
git --version
java --version
javac --version
mvn --version

Hasil pemanggilan program-program di atas seharusnya akan mencetak versi program yang terpasang di komputer anda.

Buka proyek Spring Petclinic BDD di GitLab CSUI dan buat salinan proyek (fork) ke dalam akun GitLab CSUI anda. Kemudian buat salinan kode templat dari fork ke komputer anda menggunakan perintah git:

1
git clone https://gitlab.cs.ui.ac.id/[akun GitLab CSUI anda]/spring-petclinic-bdd.git

Proyek tersebut mengandung kode templat awal untuk berlatih membuat test suite BDD yang akan diujikan terhadap aplikasi Spring Petclinic. Sudah tersedia aplikasi Spring Petclinic bagi masing-masing peserta yang dapat dicoba secara daring. Berikut ini adalah tautan ke aplikasi Spring Petclinic bagi setiap peserta:

Catatan: Mohon hanya akses aplikasi Spring Petclinic sesuai nama depan masing-masing!

Sebagai alternatif bagi peserta yang ingin mencoba menjalankan aplikasinya secara lokal, tersedia juga berkas docker-compose.yml berikut untuk menjalankan aplikasi Spring Petclinic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
---
services:
  api:
    image: docker.io/springcommunity/spring-petclinic-rest:3.0.2
    ports:
      - "127.0.0.1:9966:9966"
  app:
    image: docker.io/addianto/spring-petclinic-angular:latest
    ports:
      - "127.0.0.1:80:8080"

Backend aplikasi Spring Petclinic menggunakan Spring Petclinic REST yang dapat diakses melalui port 9966 di 127.0.0.1. Sedangkan frontend aplikasi menggunakan Spring Petclinic Angular yang dapat diakses melalui port 80 di 127.0.0.1. Kedua komponen aplikasi ini dapat jalan sebagai container, ataupun di-build secara manual. Silakan merujuk ke README.md masing-masing komponen jika ingin menjalankannya secara manual (tanpa container).

Apabila sudah menyalin repositori Git dan telah menyiapkan tools yang dibutuhkan, silakan coba jalankan terlebih dahulu aplikasi contoh:

1
2
3
docker compose up --detach
# Alternatif lain jika menggunakan plugin Docker Compose melalui program docker-compose
docker-compose up --detach

Kemudian buka laman depan aplikasi menggunakan web browser seperti Google Chrome di alamat aplikasi atas nama anda atau di http://127.0.0.1 jika anda menjalankannya secara lokal. Tampilan aplikasi akan terlihat serupa dengan screenshot berikut:

Tampilan laman depan Spring Petclinic

Templat Kode Spring Petclinic BDD

Templat kode yang digunakan pada workshop hari ini mengadopsi templat starter code Serenity & Cucumber. Templat kode telah dimodifikasi agar dapat menggunakan contoh aplikasi Spring Petclinic secara lokal menggunakan container.

Sesuai dengan nama templat starter code asli yang digunakan, kita akan menggunakan Serenity dan Cucumber. Serenity merupakan test framework yang menyediakan library dan mendukung proses otomasi test yang mengikuti alur BDD. Sedangkan Cucumber menyediakan library untuk menuliskan deskripsi fitur pada test suite BDD dan menyambungkannya ke test framework seperti Serenity.

Ingat kembali bahwa salah satu tujuan BDD adalah ingin menjembatani peran non-teknis (misal: client, analis) dengan peran teknis (misal: tim pengembang). Peran non-teknis akan berkolaborasi lebih erat dengan peran teknis selama proses pengembangan berlangsung. Bentuk kolaborasi lebih erat ini salah satunya dituangkan dalam bentuk keterlibatan peran non-teknis dalam membuat deskripsi fitur serta menyusun skenario test fitur tersebut.

Peran non-teknis dapat membuat deskripsi fitur dan skenario test dengan menggunakan bahasa Gherkin. Gherkin adalah bahasa dengan struktur yang sederhana dan dituliskan menggunakan sintaks bahasa natural. Deskripsi fitur dan skenario test dapat dituliskan menggunakan Gherkin seperti menuliskan prosa dalam bahasa natural. Tentu saja ada aturan sintaks dan struktur yang harus diikuti agar dapat dijalankan oleh test framework.

Sebagai contoh, mari lihat potongan contoh skenario test berikut yang dapat ditemukan di src/test/resources/features/veterinarian/manage_specialties.feature:

1
2
3
4
5
6
7
Feature: Manage specialties
  The veterinarian wants to be able to add, edit, and delete specialties.

  Scenario: Add new specialty
    Given a system operator named "John" is looking at the specialties page
    When he adds a new specialty called "cardiology"
    Then he should see the specialty "cardiology" on the list of specialties

Skenario test dituliskan sebagai bagian dari deskripsi fitur yang dituliskan dalam berkas dengan ekstensi .feature. Pada contoh di atas, deskripsi fitur dimulai dengan teks Feature: Manage specialties yang berarti berkas ini merupakan deskripsi fitur "Manage specialties". Selanjutnya, terdapat teks bebas yang menjelaskan lebih rinci fitur terkait. Teks bebas tersebut dapat juga berisi dengan deskripsi use case atau user story.

Sebuah fitur dapat memiliki satu atau lebih skenario test. Pada contoh di atas, saat ini hanya ada sebuah skenario test untuk menguji fitur menambahkan bidang spesialis dokter hewan. Skenario test berisi tiga buah komponen penting di dalam membuat instruksi pengujian dengan gaya BDD: Given, When, dan Then. Given merepresentasikan kondisi awal skenario test sebelum menjalankan aksi aktor. When merepresentasikan aksi aktor dalam SUT. Then merepresentasikan kondisi akhir skenario test setelah menjalankan aksi aktor.

Berikut ini adalah daftar referensi terkait sintaks Gherkin yang direkomendasikan:

Latihan Singkat: Menulis Deskripsi Fitur & Skenario Test Baru

Mari coba membuat sebuah deskripsi fitur beserta sebuah skenario test baru. Silakan ikuti tasks berikut:

  1. Buat berkas deskripsi fitur baru bernama manage_pet_types.feature di src/test/resources/features/veterinarian.
  2. Tulis nama fitur baru "Manage pet types" beserta deskripsi singkatnya.
  3. Susun sebuah skenario test baru bernama "Add new pet type" yang menguji operator sistem dapat menambahkan sebuah jenis binatang peliharaan baru ke dalam sistem. Tulis skenarionya mengikuti kerangka Given, When, dan Then.

Salah satu contoh solusi dari latihan mandiri dapat dilihat pada contoh kode berikut:

1
2
3
4
5
6
7
Feature: Manage pet types
  The veterinarian wants to be able to add, edit, and delete pet types.

  Scenario: Add new pet type
    Given a system operator named "Jane" is looking at the pet types page
    When she adds a new pet type named "turtle"
    Then she should see the pet type "turtle" on the list of pet types

Apabila telah selesai, simpan hasil pekerjaan. Buat commit Git dan push ke "fork" di GitLab CSUI.

Menjalankan Test Suite BDD

Test suite BDD dapat dijalankan dengan dua cara yaitu menggunakan IDE (IntelliJ IDEA) atau menggunakan Maven di shell. Jika anda ingin menjalankan test suite BDD menggunakan IDE, maka buka berkas class Java yang merepresentasikan test suite BDD, yaitu class CucumberTestSuite di IDE. Kemudian jalankan class tersebut dengan menekan tombol yang muncul di sisi kiri editor pada IDE dan pilih Run 'CucumberTestSuite' seperti yang tergambarkan di screenshot berikut:

CucumberTestSuite di IDE

Jika nyaman menggunakan shell atau ingin menjalankan test suite BDD di lingkungan Continuous Integration (CI), panggil perintah Maven berikut di shell:

1
mvn verify

Test suite BDD akan dijalankan oleh test runner secara headless terhadap aplikasi yang berjalan secara lokal. Begitu test suite BDD selesai dijalankan, laporan hasil test dapat ditemukan di folder target/site/serenity. Apabila menggunakan Maven, perintah mvn verify juga akan menghasilkan laporan komplit hasil test sebagai dokumen HTML. Contoh tampilan laporannya dapat dilihat pada screenshot berikut:

Contoh laporan BDD Serenity

Jika mengacu pada contoh laporan di atas, dapat dilihat contoh test suite BDD mengandung tiga buah test case. Dari tiga test case tersebut, dua test case berhasil dan satu test case gagal.

Deskripsi fitur memang bermanfaat bagi peran non-teknis agar dapat ikut berkontribusi di dalam proses pengembangan. Namun bukan berarti deskripsi fitur dan skenario test secara ajaib bisa otomatis menguji coba SUT sesuai keinginan. Peran teknis seperti programmer dan test engineer penting untuk dapat menyambungkan deskripsi fitur ke kode test.

Catatan: Salah satu masalah yang umum terjadi ketika melakukan uji coba fungsionalitas melalui simulasi interaksi pada user interface adalah hasil test yang tidak stabil. Ada kemungkinan skenario uji coba yang dijalankan oleh kode test tidak berhasil berinteraksi dengan elemen user interface yang diinginkan. Pada aplikasi Web, isu elemen user interface yang gagal ditemukan atau tidak bisa diujicobakan oleh kode test seringkali terjadi karena DOM Tree dari tampilan HTML belum sinkron. Hal ini sangat mungkin terjadi pada aplikasi Web dengan user interface yang dibuat atau diubah oleh kode JavaScript.

Latihan Singkat: Atur Konfigurasi Test Suite BDD

Saat ini test suite BDD disiapkan untuk menjalankan test case terhadap aplikasi yang berjalan secara lokal. Anda dapat mengubah alamat aplikasi yang akan diujicobakan dengan membuka berkas class Java SpringPetclinicHomePage di dalam folder src/test/java/starter/navigation.

Isi dari class tersebut saat ini adalah sebagai berikut:

1
2
3
4
5
6
7
package starter.navigation;

import net.serenitybdd.annotations.DefaultUrl;
import net.serenitybdd.core.pages.PageObject;

@DefaultUrl("http://127.0.0.1")
public class SpringPetclinicHomePage extends PageObject { }

Silakan ubah nilai string di dalam anotasi @DefaultUrl ke alamat aplikasi anda. Misalnya: @DefaultUrl("https://spring-petclinic-angular-[nama depan anda].dokku-ppl.cs.ui.ac.id").

Jika anda menjalankan aplikasi contoh Spring Petclinic secara lokal, maka anda tidak perlu mengubah nilai string di dalam @DefaultUrl.

Anda juga dapat mengatur apakah test suite BDD akan berjalan secara headless ataupun tidak. Saat ini opsi headless.mode diatur menjadi true sehingga web browser tidak akan muncul di layar ketika test suite BDD berjalan. Konfigurasi terkait dapat dilihat di berkas serenity.conf dengan contoh sebagai berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
serenity {
    take.screenshots = FOR_FAILURES
}

headless.mode = true
webdriver {
  driver = chrome
  capabilities {
    browserName = "chrome"
    acceptInsecureCerts = true
    "goog:chromeOptions" {
      args = ["remote-allow-origins=*","test-type", "no-sandbox", "ignore-certificate-errors", "--window-size=1000,800",
        "incognito", "disable-infobars", "disable-gpu", "disable-default-apps", "disable-popup-blocking",
        "disable-dev-shm-usage", "disable-extensions", "disable-web-security", "disable-translate", "disable-logging"]
    }
  }
}

Nilai di dalam opsi headless.mode dapat diatur menjadi false apabila ingin melihat web browser menyimulasikan interaksi dengan SUT.

Membuat Step Definition & Glue Code

Mari lihat kembali contoh skenario test "Add new specialty":

1
2
3
4
Scenario: Add new specialty
  Given a system operator named "John" is looking at the specialties page
  When he adds a new specialty called "cardiology"
  Then he should see the specialty "cardiology" on the list of specialties

Masing-masing kalimat yang diawali dengan kata kunci Given, When, dan Then perlu dipasangkan dengan serangkaian instruksi bahasa pemrograman. Instruksi-instruksi tersebut seringkali disebut sebagai step definitions yang merupakan bagian dari Glue Code, yaitu kode program yang mendukung dan menjalankan test suite BDD.

Sebagai contoh, mari lihat step definition padanan kalimat berawalan Given:

1
2
3
4
5
6
7
8
9
@Given("a system operator named {actor} is looking at the specialties page")
public void lookingAtSpecialtiesPage(Actor actor) {
    actor.wasAbleTo(
            Open.browserOn()
                .the(SpringPetclinicHomePage.class),
            HoverOverBy.over(Link.withText("Specialties")),
            Click.on(Link.withText("Specialties"))
    );
}

Step definition diimplementasikan sebagai fungsi dengan informasi tambahan yang akan memetakan fungsi tersebut dengan kalimat terkait. Pada contoh di atas, fungsi diberikan anotasi @Given dengan parameter berisi teks regular expression (regex) yang akan dicocokkan dengan kalimat di skenario test.

Apabila kalimat pada skenario test mengandung elemen yang dapat berubah-ubah seperti aktor/pengguna fitur atau input, maka teks regex dapat mengandung placeholder yang akan menjadi parameter bagi fungsi step definition. Pada contoh di atas, actor merupakan tipe parameter khusus yang disediakan templat kode untuk memetakan aktor di skenario test dengan objek Actor yang akan dikendalikan oleh implementasi fungsi step definition.

Inti utama dari fungsi step definition adalah objek Actor, yaitu representasi dari aktor/pengguna fitur. Pada contoh di atas, objek Actor memanggil fungsi wasAbleTo yang menyimulasikan aksi-aksi aktor untuk mencapai kondisi awal yang diinginkan. Fungsi wasAbleTo menerima parameter berupa satu atau lebih objek Performable yang merepresentasikan aksi fisik ketika berinteraksi dengan SUT. Pada contoh di atas, aktor melakukan tiga aksi berikut:

  1. Buka web browser untuk membuka SUT, yaitu aplikasi Spring Petclinic.
  2. Menggerakkan kursor mouse ke tautan yang mengandung teks "Specialties"
  3. Klik tautan yang mengandung teks "Specialties"

Apabila ketiga aksi tersebut berhasil dilakukan, maka kondisi web browser aktor saat ini sudah menampilkan halaman daftar bidang spesialis dokter hewan. Dengan demikian, kondisi awal yang dibutuhkan untuk menguji skenario "Add new specialty" tercapai dan dapat lanjut ke aksi-aksi utama untuk menambahkan data baru.

Mari lihat step definition padanan kalimat berawalan When:

1
2
3
4
5
6
7
8
9
@When("{actor} adds a new specialty called {string}")
public void addsNewSpecialty(Actor actor, String name) {
    actor.attemptsTo(
            Click.on(Button.withText("Add")),
            Enter.theValue(name)
                 .into(InputField.withNameOrId("name"))
                 .thenHit(Keys.ENTER)
    );
}

Pada contoh di atas, objek Actor memanggil fungsi attemptsTo yang kurang lebih serupa dengan fungsi wasAbleTo di contoh sebelumnya. Fungsi attemptsTo sama-sama menerima satu atau lebih objek Performable. Aktor melakukan tiga aksi berikut dengan asumsi saat ini aktor sedang melihat halaman daftar bidang spesialis dokter hewan:

  1. Klik tombol dengan teks "Add"
  2. Masukkan input string ke sebuah input field dengan atribut name atau id bernilai name.
  3. Tekan tombol Enter di keyboard untuk submit input.

Hasil akhir dari ketiga aksi di atas akan diverifikasi oleh fungsi step definition yang dipetakan dengan kalimat Then. Contohnya adalah sebagai berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Then("{actor} should see the specialty {string} on the list of specialties")
public void shouldSeeTheSpecialty(Actor actor, String name) {
    actor.attemptsTo(
            Ensure.thatAmongst(InputField.withNameOrId("spec_name"))
                  .anyMatch(name + " should be on the list",
                          (field) -> field.getValue()
                                          .equals(name)
                  )
    );
}

Pada contoh kode di atas, objek Actor sama-sama menggunakan fungsi attemptsTo seperti pada fungsi step definition untuk kalimat When. Hanya saja, parameter fungsi tersebut sebaiknya diisi dengan objek PerformablePredicate yang dapat dibuatkan oleh objek Ensure. Objek Ensure menyediakan fungsi untuk merepresentasikan aksi verifikasi yang dilakukan oleh aktor terhadap kondisi akhir SUT. Aksi verifikasi yang dilakukan oleh aktor adalah sebagai berikut:

  1. Aktor melihat seluruh input field dengan atribut name atau id bernilai spec_name.
  2. Kemudian cek apakah nama bidang spesialis yang baru dimasukkan di antara seluruh input field yang ditemukan.

Jika aksi verifikasi berhasil dilakukan, maka test framework akan melaporkan skenario test tersebut berhasil/"pass". Sebaliknya, jika ternyata verifikasi gagal dilakukan, maka test framework akan melaporkan skenario test tersebut gagal/"fail".

Sebagai ringkasan, resep yang umum dipakai dalam membuat step definition adalah sebagai berikut:

  • Step definition dari kalimat berawalan Given berperan untuk menyiapkan kondisi awal pada SUT sebelum menyimulasikan fitur. Kode Java yang dapat digunakan dalam implementasi step definition ini adalah fungsi wasAbleTo milik objek Actor. Fungsi tersebut menerima parameter berupa satu atau lebih objek Performable yang merepresentasikan aksi aktor. Masing-masing objek Performable akan dijalankan sesuai dengan urutan objeknya di dalam parameter fungsi wasAbleTo.
  • Step definition dari kalimat berawalan When berperan untuk menjalankan aksi-aksi utama yang menyimulasikan fitur. Kode Java yang dapat digunakan dalam implementasi step definition ini adalah fungsi attemptsTo milik objek Actor. Serupa dengan wasAbleTo, fungsi attemptsTo menerima parameter berupa satu atau lebih objek Performable.
  • Step definition dari kalimat berawalan Then berperan untuk melakukan verifikasi terhadap kondisi akhir pada SUT dengan kondisi yang diharapkan oleh skenario test. Kode Java yang dapat digunakan dalam implementasi step definition ini adalah fungsi attemptsTo milik objek Actor. Hanya saja, objek-objek yang dimasukkan ke parameter fungsi attemptsTo sebaiknya adalah objek bertipe PerformablePredicate yang dapat dibangun dengan bantuan objek Ensure. Objek Ensure mengandung instruksi yang melakukan verifikasi terhadap kondisi akhir SUT.

Sebelum memulai latihan mandiri membuat step definition baru, ada baiknya kita mempelajari secara singkat bagaimana kita dapat merepresentasikan elemen-elemen user interface ke dalam kode test.

XPath

Serenity sebagai test framework sudah menyediakan library yang mempermudah pencarian elemen user interface yang umum terlibat dalam interaksi aktor dengan SUT. Sebagai contoh, kita dapat mendapatkan referensi terhadap elemen tombol dengan teks "Add" dengan memanggil Button.withText("Add"), dimana fungsi tersebut akan mencari elemen HTML <button> yang mengandung nilai teks "Add" di dalam struktur DOM Tree halaman HTML yang sedang dibuka. Namun akan ada kasus dimana elemen user interface yang diinginkan berada di dalam struktur halaman HTML yang cukup kompleks dan belum ada library yang dapat membantu kita menemukan elemen tersebut.

Sebagai contoh, mari lihat kode HTML untuk elemen tabel data pada halaman web fitur "Manage specialties":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<table _ngcontent-ng-c554405439="" id="specialties" class="table table-striped">
    <thead _ngcontent-ng-c554405439="">
        <tr _ngcontent-ng-c554405439="">
            <th _ngcontent-ng-c554405439="">Name</th>
            <th _ngcontent-ng-c554405439=""></th>
            <th _ngcontent-ng-c554405439=""></th>
        </tr>
    </thead>
    <tbody _ngcontent-ng-c554405439="">
        <tr _ngcontent-ng-c554405439="">
            <td _ngcontent-ng-c554405439="">
                <input _ngcontent-ng-c554405439="" type="text" name="spec_name" class="form-control ng-untouched ng-pristine ng-valid" ng-reflect-name="spec_name" id="0" readonly="" ng-reflect-model="radiology">
            </td>
            <td _ngcontent-ng-c554405439="">
                <button _ngcontent-ng-c554405439="" class="btn btn-default">Edit</button>
                <button _ngcontent-ng-c554405439="" class="btn btn-default">Delete</button>
            </td>
        </tr>
        <tr _ngcontent-ng-c554405439="">
            <td _ngcontent-ng-c554405439="">
                <input _ngcontent-ng-c554405439="" type="text" name="spec_name" class="form-control ng-untouched ng-pristine ng-valid" ng-reflect-name="spec_name" id="1" readonly="" ng-reflect-model="surgery">
            </td>
            <td _ngcontent-ng-c554405439="">
                <button _ngcontent-ng-c554405439="" class="btn btn-default">Edit</button>
                <button _ngcontent-ng-c554405439="" class="btn btn-default">Delete</button>
            </td>
        </tr>
        <tr _ngcontent-ng-c554405439="">
            <td _ngcontent-ng-c554405439="">
                <input _ngcontent-ng-c554405439="" type="text" name="spec_name" class="form-control ng-untouched ng-pristine ng-valid" ng-reflect-name="spec_name" id="2" readonly="" ng-reflect-model="dentistry">
            </td>
            <td _ngcontent-ng-c554405439="">
                <button _ngcontent-ng-c554405439="" class="btn btn-default">Edit</button>
                <button _ngcontent-ng-c554405439="" class="btn btn-default">Delete</button>
            </td></tr><!--bindings={
  "ng-reflect-ng-for-of": "[object Object],[object Object"
}--></tbody>
</table>

Misalkan kita ingin menyimulasikan aksi pengguna dimana pengguna menekan tombol "Edit" pada item pertama di dalam tabel. Dapat dilihat pada potongan HTML di atas bahwa ada tiga elemen <button> yang mengandung teks "Edit". Bagaimana kita dapat mendapatkan referensi terhadap tombol "Edit" yang diinginkan?

Salah satu algoritma yang dapat diikuti:

  1. Dapatkan referensi ke elemen HTML tabel yang diinginkan dan simpan referensinya ke dalam variabel table.
  2. Melalui referensi table, dapatkan referensi ke badan utama tabel dan simpan referensinya ke dalam variable tbody.
  3. Melalui referensi tbody, dapatkan referensi ke baris pertama tabel data dan simpan referensinya ke dalam variable row.
  4. Melalui referensi row, dapatkan referensi ke kolom yang mengandung tombol dengan teks "Edit" dan simpan referensinya ke dalam variabel column.
  5. Akhirnya, melalui referensi column, dapatkan referensi ke tombol di dalam kolom tersebut dan lakukan aksi klik pada tombol tersebut.

Algoritma di atas dapat diimplementasikan menjadi serangkaian statement pemanggilan fungsi-fungsi dari library Serenity. Namun ada alternatif lain yang lebih ringkas, yaitu menggunakan bahasa XPath. XPath adalah bahasa yang digunakan untuk menulis ekspresi yang berisi sebuah pencarian pada dokumen XML atau HTML. Instruksi pencarian elemen secara imperatif yang digambarkan pada contoh algoritma di atas dapat diringkas menjadi ekpresi singkat.

Untuk dapat menuliskan XPath, kita perlu memahami bahasanya terlebih dahulu. Terdapat beberapa referensi yang disebutkan di akhir bagian dokumen ini untuk mempelajari lebih lanjut. Namun untuk keperluan workshop singkat ini, kita dapat menggunakan shortcut yang disediakan web browser untuk mengetahui ekspresi XPath sebuah elemen pada dokumen HTML.

Buka halaman fitur "Manage specialties" dan buka Developer Tools di web browser. Pilih "Inspect Element" lalu klik tombol "Edit" agar web browser menampilkan kode HTML untuk tombol tersebut. Kemudian klik kanan kode HTML tombol tersebut, lalu piih Copy > XPath seperti yang digambarkan pada screenshot berikut:

Contoh mencari XPath melalui Inspect Element

Coba salin ("paste") ekspresi XPath ke dalam text editor untuk melihatnya. Hasilnya dapat dilihat pada teks berikut:

1
/html/body/app-root/div[2]/app-specialty-list/div/div/table/tbody/tr[1]/td[2]/button[1]

Ekspresi XPath di atas dapat diringkas menjadi:

1
//table[@id='specialties']/tbody/tr[1]/td[2]/button[1]

Sebagai contoh penerapan XPath, lihat potongan kode berikut yang diambil dari fungsi step definition untuk mengubah nama bidang spesialis pertama pada daftar:

1
Click.on(Button.located(By.xpath("//table[@id='specialties']/tbody/tr[1]/td[2]/button[1]")))

Ekspresi XPath di atas merupakan instruksi untuk mencari sebuah elemen pada dokumen HTML. //table[@id='specialties'] mencari elemen <table> dengan atribut id bernilai specialties. Kemudian dilanjutkan dengan /tbody yang akan mencari elemen <tbody> yang berada di dalam <table>. Lalu /tr[1]/td[2]/button[1] berarti mencari elemen <tr> (baris) pertama di dalam tabel data, diikuti dengan mencari elemen <td> (kolom) kedua di baris tersebut, dan diakhiri dengan mencari elemen <button> (tombol) pertama di kolom tersebut.

Berikut ini adalah daftar referensi terkait XPath yang direkomendasikan:

Materi Tambahan: Troubleshooting Kegagalan Pencarian Elemen

Salah satu isu yang umum terjadi dalam pengujian aplikasi melalui simulasi interaksi dengan user interface adalah hasil eksekusi test yang tidak stabil, atau seringkali disebut sebagai flaky test. Misalnya, kadang ada sebuah test case yang sukses berjalan kemudian gagal ketika dijalankan kembali. Test yang tidak stabil bisa disebabkan karena beberapa hal, seperti isu koneksi antara web browser dengan aplikasi yang diujicobakan, atau struktur dokumen yang belum final di-update oleh kode JavaScript yang berjalan di web browser.

Beberapa isu seperti masalah koneksi memang sulit diantisipasi, sehingga solusinya terkadang cukup sekedar menjalankan test-nya kembali. Tapi terkadang ada isu yang muncul karena implementasi test yang belum komprehensif.

Ambil contoh misalnya ada sebuah test yang gagal dan laporan error yang ditangkap oleh Serenity adalah sebagai berikut:

1
Element click intercepted exception: element click intercepted: Element (button _ngcontent-ng-c910900098='' class='btn btn-default')...(/button) is not clickable at point (115, 717). Other element would receive the click: (div _ngcontent-ng-c87473289='' class='col-12 text-center')...(/div)

Berdasarkan pesan error yang ditangkap oleh Serenity dan Selenium, web browser yang dikendalikan oleh Selenium gagal menekan tombol pada titik koordinat (115, 717). Kemungkinan besar error tersebut muncul karena secara visual tombol terkait memang tidak terlihat di dalam jendela web browser. Oleh karena itu, instruksi di dalam fungsi step definition bisa diperbaiki untuk menjamin jendela web browser digerakkan (di-scroll) ke lokasi tombol yang diinginkan.

Sebagai contoh, mari perbaiki step definition yang dipetakan ke kalimat When {actor} adds a new specialty sehingga web browser di-scroll ke lokasi tombol yang diinginkan sebelum menekan tombol:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@When("{actor} adds a new specialty called {string}")
public void addsNewSpecialty(Actor actor, String name) {
    actor.attemptsTo(
        Scroll.to(Button.withText("Add")),  // <-- Statement baru ditambahkan ke dalam step definition
        Click.on(Button.withText("Add")),
        Enter.theValue(name)
             .into(InputField.withNameOrId("name"))
             .thenHit(Keys.ENTER)
    );
}

Latihan Singkat: Membuat Step Definition Baru

Mari coba membuat sebuah deskripsi fitur beserta sebuah skenario test baru. Silakan ikuti tasks berikut:

  1. Buat class Java baru bernama PetTypeStepDefinitions di dalam package stepdefinitions.
  2. Buat fungsi step definition yang dipetakan ke kalimat Given pada skenario test "Add new pet type"
  3. Buat fungsi step definition yang dipetakan ke kalimat When pada skenario test "Add new pet type"
  4. Buat fungsi step definition yang dipetakan ke kalimat Then pada skenario test "Add new pet type"
  5. Jalankan test suite BDD untuk fitur "Manage pet types" dan pastikan masing-masing fungsi step definition berhasil dicocokkan dengan kalimat terkait.

Tidak ada contoh solusi untuk latihan ini karena hasil akhirnya mirip dengan fungsi-fungsi step definition untuk fitur "Manage specialties". Silakan lihat isi class SpecialtyStepDefinitions sebagai referensi.

Refactoring Pada Deskripsi Fitur & Glue Code

Mari lihat kembali deskripsi fitur "Manage specialties":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Feature: Manage specialties
  The veterinarian wants to be able to add, edit, and delete specialties.

  Scenario: Add new specialty
    Given a system operator named "John" is looking at the specialties page
    When he adds a new specialty called "cardiology"
    Then he should see the specialty "cardiology" on the list of specialties

  Scenario: Edit the first specialty
    Given a system operator named "John" is looking at the specialties page
    When he edits the first specialty to "snake oil"
    Then he should see the specialty "snake oil" on the list of specialties

Kalimat berawalan Given muncul berulang kali pada deskripsi fitur di atas karena masing-masing skenario test memiliki kondisi awal SUT yang sama, yaitu aktor membuka laman daftar bidang spesialis dokter hewan.

Untuk mengurangi duplikasi dan menerapkan prinsip DRY ("Don't Repeat Yourself") pada kode test, kita dapat menggunakan kata kunci Background. Background dapat mengandung kalimat-kalimat yang akan diterapkan ke setiap skenario test di dalam deskripsi fitur.

Sebagai contoh, ubah deskripsi fitur "Manage specialties":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Feature: Manage specialties
  The veterinarian wants to be able to add, edit, and delete specialties.

  Background:
    Given a system operator named "John" is looking at the specialties page

  Scenario: Add new specialty
    When he adds a new specialty called "cardiology"
    Then he should see the specialty "cardiology" on the list of specialties

  Scenario: Edit the first specialty
    When he edits the first specialty to "snake oil"
    Then he should see the specialty "snake oil" on the list of specialties

Coba jalankan kembali test suite BDD dan lihat laporan hasil test. Masing-masing skenario test akan melaporkan kalimat Given yang sama di dalam laporannya.

Refactoring juga dapat diterapkan pada Glue Code. Sebagai contoh, mari lihat potongan kode yang menyimulasikan aksi klik menu pada navigation bar:

1
2
3
4
5
6
// Ditemukan pada step definition fitur terkait Specialty
HoverOverBy.over(Link.withText("Specialties"))
Click.on(Link.withText("Specialties"))
// Ditemukan pada step definition fitur terkait Pet Type
HoverOverBy.over(Link.withText("Pet Types"))
Click.on(Link.withText("Pet Types"))

Aksi klik menu tersebut dapat digeneralisasi untuk digunakan kembali ketika kita akan menguji fitur lainnya yang dapat diakses melalui navigation bar yang sama. Sebagai contoh, class Java NavigationBar di dalam package starter.helpers mengandung sebuah fungsi clickMenu yang mengenkapsulasi aksi klik menu. Contoh kode dari class tersebut dapat dilihat pada potongan kode berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package starter.helpers;

import net.serenitybdd.screenplay.Performable;
import net.serenitybdd.screenplay.Task;
import net.serenitybdd.screenplay.actions.Click;
import net.serenitybdd.screenplay.actions.HoverOverBy;
import net.serenitybdd.screenplay.ui.Link;

public class NavigationBar {

    public static Performable clickMenu(String name) {
        return Task.where("{0} clicks '" + name + "' on the navigation bar",
                HoverOverBy.over(Link.withText(name)),
                Click.on(Link.withText(name)));
    }
}

Aksi klik menu pada navigation bar bisa digantikan dengan pemanggilan fungsi clickMenu. Cari kemunculan dua statement yang menyimulasikan aksi klik menu, kemudian ganti dengan pemanggilan prosedur yang mengenkapsulasi aksi klik menu tersebut.

Sebagai contoh:

1
2
3
4
5
6
7
8
9
// ...
HoverOverBy.over(Link.withText([nama menu]))
Click.on(Link.withText([nama menu]))
// ...

// Ganti dua statement di atas menjadi
// ...
NavigationBar.clickMenu([nama menu])
// ...

Latihan Singkat: Refactor Aksi Verifikasi Isi Daftar

Lihat kembali fungsi step definition untuk kalimat Then fitur "Manage specialties":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Then("{actor} should see the pet type {string} on the list of pet types")
public void shouldSeeThePetType(Actor actor, String name) {
    actor.attemptsTo(
            Ensure.thatAmongst(InputField.withNameOrId("pettype_name"))
                  .anyMatch(name + " should be on the list",
                          (field) -> field.getValue()
                                          .equals(name)
                  )
    );
}

Prosedur verifikasi data item pada daftar berpotensi untuk digeneralisasi sehingga dapat digunakan untuk prosedur serupa di fitur lain. Coba lakukan refactoring terhadap prosedur verifikasi tersebut dengan langkah-langkah berikut:

  1. Buat class Java baru dengan nama bebas di dalam package starters.helpers. Berikan nama class Java yang deskriptif.
  2. Di dalam class baru tersebut, implementasikan sebuah fungsi static baru yang mengembalikan enkapsulasi prosedur verifikasi isi daftar sebagai objek tipe PerformablePredicate.
  3. Ganti prosedur verifikasi di fungsi step definition terkait dengan pemanggilan fungsi yang dibuat pada langkah sebelumnya.
  4. Jalankan kembali test suite BDD dan pastikan hasilnya masih sama sebelum melakukan refactoring.

Contoh solusi dari fungsi yang mengandung hasil enkapsulasi adalah sebagai berikut:

1
2
3
4
5
6
7
public static PerformablePredicate verifyDataItem(String inputFieldNameOrId, String dataItem) {
    return Ensure.thatAmongst(InputField.withNameOrId(inputFieldNameOrId))
                 .anyMatch(name + " should be on the list",
                         (field) -> field.getValue()
                                         .equals(dataItem)
                 );
}

Apabila telah selesai, simpan hasil pekerjaan. Buat commit Git dan push ke "fork" di GitLab CSUI.

Latihan Mandiri: Membuat Test Suite BDD Untuk Fitur Lainnya

Anda telah mencoba melengkapi deskripsi fitur beserta skenario test untuk dua fitur, yaitu fitur "Manage specialties" dan "Manage pet type". Masih ada beberapa fitur lain seperti fitur mengelola data dokter hewan ("Manage veterinarian"), dan fitur mengelola data pemilik binatang peliharaan ("Manage owner"). Pada waktu workshop yang tersisa, silakan lengkapi test suite BDD dengan menambahkan deskripsi fitur dan skenario test bagi fitur-fitur yang belum diuji.

Penutup

Selamat! Anda telah mencapai hari terakhir dari kegiatan workshop internal. Anda telah berlatih menerapkan otomasi kegiatan Software Quality Assurance, serta menerapkan TDD dan BDD.

Untuk bahan diskusi saat refleksi:

  • Apa perbedaan test tradisional (yaitu, test yang tidak memiliki keterkaitan dengan requirements) dengan test BDD?
  • Apakah hasil eksekusi test suite BDD anda stabil?
  • Apa saja isu yang dapat terjadi ketika menguji SUT melalui simulasi interaksi terhadap user interface?
  • Apakah BDD bisa diterapkan juga untuk menguji software dengan user interface non-visual seperti API?

Last update: 2023-11-09 06:01:12
Created: 2023-10-17 02:48:29