Halo Habr. Nama saya Vitaliy Kotov, saya bekerja di departemen pengujian Badoo. Saya menulis banyak tes otomatis UI, tetapi saya bekerja lebih banyak dengan mereka yang telah melakukan ini belum lama ini dan belum berhasil menginjak semua garu.
Jadi, setelah menambahkan pengalaman dan pengamatan saya sendiri terhadap orang lain, saya memutuskan untuk menyiapkan bagi Anda kumpulan "cara menulis tes tidak sepadan". Saya mendukung setiap contoh dengan uraian terperinci, contoh kode, dan tangkapan layar.
Artikel ini akan menarik bagi para penulis pemula tes UI, tetapi orang-orang tua dalam topik ini mungkin akan mempelajari sesuatu yang baru, atau hanya tersenyum, mengingat diri mereka sendiri "di masa muda mereka". :)
Ayo pergi!

Isi
Pelacak tanpa atribut
Mari kita mulai dengan contoh sederhana. Karena kita berbicara tentang tes UI, pelacak memainkan peran penting di dalamnya. Locator adalah garis yang disusun berdasarkan aturan tertentu dan menjelaskan satu atau lebih elemen XML (khususnya HTML).
Ada beberapa jenis pelacak. Misalnya,
pencari css digunakan untuk lembar gaya kaskade.
Pelacak XPath digunakan untuk bekerja dengan dokumen XML. Dan sebagainya.
Daftar lengkap jenis pelacak yang digunakan oleh
Selenium dapat ditemukan di
seleniumhq.imtqy.com .
Dalam pengujian UI, pelacak digunakan untuk menggambarkan elemen-elemen yang dengannya pengemudi harus berinteraksi.
Di hampir semua inspektur browser, dimungkinkan untuk memilih elemen yang menarik bagi kami dan menyalin XPath-nya. Itu terlihat seperti ini:

Ternyata pelacak seperti itu:
/html/body/div[3]/div[1]/div[2]/div/div/div[2]/div[1]/a
Tampaknya tidak ada yang salah dengan pencari lokasi seperti itu. Bagaimanapun, kita dapat menyimpannya dalam konstanta atau bidang kelas, yang dengan namanya akan menyampaikan esensi elemen:
@FindBy(xpath = "/html/body/div[3]/div[1]/div[2]/div/div/div[2]/div[1]/a") public WebElement createAccountButton;
Dan bungkus teks kesalahan yang sesuai jika elemen tidak ditemukan:
public void waitForCreateAccountButton() { By by = By.xpath(this.createAccountButton); WebDriverWait wait = new WebDriverWait(driver, timeoutInSeconds); wait .withMessage(โCannot find Create Account button.โ) .until( ExpectedConditions.presenceOfElementLocated(by) ); }
Pendekatan ini memiliki nilai tambah: tidak perlu mempelajari XPath.
Namun, ada sejumlah kelemahan. Pertama, ketika mengubah tata letak tidak ada jaminan bahwa elemen pada locator akan tetap sama. Mungkin saja orang lain akan menggantikannya, yang akan mengarah pada keadaan yang tidak terduga. Kedua, tugas autotest adalah mencari bug, dan tidak memantau perubahan tata letak. Oleh karena itu, penambahan beberapa pembungkus atau beberapa elemen lain yang lebih tinggi di pohon seharusnya tidak mempengaruhi pengujian kami. Kalau tidak, kami butuh cukup banyak waktu untuk memperbarui lokasi.
Kesimpulan: Anda harus membuat pencari lokasi yang menggambarkan elemen dengan benar dan tahan terhadap perubahan tata letak di luar bagian yang diuji dari aplikasi kami. Misalnya, Anda dapat mengikat ke satu atau beberapa atribut elemen:
//a[@rel=โcreateAccountโ]
Pencari seperti itu lebih mudah dilihat dalam kode, dan itu akan rusak hanya jika "rel" menghilang.
Kelebihan lain dari locator tersebut adalah kemampuan untuk mencari di repositori template dengan atribut yang ditentukan. Tetapi apa yang harus dicari jika locator terlihat seperti dalam contoh aslinya? :)
Jika pada awalnya dalam aplikasi elemen tidak memiliki atribut atau mereka diatur secara otomatis (misalnya, karena
kebingungan kelas), ini layak untuk didiskusikan dengan pengembang. Mereka seharusnya tidak kurang tertarik dalam mengotomatisasi pengujian produk dan pasti akan bertemu dengan Anda dan menawarkan solusi.
Periksa item yang hilang
Setiap pengguna Badoo memiliki profilnya sendiri. Ini berisi informasi tentang pengguna: (nama, umur, foto) dan informasi tentang dengan siapa pengguna ingin mengobrol. Selain itu, dimungkinkan untuk menunjukkan minat Anda.
Misalkan kita pernah memiliki bug (walaupun, tentu saja, ini tidak begitu :)). Pengguna di profilnya memilih minat. Tidak menemukan minat yang sesuai dari daftar, ia memutuskan untuk mengklik "Lainnya" untuk memperbarui daftar.
Perilaku yang diharapkan: minat lama akan hilang, minat baru akan muncul. Melainkan, muncul "Kesalahan Tidak Terduga":

Ternyata ada masalah di sisi server, jawabannya tidak sama, dan klien memproses masalah ini dengan menunjukkan pemberitahuan.
Tugas kita adalah menulis autotest yang akan memeriksa kasus ini.
Kami menulis sekitar skrip berikut:
- Buka profil
- Buka daftar minat
- Klik tombol "Lainnya"
- Pastikan bahwa kesalahan tidak muncul (misalnya, tidak ada elemen div.error)
Kami menjalankan tes seperti itu. Namun, hal berikut terjadi: setelah beberapa hari / bulan / tahun, bug muncul kembali, meskipun tes tidak menangkap apa pun. Mengapa
Semuanya cukup sederhana: selama lulus tes yang berhasil, pelacak elemen tempat kami mencari teks kesalahan telah berubah. Ada refactoring dari template dan alih-alih kelas "error" kami mendapat kelas "error_new".
Selama refactoring, tes terus bekerja seperti yang diharapkan. Elemen div.error tidak muncul, tidak ada alasan untuk jatuh. Tetapi sekarang elemen "div.error" tidak ada sama sekali - oleh karena itu, tes tidak akan pernah gagal, apa pun yang terjadi dalam aplikasi.
Kesimpulan: lebih baik untuk menguji operabilitas antarmuka dengan pemeriksaan positif. Dalam contoh kita, kita harus berharap bahwa daftar minat telah berubah.
Ada situasi ketika tes negatif tidak dapat digantikan dengan tes positif. Misalnya, ketika berinteraksi dengan beberapa elemen, tidak ada yang terjadi dalam situasi "baik", dan kesalahan muncul dalam situasi "buruk". Dalam hal ini, Anda harus menemukan cara untuk mensimulasikan skenario "buruk" dan menulis autotest juga. Jadi, kami memverifikasi bahwa elemen kesalahan muncul dalam kasus negatif, dan dengan demikian kami memantau relevansi locator.
Periksa item
Bagaimana memastikan bahwa interaksi pengujian dengan antarmuka berhasil dan semuanya berfungsi? Ini paling sering terlihat pada perubahan yang terjadi pada antarmuka ini.
Pertimbangkan sebuah contoh. Anda perlu memastikan bahwa ketika mengirim pesan, pesan itu muncul di obrolan:

Scriptnya terlihat seperti ini:
- Buka profil pengguna
- Buka obrolan dengannya
- Tulis pesan
- Kirim
- Tunggu hingga pesan muncul.
Kami menggambarkan skenario seperti itu dalam pengujian kami. Misalkan pesan obrolan cocok dengan locator:
p.message_text
Ini adalah cara kami memverifikasi bahwa elemen muncul:
this.waitForPresence(By.css('p.message_text'), "Cannot find sent message.");
Jika menunggu kami berfungsi, maka semuanya beres: pesan obrolan dibuat.
Seperti yang mungkin sudah Anda duga, setelah beberapa saat, pengiriman pesan obrolan berhenti, tetapi pengujian kami terus berfungsi tanpa gangguan. Mari kita perbaiki.
Ternyata sehari sebelum elemen baru muncul di obrolan: beberapa teks yang meminta pengguna untuk menyorot pesan jika tiba-tiba tidak diperhatikan:

Dan, yang paling lucu, itu juga berada di bawah locator kami. Hanya ia memiliki kelas tambahan yang membedakannya dari pesan yang dikirim:
p.message_text.highlight
Tes kami tidak pecah ketika blok ini muncul, tetapi tanda centang "tunggu sampai pesan muncul" tidak lagi relevan. Elemen yang tadinya merupakan indikator dari acara yang sukses sekarang selalu ada.
Kesimpulan: jika logika tes didasarkan pada memeriksa penampilan beberapa elemen, perlu untuk memeriksa bahwa tidak ada elemen seperti itu sebelum interaksi kita dengan UI.
- Buka profil pengguna
- Buka obrolan dengannya
- Pastikan tidak ada pesan terkirim
- Tulis pesan
- Kirim
- Tunggu hingga pesan muncul.
Data acak
Cukup sering, tes UI berfungsi dengan formulir tempat mereka memasukkan data. Misalnya, kami memiliki formulir pendaftaran:

Data untuk tes tersebut dapat disimpan dalam konfigurasi atau hardcode dalam suatu pengujian. Tetapi kadang-kadang muncul pikiran: mengapa tidak mengacak data? Ini bagus, kami akan membahas lebih banyak kasus!
Saran saya: jangan. Dan sekarang saya akan memberi tahu Anda alasannya.
Misalkan pengujian kami terdaftar di Badoo. Kami memutuskan bahwa kami akan memilih jenis kelamin pengguna secara acak. Pada saat menulis tes, arus pendaftaran untuk anak perempuan dan untuk anak laki-laki tidak berbeda, sehingga ujian kami berhasil.
Sekarang bayangkan bahwa setelah beberapa saat aliran registrasi menjadi berbeda. Misalnya, kami memberikan bonus gratis kepada gadis itu segera setelah pendaftaran, yang kami beri tahu dia dengan hamparan khusus.
Dalam tes, tidak ada logika untuk menutup overlay, tetapi pada gilirannya, mengganggu tindakan lebih lanjut yang ditentukan dalam tes. Kami mendapatkan tes yang masuk dalam 50% kasus. Alat otomatisasi apa pun akan mengonfirmasi bahwa tes UI pada dasarnya tidak stabil secara alami. Dan ini normal, kita harus hidup dengannya, terus-menerus menempel di antara logika berlebih "untuk semua kesempatan" (yang terasa merusak pembacaan kode dan mempersulit dukungannya) dan ketidakstabilan ini sendiri.
Lain kali, ketika tes jatuh, kita mungkin tidak punya waktu untuk menghadapinya. Kami baru saja restart dan melihat bahwa itu telah berlalu. Kami memutuskan bahwa dalam aplikasi kami semuanya berfungsi sebagaimana mestinya dan masalahnya adalah tes yang tidak stabil. Dan tenanglah.
Sekarang mari kita lanjutkan. Bagaimana jika hamparan ini rusak? Tes akan terus lulus dalam 50% kasus, yang secara signifikan menunda menemukan masalah.
Dan ada baiknya ketika, karena pengacakan data, kami menciptakan situasi "50 kali 50". Tetapi itu terjadi secara berbeda. Misalnya, sebelum mendaftar, kata sandi dianggap dapat diterima setidaknya tiga karakter. Kami menulis kode yang muncul dengan kata sandi acak tidak lebih pendek dari tiga karakter (kadang-kadang tiga karakter, dan kadang-kadang lebih). Dan kemudian aturannya berubah - dan kata sandi seharusnya sudah mengandung setidaknya empat karakter. Berapa probabilitas jatuh dalam kasus ini? Dan, jika pengujian kami menemukan bug nyata, seberapa cepat kami akan mengetahuinya?
Terutama sulit untuk bekerja dengan tes di mana banyak data acak dimasukkan: nama, jenis kelamin, kata sandi, dan sebagainya ... Dalam hal ini, ada juga banyak kombinasi yang berbeda, dan jika terjadi kesalahan pada salah satu dari mereka, biasanya sulit untuk diperhatikan.
Kesimpulan Seperti yang saya tulis di atas, mengacak data itu buruk. Lebih baik untuk menutupi lebih banyak kasus dengan mengorbankan penyedia data, tanpa melupakan
kelas kesetaraan , tentu saja. Lulus tes akan memakan waktu lebih lama, tetapi Anda bisa melawannya. Tetapi kami akan yakin bahwa jika ada masalah, itu akan terdeteksi.
Atomicity tes (bagian 1)
Mari kita lihat contoh berikut ini. Kami sedang menulis tes yang memeriksa penghitung pengguna di footer.

Skenarionya sederhana:
- Buka aplikasi
- Temukan penghitung footer
- Pastikan itu terlihat
Kami menyebutnya testFooterCounter tes dan menjalankannya. Maka menjadi perlu untuk memeriksa bahwa penghitung tidak menunjukkan nol. Kami menambahkan tes ini ke tes yang ada, mengapa tidak?
Tetapi kemudian menjadi perlu untuk memverifikasi bahwa di catatan kaki ada tautan ke deskripsi proyek (tautan "Tentang Kami"). Tulis tes baru atau tambahkan yang sudah ada? Dalam hal pengujian baru, kami harus menaikkan kembali aplikasi, menyiapkan pengguna (jika kami memeriksa catatan kaki di halaman yang diotorisasi), masuk - secara umum, habiskan waktu berharga. Dalam situasi seperti itu, mengganti nama tes menjadi testFooterCounterAndLinks sepertinya ide yang bagus.
Di satu sisi, pendekatan ini memiliki kelebihan: menghemat waktu, menyimpan semua cek dari beberapa bagian aplikasi kita (dalam hal ini, footer) di satu tempat.
Tetapi ada minus yang nyata. Jika pengujian gagal pada pengujian pertama, kami tidak akan memeriksa sisa komponen. Misalkan tes mogok di beberapa cabang, bukan karena ketidakstabilan, tetapi karena bug. Apa yang harus dilakukan Mengembalikan tugas yang hanya menjelaskan masalah ini? Kemudian kami menjalankan risiko mendapatkan tugas dengan memperbaiki hanya bug ini, menjalankan tes dan mengetahui bahwa komponen juga rusak lebih lanjut, di tempat lain. Dan mungkin ada banyak iterasi seperti itu. Menendang tiket bolak-balik dalam kasus ini akan memakan banyak waktu dan tidak akan efektif.
Kesimpulan: jika memungkinkan, lakukan atomisasi pada cek. Dalam hal ini, walaupun memiliki masalah dalam satu kasus, kami akan memeriksa yang lainnya. Dan, jika Anda harus mengembalikan tiket, kami dapat segera menggambarkan semua area masalah.
Atomitas pengujian (bagian 2)
Pertimbangkan contoh lain. Kami sedang menulis tes obrolan yang memeriksa logika berikut. Jika pengguna memiliki simpati timbal balik, promoblock berikut ini muncul di obrolan:

Skenarionya adalah sebagai berikut:
- Vote oleh pengguna A untuk pengguna B
- Vote oleh pengguna B untuk pengguna A
- Pengguna Obrolan terbuka dengan pengguna B
- Konfirmasikan bahwa unit ada di tempatnya
Untuk beberapa waktu, tes berhasil, tetapi kemudian terjadi ... Tidak, kali ini tes tidak melewatkan bug apa pun. :)
Setelah beberapa waktu, kami mengetahui bahwa ada bug lain yang tidak terkait dengan pengujian kami: jika Anda membuka obrolan, segera tutup dan buka lagi, blok tersebut hilang. Bukan kasus yang paling jelas, dan dalam ujian kami, tentu saja, tidak memperkirakannya. Tetapi kami memutuskan bahwa kami perlu menutupinya juga.
Pertanyaan yang sama muncul: tulis tes lain atau masukkan tes ke yang sudah ada? Menulis yang baru tampaknya tidak pantas, karena 99% dari waktu dia akan melakukan hal yang sama dengan yang sudah ada. Dan kami memutuskan untuk menambahkan tes ke tes yang sudah ada:
- Vote oleh pengguna A untuk pengguna B
- Vote oleh pengguna B untuk pengguna A
- Pengguna Obrolan terbuka dengan pengguna B
- Konfirmasikan bahwa unit ada di tempatnya
- Tutup obrolan
- Buka obrolan
- Konfirmasikan bahwa unit ada di tempatnya
Masalah dapat muncul ketika, misalnya, kami melakukan refactor setelah beberapa lama. Misalnya, desain ulang akan terjadi pada proyek - dan Anda harus menulis ulang banyak tes.
Kami akan membuka tes dan mencoba mengingat apa yang diperiksa. Misalnya, tes disebut testPromoAfterMutualAttraction. Apakah kita mengerti mengapa pembukaan dan penutupan obrolan ditulis di akhir? Kemungkinan besar tidak. Apalagi jika tes ini tidak ditulis oleh kami. Akankah kita meninggalkan bagian ini? Mungkin ya, tetapi jika ada masalah dengannya, kemungkinan kami hanya akan menghapusnya. Dan verifikasi akan hilang hanya karena artinya tidak akan jelas.
Saya melihat dua solusi di sini. Pertama: masih melakukan tes kedua dan menyebutnya testCheckBlockPresentAfterOpenAndCloseChat. Dengan nama seperti itu, akan menjadi jelas bahwa kita tidak hanya melakukan serangkaian tindakan tertentu, tetapi melakukan pemeriksaan yang sangat sadar, karena ada pengalaman negatif. Solusi kedua adalah menulis komentar terperinci dalam kode tentang mengapa kami melakukan tes ini dalam tes khusus ini. Disarankan juga untuk menunjukkan nomor bug dalam komentar.
Kesalahan mengklik item yang ada
Contoh berikut melemparkan
bbidox kepada saya, yang mana ia merupakan nilai tambah besar dalam karma!
Ada situasi yang sangat menarik ketika kode uji menjadi ... sebuah kerangka kerja. Misalkan kita memiliki metode seperti ini:
public void clickSomeButton() { WebElement button_element = this.waitForButtonToAppear(); button_element.click(); }
Pada titik tertentu, sesuatu yang aneh mulai terjadi pada metode ini: tes macet ketika Anda mencoba mengklik tombol. Kami membuka tangkapan layar yang diambil pada saat tes mogok, dan kami melihat bahwa ada tombol di tangkapan layar dan metode waitForButtonToAppear bekerja dengan sukses. Pertanyaan: apa yang salah dengan klik?
Bagian tersulit dalam situasi ini adalah bahwa tes kadang-kadang bisa berhasil. :)
Mari kita perbaiki. Misalkan tombol yang dipertimbangkan dalam contoh terletak pada hamparan seperti itu:

Ini adalah hamparan khusus di mana pengguna di situs web kami dapat mengisi informasi tentang dirinya. Ketika Anda mengklik tombol overlay yang disorot, blok berikutnya muncul untuk mengisi.
Untuk bersenang-senang, mari tambahkan kelas OLOLO tambahan untuk tombol ini:

Setelah itu kita klik tombol ini. Secara visual, tidak ada yang berubah, tetapi tombol itu sendiri tetap ada:

Apa yang terjadi Faktanya, ketika JS menggambar ulang blok untuk kami, ia menggambar ulang tombol juga. Itu masih tersedia di locator yang sama, tetapi ini adalah tombol lain. Ini dibuktikan dengan kurangnya kelas OLOLO yang kami tambahkan.
Dalam kode di atas, kami menyimpan elemen dalam variabel $ element. Jika sebuah elemen dibuat ulang selama waktu ini, mungkin tidak terlihat secara visual, tetapi Anda tidak dapat mengkliknya lagi - metode klik () akan gagal.
Ada beberapa solusi:
- Bungkus klik di blok coba dan dalam elemen membangun kembali
- Tambahkan tombol ke atribut untuk memberi sinyal bahwa itu telah berubah
Teks salah
Akhirnya, poin yang sederhana, tetapi tidak kalah pentingnya.
Contoh ini tidak hanya berlaku untuk tes UI, tetapi juga sangat sering terjadi di dalamnya. Biasanya, ketika Anda menulis tes, Anda berada dalam konteks apa yang terjadi: Anda menggambarkan verifikasi setelah verifikasi dan memahami artinya. Dan Anda menulis teks kesalahan dalam konteks yang sama:
WebElement element = this.waitForPresence(By.css("a.link"), "Cannot find button");
Apa yang mungkin tidak bisa dipahami dalam kode ini? Tes mengharapkan tampilan tombol dan, jika tidak ada di sana, secara alami jatuh.
Sekarang bayangkan penulis tes cuti sakit, dan rekannya sedang mengurus tes. Dan kemudian dia menjatuhkan tes testQuestionsOnProfile dan menulis pesan ini: "Tidak dapat menemukan tombol". Seorang kolega perlu memahami apa yang terjadi secepat mungkin, karena rilis akan segera datang.
Apa yang harus dia lakukan?
Tidak ada gunanya membuka halaman tempat tes jatuh dan memeriksa "a.link" locator - tidak ada elemen. Karena itu, Anda harus mempelajari tes dengan cermat dan mencari tahu apa yang diperiksa.
Akan jauh lebih sederhana dengan teks kesalahan yang lebih terperinci: "Tidak dapat menemukan tombol kirim pada hamparan pertanyaan". Dengan kesalahan seperti itu, Anda dapat segera membuka overlay dan melihat ke mana tombolnya.
Keluaran dua. Pertama, perlu mengirimkan teks kesalahan ke metode kerangka kerja pengujian Anda apa pun, dan ini merupakan parameter yang diperlukan sehingga tidak ada godaan untuk melupakannya. Kedua, teks kesalahan harus dibuat rinci. Ini tidak selalu berarti bahwa itu harus panjang, cukup untuk membuatnya jelas tentang apa yang salah dalam tes.
Bagaimana memahami bahwa teks kesalahan ditulis dengan baik? Sangat sederhana. Bayangkan aplikasi Anda rusak dan Anda harus pergi ke pengembang dan menjelaskan apa dan di mana rusak. Jika Anda memberi tahu mereka hanya apa yang tertulis dalam teks kesalahan, apakah mereka akan mengerti?
Ringkasan
Menulis skrip ujian seringkali merupakan kegiatan yang menarik. Pada saat yang sama, kami mengejar banyak tujuan. Tes kami harus:
- tutupi sebanyak mungkin kasus
- bekerja secepat mungkin
- untuk dipahami
- perluas saja
- mudah dirawat
- memesan pizza
- dan seterusnya ...
Sangat menarik untuk bekerja dengan tes dalam proyek yang terus berkembang dan berubah, di mana mereka harus terus diperbarui: menambahkan sesuatu dan memotong sesuatu. Itulah sebabnya penting untuk memikirkan beberapa poin sebelumnya dan tidak selalu terburu-buru dengan keputusan. :)
Saya harap tips saya akan membantu Anda menghindari beberapa masalah dan membuat Anda lebih bijaksana dalam studi kasus. Jika audiens menyukai artikel ini, saya akan mencoba mengumpulkan beberapa contoh yang lebih membosankan. Sementara itu - sampai jumpa!