Skenario - Bukan Objek Halaman

Seiring waktu, membuat perubahan pada produk apa pun menjadi lebih sulit, dan ada risiko yang semakin besar tidak hanya merilis fitur baru, tetapi juga merusak yang lama. Seringkali, alih-alih memeriksa keseluruhan proyek secara manual, mereka mencoba mengotomatiskan proses ini. Jika Anda berbicara dengan orang-orang yang menguji antarmuka dan berkeliling konferensi, menjadi jelas bahwa Selenium aturan dalam dunia pengujian web, dan sebagian besar menggunakan Page Object sebagai organisasi kode.


Tetapi bagi saya, sebagai seorang programmer, untuk beberapa alasan saya tidak pernah menyukai pola dan kode ini yang saya lihat dengan tim yang berbeda - huruf-huruf SOLID terdengar di kepala saya. Tetapi saya siap menerima kenyataan bahwa penguji menulis kode sesuka mereka, karena kurangnya alternatif, seperti sekitar setahun yang lalu, di Angular Connect, saya mendengar laporan tentang pengujian aplikasi Angular menggunakan pola Skenario. Sekarang saya ingin berbagi.



Kelinci percobaan


Pertama, deskripsi singkat tentang alat yang digunakan. Sebagai contoh implementasi, saya akan mengambil SerenityJS dari laporan asli, dengan TypeScript sebagai bahasa skrip pengujian.


Kami akan melakukan eksperimen pada aplikasi TODO - aplikasi untuk membuat daftar tugas sederhana. Sebagai contoh, kita akan menggunakan kode dari Jan Molak , pencipta perpustakaan (kode TypeScript, jadi highlightnya sedikit).


Mari kita mulai tahun 2009


Pada saat ini, Selenium WebDriver muncul, dan orang-orang mulai menggunakannya. Sayangnya, dalam banyak kasus, karena kebaruan teknologi dan kurangnya pengalaman pemrograman, itu salah. Tes ternyata menjadi salin-tempel penuh, tidak didukung, dan "rapuh".


Hasilnya adalah umpan balik negatif dan reputasi buruk dengan autotest. Pada akhirnya, Simon Stewart, pencipta Selenium WebDriver, merespons in absentia dalam artikelnya Tes Selenium Saya Tidak Stabil! . Pesan umum: "jika tes Anda rapuh dan tidak berfungsi dengan baik - ini bukan karena Selenium, tetapi karena tes itu sendiri tidak terlalu ditulis."


Untuk memahami apa yang dipertaruhkan, mari kita lihat skenario pengguna berikut ini:


Feature: Add new items to the todo list In order to avoid having to remember things that need doing As a forgetful person I want to be able to record what I need to do in a place where I won't forget about them Scenario: Adding an item to a list with other items Given that James has a todo list containing Buy some cookies, Walk the dog When he adds Buy some cereal to his list Then his todo list should contain Buy some cookies, Walk the dog, Buy some cereal 

Implementasi naif "pada dahi" akan terlihat seperti ini:


 import { browser, by, element, protractor } from 'protractor'; export = function todoUserSteps() { this.Given(/^.*that (.*) has a todo list containing (.*)$/, (name: string, items: string, callback: Function) => { browser.get('http://todomvc.com/examples/angularjs/'); browser.driver.manage().window().maximize(); listOf(items).forEach(item => { element(by.id('new-todo')).sendKeys(item, protractor.Key.ENTER); }); browser.driver.controlFlow().execute(callback); }); this.When(/^s?he adds (.*?) to (?:his|her) list$/, (itemName: string, callback: Function) => { element(by.id('new-todo')) .sendKeys(itemName, protractor.Key.ENTER) .then(callback); }); this.Then(/^.* todo list should contain (.*?)$/, (items: string, callback: Function) => { expect(element.all(by.repeater('todo in todos')).getText()) .to.eventually.eql(listOf(items)) .and.notify(callback); }); }; 

Seperti yang Anda lihat, API tingkat rendah digunakan di sini, manipulasi dengan DOM, salin-tempel pemilih css. Jelas bahwa di masa depan, bahkan dengan perubahan kecil di UI, Anda harus mengubah kode di banyak tempat.


Sebagai solusi, Selenium harus menawarkan sesuatu yang cukup baik untuk menyingkirkan masalah seperti itu, dan pada saat yang sama dapat diakses oleh orang-orang dengan sedikit atau tanpa pengalaman dalam pemrograman berorientasi objek. Solusi semacam itu adalah Page Object, sebuah pola untuk mengatur kode.


Martin Fowler menggambarkannya sebagai objek abstraksi di sekitar halaman HTML atau bagiannya, yang memungkinkan Anda untuk berinteraksi dengan elemen halaman tanpa menyentuh HTML itu sendiri. Idealnya, objek semacam itu harus memungkinkan kode klien untuk melakukan dan melihat segala sesuatu yang dapat dilakukan dan dilihat seseorang.


Setelah menulis ulang contoh kami sesuai dengan pola ini, kami memiliki yang berikut:


 import { browser, by, element, protractor } from 'protractor'; class TodoList { What_Needs_To_Be_Done = element(by.id('new-todo')); Items = element.all(by.repeater('todo in todos')); addATodoItemCalled(itemName: string): PromiseLike<void> { return this.What_Needs_To_Be_Done.sendKeys(itemName, protractor.Key.ENTER); } displayedItems(): PromiseLike<string[]> { return this.Items.getText(); } } export = function todoUserSteps() { let todoList = new TodoList(); this.Given(/^.*that (.*) has a todo list containing (.*)$/, (name: string, items: string, callback: Function) => { browser.get('http://todomvc.com/examples/angularjs/'); browser.driver.manage().window().maximize(); listOf(items).forEach(item => { todoList.addATodoItemCalled(item); }); browser.driver.controlFlow().execute(callback); }); this.When(/^s?he adds (.*?) to (?:his|her) list$/, (itemName: string, callback: Function) => { todoList.addATodoItemCalled(itemName).then(() => callback()); }); this.Then(/^.* todo list should contain (.*?)$/, (items: string, callback: Function) => { expect(todoList.displayedItems()) .to.eventually.eql(listOf(items)) .and.notify(callback); }); }; 

Sepintas, terlihat bagus - kami menyingkirkan duplikasi dan menambahkan enkapsulasi. Tetapi ada satu masalah: sangat sering kelas-kelas seperti itu dalam proses pengembangan tumbuh menjadi ukuran yang sangat besar .



Anda dapat mencoba menguraikan kode ini menjadi komponen independen, tetapi jika kode mencapai keadaan seperti itu, itu sudah sangat erat digabungkan sehingga bisa sangat sulit dan akan membutuhkan penulisan ulang baik kelas itu sendiri dan seluruh kode klien (kasus uji) yang menggunakan ini objek. Sebenarnya, kelas besar bukanlah penyakit, tetapi gejala ( bau kode ). Masalah utama adalah pelanggaran terhadap Prinsip Tanggung Jawab Tunggal dan Prinsip Tertutup Terbuka . Objek Halaman menyiratkan deskripsi halaman dan semua cara berinteraksi dengannya dalam satu entitas, yang tidak dapat diperluas tanpa mengubah kodenya.


Untuk aplikasi tanggung jawab kelas TODO kami adalah sebagai berikut (gambar dari artikel ):
Tanggung jawab


Jika Anda mencoba memisahkan kelas ini berdasarkan prinsip-prinsip SOLID, Anda mendapatkan sesuatu seperti berikut:


Model domain


Masalah berikutnya yang kita miliki dengan Obyek Halaman adalah bahwa ia beroperasi pada halaman. Dalam pengujian penerimaan apa pun, adalah umum untuk menggunakan cerita pengguna. Behaviour Driven Development (BDD) dan bahasa Gherkin sangat cocok dengan model ini. Dipahami bahwa jalur pengguna ke sasaran (skenario) lebih penting daripada implementasi spesifik. Misalnya, jika Anda menggunakan widget login di semua tes, saat mengubah metode login (formulir telah pindah, beralih ke Sistem Masuk Tunggal), Anda harus mengubah semua tes. Dalam proyek-proyek yang sangat dinamis, ini bisa menjadi mahal dan memakan waktu dan mendorong tim untuk menunda tes menulis sampai halaman stabil (bahkan jika semuanya jelas dengan use case).


Untuk mengatasi masalah ini, ada baiknya melihat dari sisi lain. Kami memperkenalkan konsep-konsep berikut:


  1. Peran - untuk siapa ini semua?
  2. Tujuan - mengapa mereka (pengguna) ada di sini dan apa yang ingin mereka capai?
  3. Tugas - Apa yang perlu mereka lakukan untuk mencapai tujuan ini?
  4. Tindakan - bagaimana tepatnya pengguna harus berinteraksi dengan halaman untuk menyelesaikan tugas?

Dengan demikian, setiap skrip uji menjadi, secara de facto, skrip untuk pengguna, yang ditujukan untuk pelaksanaan cerita pengguna tertentu. Jika Anda menggabungkan pendekatan ini dengan prinsip-prinsip pemrograman berorientasi objek yang dijelaskan di atas, hasilnya akan menjadi pola Screenplay (atau Perjalanan Pengguna ). Ide dan prinsipnya pertama kali disuarakan pada tahun 2007, bahkan sebelum PageObject.


Skenario


Kami menulis ulang kode kami menggunakan prinsip-prinsip yang dihasilkan.


James adalah aktor yang akan memainkan pengguna kami dalam skenario ini. Dia tahu cara menggunakan browser.


 let james = Actor.named('James').whoCan(BrowseTheWeb.using(protractor.browser)); 

Tujuan James adalah untuk menambahkan item pertama ke daftar:


  Scenario: Adding an item to a list with other items 

Untuk mewujudkan tujuan ini, James membutuhkan yang berikut:


  1. mulai dengan daftar yang berisi beberapa item,
  2. tambahkan item baru.

Kita dapat memecah ini menjadi dua kelas:


 import { PerformsTasks, Task } from 'serenity-js/lib/screenplay'; import { Open } from 'serenity-js/lib/screenplay-protractor'; import { AddATodoItem } from './add_a_todo_item'; export class Start implements Task { static withATodoListContaining(items: string[]) { return new Start(items); } performAs(actor: PerformsTasks) { return actor.attemptsTo( Open.browserOn('/examples/angularjs/'), ...this.addAll(this.items) ); } constructor(private items: string[]) { } private addAll(items: string[]): Task[] { return items.map(item => AddATodoItem.called(item)); } } 

Antarmuka Task memerlukan mendefinisikan metode performAs , di mana aktor akan ditransfer ke selama eksekusi. attemptsTo - fungsi kombinator, menerima sejumlah pengocokan. Dengan demikian, Anda dapat membangun berbagai urutan. Faktanya, semua yang dilakukan tugas ini adalah membuka browser di halaman yang diinginkan dan menambahkan elemen di sana. Sekarang mari kita lihat tugas menambahkan elemen:


 import { PerformsTasks, Task } from 'serenity-js/lib/screenplay'; import { Enter } from 'serenity-js/lib/screenplay-protractor'; import { protractor } from 'protractor'; import { TodoList } from '../components/todo_list'; export class AddATodoItem implements Task { static called(itemName: string) { return new AddATodoItem(itemName); } // required by the Task interface performAs(actor: PerformsTasks): PromiseLike<void> { // delegates the work to lower-level tasks return actor.attemptsTo( Enter.theValue(this.itemName) .into(TodoList.What_Needs_To_Be_Done) .thenHit(protractor.Key.ENTER) ); } constructor(private itemName: string) } } 

Lebih menarik di sini, tindakan tingkat rendah muncul - masukkan teks di elemen halaman dan tekan Enter. TodoList adalah semua yang tersisa dari bagian "deskriptif" dari Obyek Halaman, di sini kita memiliki penyeleksi css:


 import { Target, Question, Text } from 'serenity-js/lib/screenplay-protractor'; import { by } from 'protractor'; export class TodoList { static What_Needs_To_Be_Done = Target .the('"What needs to be done?" input box') .located(by.id('new-todo')); static Items = Target .the('List of Items') .located(by.repeater('todo in todos')); static Items_Displayed = Text.ofAll(TodoList.Items); } 

Ok, tetap memverifikasi bahwa setelah semua manipulasi informasi yang benar ditampilkan. SerenityJS menawarkan antarmuka Question<T> - entitas yang mengembalikan nilai yang ditampilkan atau daftar nilai ( Text.ofAll dalam contoh di atas). Bagaimana seseorang dapat mengimplementasikan pertanyaan yang mengembalikan teks elemen HTML:


 export class Text implements Question<string> { public static of(target: Target): Text { return new Text(target); } answeredBy(actor: UsesAbilities): PromiseLike<string[]> { return BrowseTheWeb.as(actor).locate(this.target).getText(); } constructor(private target: Target) { } } 

Yang penting, pengikatan browser bersifat opsional. BrowseTheWeb hanyalah Kemampuan , yang memungkinkan Anda berinteraksi dengan browser. Anda dapat menerapkan, misalnya, kemampuan RecieveEmails , yang memungkinkan aktor membaca surat (untuk pendaftaran di situs).


Menyatukan semuanya, kami mendapatkan skema ini (dari Jan Molak ):


dan skenario berikut:


 let actor: Actor; this.Given(/^.*that (.*) has a todo list containing (.*)$/, function (name: string, items: string) { actor = Actor.named(name).whoCan(BrowseTheWeb.using(protractor.browser)); return actor.attemptsTo( Start.withATodoListContaining(listOf(items)) ); }); this.When(/^s?he adds (.*?) to (?:his|her) list$/, function (itemName: string) { return actor.attemptsTo( AddATodoItem.called(itemName) ) }); this.Then(/^.* todo list should contain (.*?)$/, function (items: string) { return expect(actor.toSee(TodoList.Items_Displayed)).eventually.deep.equal(listOf(items)) }); 

Pada awalnya, hasilnya terlihat agak masif, tetapi ketika sistem tumbuh, jumlah kode yang digunakan kembali akan meningkat, dan perubahan tata letak dan halaman hanya akan mempengaruhi penyeleksi css dan tugas / pertanyaan tingkat rendah, membuat skrip dan tugas tingkat tinggi praktis tidak berubah.


Implementasi


Jika kita berbicara tentang perpustakaan, dan bukan tentang upaya kota kecil untuk menerapkan pola ini, yang paling populer adalah Serenity BDD untuk Jawa. Javascript / TypeScript menggunakan port SerenityJS sendiri. Di luar kotak, dia tahu cara bekerja dengan Mentimun dan Mocha.


Pencarian cepat juga menghasilkan perpustakaan untuk .NET - tranquire . Saya tidak bisa mengatakan apa-apa tentang dia, karena saya belum pernah bertemu dengannya sebelumnya.


Saat menggunakan Serenity dan SerenityJS, Anda dapat menggunakan utilitas pembuatan laporan .


Laporkan dengan gambar

Ambil kodenya menggunakan Mocha:


 describe('Finding things to do', () => { describe('James can', () => { describe('remove filters so that the list', () => { it('shows all the items', () => Actor.named('James').attemptsTo( Start.withATodoListContaining([ 'Write some code', 'Walk the dog' ]), CompleteATodoItem.called('Write some code'), FilterItems.toShowOnly('Active'), FilterItems.toShowOnly('All'), Ensure.theListOnlyContains('Write some code', 'Walk the dog'), )); }); }); }); 

Laporan akan berisi statistik umum:


dan uraian langkah-langkah dengan tangkapan layar:


Referensi


Beberapa artikel terkait:


  1. Objek Halaman Refactored: Langkah SOLID ke Skenario Skenario / Perjalanan
  2. Melampaui Objek Halaman: Otomasi Uji Generasi Selanjutnya dengan Ketenangan dan Pola Skenario
  3. Serenity BDD dan Pola Skenario

Makalah:





Perpustakaan:


  1. Serenity BDD - Java
  2. Serenity JS - JavaScript / TypeScript
  3. tranquire - .NET

Silakan tulis di komentar jika Anda menggunakan pola / cara lain untuk mengatur kode autotest.

Source: https://habr.com/ru/post/id434122/


All Articles