Mungkin arsitektur terbaik untuk pengujian UI



Mungkin, di suatu tempat ada artikel ideal yang secara langsung dan lengkap mengungkapkan topik arsitektur pengujian, mudah ditulis, dibaca, dan dipelihara, sehingga dapat dimengerti oleh pemula, dengan contoh area penerapan dan aplikasi. Saya ingin menawarkan visi saya tentang "artikel ideal" ini dalam format yang saya impikan, hanya setelah menerima tugas pertama "menulis autotests". Untuk melakukan ini, saya akan berbicara tentang pendekatan yang terkenal dan tidak begitu terkenal untuk tes otomatis web, mengapa, bagaimana dan kapan menggunakannya, serta tentang solusi yang berhasil untuk menyimpan dan membuat data.



Halo, Habr! Nama saya Diana, saya adalah kepala grup pengujian antarmuka pengguna, dan saya telah mengotomatiskan pengujian web dan desktop selama lima tahun. Contoh kode akan ada di java dan untuk web, tetapi, dalam praktiknya, telah diuji, pendekatannya dapat diterapkan ke python dengan desktop.



Pada awalnya itu ...



Pada awalnya ada sebuah kata, dan ada banyak kata, dan kata-kata itu memenuhi semua halaman secara merata dengan kode, terlepas dari arsitektur dan prinsip KERING Anda (jangan ulangi sendiri - tidak perlu mengulangi kode yang sudah Anda tulis tiga paragraf di atas).



Lembar



Faktanya, arsitektur "footcloth", alias "sheet", alias kode tidak terstruktur yang ditumpuk di tumpukan yang mengisi layar secara merata, tidak terlalu buruk dan cukup dapat diterapkan dalam situasi berikut:



  • Klik cepat dalam tiga baris (oke, dua ratus tiga) untuk proyek yang sangat kecil;
  • Untuk contoh kode di demo mini;
  • Untuk kode pertama dalam gaya "Hello Word" di antara tes otomatis.


Apa yang perlu Anda lakukan untuk mendapatkan arsitektur Sprei? cukup tulis semua kode yang diperlukan ke dalam satu file, kanvas umum.



import com.codeborne.selenide.Condition;
import com.codeborne.selenide.WebDriverRunner;
import org.testng.annotations.Test;

import static com.codeborne.selenide.Selenide.*;

public class RandomSheetTests {
    @Test
    void addUser() {
        open("https://ui-app-for-autotest.herokuapp.com/");
        $("#loginEmail").sendKeys("test@protei.ru");
        $("#loginPassword").sendKeys("test");
        $("#authButton").click();
        $("#menuMain").shouldBe(Condition.appear);

        $("#menuUsersOpener").hover();
        $("#menuUserAdd").click();

        $("#dataEmail").sendKeys("mail@mail.ru");
        $("#dataPassword").sendKeys("testPassword");
        $("#dataName").sendKeys("testUser");
        $("#dataGender").selectOptionContainingText("");
        $("#dataSelect12").click();
        $("#dataSelect21").click();
        $("#dataSelect22").click();
        $("#dataSend").click();

        $(".uk-modal-body").shouldHave(Condition.text(" ."));

        WebDriverRunner.closeWebDriver();
    }
}


Jika Anda baru mulai mengenal autotest, maka "sheet" tersebut sudah cukup untuk menyelesaikan tugas tes sederhana, terutama jika Anda menunjukkan pengetahuan yang baik tentang desain tes dan cakupan yang baik. Tetapi ini terlalu mudah untuk proyek berskala besar, jadi jika Anda memiliki ambisi, tetapi tidak punya waktu untuk mengeksekusi setiap kasus uji secara ideal, maka setidaknya gita Anda harus memiliki contoh arsitektur yang lebih kompleks.



PageObject



Pernah mendengar rumor bahwa PageObject tidak digunakan lagi? Anda hanya tidak tahu cara memasaknya!



Unit kerja utama dalam pola ini adalah "halaman", yaitu, satu set lengkap elemen dan tindakan dengan mereka, misalnya, MenuPage - kelas yang mendeskripsikan semua tindakan dengan menu, yaitu, klik pada tab, memperluas item drop-down, dan sebagainya.







Sedikit lebih sulit untuk membuat PageObject untuk jendela modal (singkatnya "modal") dari pembuatan objek. Kumpulan bidang kelas jelas: semua bidang masukan, kotak centang, daftar drop-down; dan untuk metode ada dua opsi: Anda dapat membuat kedua metode universal "mengisi semua bidang modal", "mengisi semua bidang modal dengan nilai acak", "memeriksa semua bidang modalk", dan memisahkan metode "isi nama", "periksa nama", "Isi deskripsi" dan seterusnya. Apa yang digunakan dalam kasus tertentu ditentukan oleh prioritas - pendekatan "satu metode untuk seluruh modal" meningkatkan kecepatan menulis tes, tetapi dibandingkan dengan pendekatan "satu metode untuk setiap bidang", pendekatan ini kehilangan banyak keterbacaan tes.



Contoh
Mari buat Objek Halaman umum untuk membuat pengguna untuk kedua jenis pengujian:

public class UsersPage {

    @FindBy(how = How.ID, using = "dataEmail")
    private SelenideElement email;
    @FindBy(how = How.ID, using = "dataPassword")
    private SelenideElement password;
    @FindBy(how = How.ID, using = "dataName")
    private SelenideElement name;
    @FindBy(how = How.ID, using = "dataGender")
    private SelenideElement gender;
    @FindBy(how = How.ID, using = "dataSelect11")
    private SelenideElement var11;
    @FindBy(how = How.ID, using = "dataSelect12")
    private SelenideElement var12;
    @FindBy(how = How.ID, using = "dataSelect21")
    private SelenideElement var21;
    @FindBy(how = How.ID, using = "dataSelect22")
    private SelenideElement var22;
    @FindBy(how = How.ID, using = "dataSelect23")
    private SelenideElement var23;
    @FindBy(how = How.ID, using = "dataSend")
    private SelenideElement save;

    @Step("Complex add user")
    public UsersPage complexAddUser(String userMail, String userPassword, String userName, String userGender, 
                                    boolean v11, boolean v12, boolean v21, boolean v22, boolean v23) {
        email.sendKeys(userMail);
        password.sendKeys(userPassword);
        name.sendKeys(userName);
        gender.selectOption(userGender);
        set(var11, v11);
        set(var12, v12);
        set(var21, v21);
        set(var22, v22);
        set(var23, v23);
        save.click();
        return this;
    }

    @Step("Fill user Email")
    public UsersPage sendKeysEmail(String text) {...}

    @Step("Fill user Password")
    public UsersPage sendKeysPassword(String text) {...}

    @Step("Fill user Name")
    public UsersPage sendKeysName(String text) {...}

    @Step("Select user Gender")
    public UsersPage selectGender(String text) {...}

    @Step("Select user variant 1.1")
    public UsersPage selectVar11(boolean flag) {...}

    @Step("Select user variant 1.2")
    public UsersPage selectVar12(boolean flag) {...}

    @Step("Select user variant 2.1")
    public UsersPage selectVar21(boolean flag) {...}

    @Step("Select user variant 2.2")
    public UsersPage selectVar22(boolean flag) {...}

    @Step("Select user variant 2.3")
    public UsersPage selectVar23(boolean flag) {...}

    @Step("Click save")
    public UsersPage clickSave() {...}

    private void set(SelenideElement checkbox, boolean flag) {
        if (flag) {
            if (!checkbox.isSelected()) checkbox.click();
        } else {
            if (checkbox.isSelected()) checkbox.click();
        }
    }
}


:



    @Test
    void addUser() {
        baseRouter.authPage()
                .complexLogin("test@protei.ru", "test")
                .complexOpenAddUser()
                .complexAddUser("mail@test.ru", "pswrd", "TESTNAME", "", true, false, true, true, true)
                .checkAndCloseSuccessfulAlert();
    }


:



    @Test
    void addUserWithoutComplex() {
        //Arrange
        baseRouter.authPage()
                .complexLogin("test@protei.ru", "test");
        //Act
        baseRouter.mainPage()
                .hoverUsersOpener()
                .clickAddUserMenu();
        baseRouter.usersPage()
                .sendKeysEmail("mail@test.ru")
                .sendKeysPassword("pswrd")
                .sendKeysName("TESTNAME")
                .selectGender("")
                .selectVar11(true)
                .selectVar12(false)
                .selectVar21(true)
                .selectVar22(true)
                .selectVar23(true)
                .clickSave();
        //Assert
        baseRouter.usersPage()
                .checkTextSavePopup(" .")
                .closeSavePopup();
    }


. : , , , , — . , , , .



Intinya adalah bahwa semua tindakan dengan halaman dienkapsulasi di dalam halaman (implementasinya tersembunyi, hanya tindakan logis yang tersedia), dengan demikian, fungsi bisnis sudah digunakan dalam pengujian. Dan ini, pada gilirannya, memungkinkan Anda untuk menulis halaman Anda sendiri untuk setiap platform (web, desktop, ponsel), tanpa mengubah pengujian.



Satu-satunya hal yang disayangkan adalah antarmuka yang benar-benar identik jarang ditemukan pada platform yang berbeda.



Untuk mengurangi perbedaan antara antarmuka, ada godaan untuk memperumit langkah-langkah individu, mereka dibawa ke dalam kelas-kelas menengah yang terpisah, dan pengujian menjadi semakin tidak dapat dibaca, hingga dua langkah: "masuk, lakukan dengan baik", pengujian selesai. Selain web, tidak ada antarmuka tambahan dalam proyek kami, dan kami harus membaca kasus lebih sering daripada menulis, oleh karena itu, demi keterbacaan, PageObject historis telah memperoleh tampilan baru.



PageObject adalah karya klasik yang diketahui semua orang. Anda dapat menemukan banyak artikel tentang pendekatan ini dengan contoh-contoh di hampir semua bahasa pemrograman. Penggunaan PageObject sangat sering digunakan untuk menilai apakah seorang kandidat mengetahui sesuatu tentang pengujian antarmuka pengguna. Melakukan tugas pengujian menggunakan pendekatan ini adalah apa yang diharapkan sebagian besar pemberi kerja, dan sebagian besar tetap berjalan dalam proyek produksi, meskipun hanya web yang menguji.



Apa lagi yang terjadi?



Anehnya, tidak ada satu pun PageObject!



  • Pola ScreenPlay sering dijumpai, yang dapat Anda baca, misalnya, di sini . Itu tidak berakar di negara kita, karena menggunakan pendekatan bdd tanpa melibatkan orang yang tidak bisa membaca kode adalah kekerasan yang tidak masuk akal terhadap para pembuatnya.
  • js- , PageObject, - , , .
  • - , , ModelBaseTesting, . , .


Dan saya akan memberi tahu Anda lebih detail tentang Elemen Halaman, yang memungkinkan Anda mengurangi jumlah jenis kode yang sama, sekaligus meningkatkan keterbacaan dan memberikan pemahaman cepat tentang pengujian, bahkan bagi mereka yang tidak terbiasa dengan proyek tersebut. Dan di atasnya (dengan blackjack dan preferensinya sendiri, tentu saja!) Framework non-js populer htmlElements, Atlas dan Epam's JDI dibuat.



Apa itu Elemen Halaman?



Untuk membuat pola Elemen Halaman, mari kita mulai dengan elemen level terendah. Seperti yang dikatakan Wiktionary , "widget" adalah perangkat lunak primitif dari antarmuka pengguna grafis yang memiliki tampilan standar dan melakukan tindakan standar. Misalnya, widget "Tombol" paling sederhana - Anda dapat mengkliknya, Anda dapat memeriksa teks dan warna. Di "bidang Input", Anda dapat memasukkan teks, memeriksa teks apa yang dimasukkan, klik, periksa tampilan fokus, periksa jumlah karakter yang dimasukkan, masukkan teks dan tekan "Enter", periksa placeholder, periksa penyorotan bidang "wajib" dan teks kesalahan, dan hanya itu, apa lagi yang mungkin dibutuhkan dalam kasus tertentu. Selain itu, semua tindakan dengan bidang ini adalah standar di halaman mana pun.







Ada widget yang lebih kompleks yang tindakannya tidak begitu jelas, misalnya, daftar isi hierarki. Saat menulisnya, Anda perlu mengembangkan apa yang dilakukan pengguna dengan area program ini, misalnya:



  • Klik pada elemen daftar isi dengan teks yang ditentukan,
  • Memeriksa keberadaan elemen dengan teks yang diberikan,
  • Memeriksa indentasi elemen dengan teks tertentu.


Widget dapat terdiri dari dua jenis: dengan locator di konstruktor dan dengan locator yang dijahit ke widget tanpa kemampuan untuk mengubahnya. Daftar isi biasanya satu di halaman, metode pencariannya di halaman dapat dibiarkan "di dalam" tindakan dengan daftar isi, tidak masuk akal untuk mengeluarkan lokatornya secara terpisah, karena pencari lokasi bisa rusak secara tidak sengaja dari luar, dan tidak ada manfaat dari menyimpannya secara terpisah. Pada gilirannya, kolom teks adalah hal yang universal, sebaliknya, Anda harus bekerja dengan itu hanya melalui locator dari konstruktor, karena akan ada banyak masukan bidang sekaligus. Jika setidaknya satu metode muncul yang ditujukan hanya untuk satu bidang masukan khusus, misalnya, dengan klik tambahan pada petunjuk drop-down, ini bukan lagi hanya bidang masukan, sekarang saatnya membuat widget Anda sendiri untuk itu.



Untuk mengurangi kekacauan secara keseluruhan, widget, seperti elemen halaman, digabungkan ke dalam halaman yang sama, dari mana, tampaknya, nama Elemen Halaman dibuat.



public class UsersPage {

    public Table usersTable = new Table();

    public InputLine email = new InputLine(By.id("dataEmail"));
    public InputLine password = new InputLine(By.id("dataPassword"));
    public InputLine name = new InputLine(By.id("dataName"));
    public DropdownList gender = new DropdownList(By.id("dataGender"));
    public Checkbox var11 = new Checkbox(By.id("dataSelect11"));
    public Checkbox var12 = new Checkbox(By.id("dataSelect12"));
    public Checkbox var21 = new Checkbox(By.id("dataSelect21"));
    public Checkbox var22 = new Checkbox(By.id("dataSelect22"));
    public Checkbox var23 = new Checkbox(By.id("dataSelect23"));
    public Button save = new Button(By.id("dataSend"));

    public ErrorPopup errorPopup = new ErrorPopup();
    public ModalPopup savePopup = new ModalPopup();
}


Untuk menggunakan semua hal di atas yang dibuat dalam pengujian, Anda perlu merujuk ke halaman, widget, tindakan secara berurutan, sehingga kita mendapatkan konstruksi berikut:



    @Test
    public void authAsAdmin() {
        baseRouter
                .authPage().email.fill("test@protei.ru")
                .authPage().password.fill("test")
                .authPage().enter.click()
                .mainPage().logoutButton.shouldExist();
    }


Anda dapat menambahkan lapisan langkah klasik jika diperlukan dalam kerangka kerja Anda (penerapan pustaka jarak jauh di Java untuk RobotFramework memerlukan kelas langkah sebagai masukan, misalnya), atau jika Anda ingin menambahkan anotasi untuk laporan yang indah. Kami menjadikannya generator berbasis anotasi, jika Anda tertarik, tulis di komentar, kami akan memberi tahu Anda.



Contoh kelas langkah otorisasi
public class AuthSteps{

    private BaseRouter baseRouter = new BaseRouter();

    @Step("Sigh in as {mail}")
    public BaseSteps login(String mail, String password) {
        baseRouter
                .authPage().email.fill(mail)
                .authPage().password.fill(password)
                .authPage().enter.click()
                .mainPage().logoutButton.shouldExist();
        return this;
    }
    @Step("Fill E-mail")
    public AuthSteps fillEmail(String email) {
        baseRouter.authPage().email.fill(email);
        return this;
    }
    @Step("Fill password")
    public AuthSteps fillPassword(String password) {
        baseRouter.authPage().password.fill(password);
        return this;
    }
    @Step("Click enter")
    public AuthSteps clickEnter() {
        baseRouter.authPage().enter.click();
        return this;
    }
    @Step("Enter should exist")
    public AuthSteps shouldExistEnter() {
        baseRouter.authPage().enter.shouldExist();
        return this;
    }
    @Step("Logout")
    public AuthSteps logout() {
        baseRouter.mainPage().logoutButton.click()
                .authPage().enter.shouldExist();
        return this;
    }
}
public class BaseRouter {
//    ,      ,     
    public AuthPage authPage() {return page(AuthPage.class);}
    public MainPage mainPage() {return page(MainPage.class);}
    public UsersPage usersPage() {return page(UsersPage.class);}
    public VariantsPage variantsPage() {return page(VariantsPage.class);}
}




Langkah-langkah ini sangat mirip dengan langkah-langkah di dalam halaman, praktis tidak berbeda. Tetapi memisahkannya ke dalam kelas yang terpisah membuka ruang lingkup untuk pembuatan kode, sementara tautan keras dengan halaman yang sesuai tidak hilang. Pada saat yang sama, jika Anda tidak menulis langkah-langkah di halaman, maka arti enkapsulasi menghilang, dan jika Anda tidak menambahkan kelas langkah-langkah ke pageElement, interaksi dengan halaman tersebut akan tetap terpisah dari logika bisnis.



, , . . , , , « , ». — , page object , !





Salah jika berbicara tentang arsitektur proyek tanpa menyentuh metode pengoperasian yang nyaman dengan data pengujian.



Cara termudah adalah dengan meneruskan data secara langsung dalam pengujian "sebagaimana adanya" atau dalam variabel. Ini bagus untuk arsitektur lembar, tetapi proyek besar menjadi berantakan.



Metode lain adalah menyimpan data sebagai objek, itu ternyata yang terbaik bagi kami, karena mengumpulkan semua data yang terkait dengan satu entitas di satu tempat, menghilangkan godaan untuk mencampur semuanya dan menggunakan sesuatu di tempat yang salah. Selain itu, metode ini memiliki banyak perbaikan tambahan yang dapat berguna pada proyek individu.



Untuk setiap entitas, model yang mendeskripsikannya dibuat, yang dalam kasus paling sederhana berisi nama dan jenis bidang, misalnya, berikut adalah model pengguna:



public class User {
    private Integer id;
    private String mail;
    private String name;
    private String password;
    private Gender gender;

    private boolean check11;
    private boolean check12;
    private boolean check21;
    private boolean check22;
    private boolean check23;

    public enum Gender {
        MALE,
        FEMALE;

        public String getVisibleText() {
            switch (this) {
                case MALE:
                    return "";
                case FEMALE:
                    return "";
            }
            return "";
        }
    }
}


Peretasan hidup # 1: jika Anda memiliki arsitektur interaksi klien-server seperti istirahat (objek json atau xml berada di antara klien dan server, dan bukan potongan kode yang tidak dapat dibaca), maka Anda dapat melakukan google json ke objek <bahasa Anda>, mungkin generator yang Anda butuhkan sudah ada ...



Retasan kehidupan # 2: jika pengembang server Anda menulis dalam bahasa pemrograman berorientasi objek yang sama, maka Anda dapat menggunakan model mereka.



Retasan kehidupan # 3: jika Anda seorang javist dan sebuah perusahaan mengizinkan Anda menggunakan perpustakaan pihak ketiga, dan tidak ada rekan kerja yang gugup, meramalkan banyak penderitaan bagi bidat yang menggunakan perpustakaan tambahan alih-alih Java yang murni dan indah, ambillah Lombok ! Ya, biasanya IDEdapat menghasilkan getter, setter, toString, dan builder. Namun ketika membandingkan model Lombok kami dan model pengembangan tanpa Lombok, terlihat keuntungan dari ratusan baris kode "kosong" yang tidak membawa logika bisnis untuk setiap kelas. Saat menggunakan Lombok, Anda tidak harus mengalahkan orang-orang yang mencampur bidang dan pengambil, penyetel, kelas lebih mudah dibaca, Anda bisa mendapatkan ide tentang objek sekaligus, tanpa menggulir tiga layar.



Jadi, kita memiliki wireframes objek yang kita perlukan untuk meregangkan data uji. Data dapat disimpan sebagai variabel statis final, misalnya, ini dapat berguna untuk administrator sistem utama, tempat pengguna lain dibuat. Lebih baik menggunakan final, sehingga tidak ada godaan untuk mengubah data dalam tes, karena tes berikutnya, alih-alih administrator, bisa mendapatkan pengguna "tidak berdaya", belum lagi eksekusi tes paralel.



public class Users {
    public static final User admin = User.builder().mail("test@protei.ru").password("test").build();
}


Untuk mendapatkan data yang tidak memengaruhi pengujian lain, Anda dapat menggunakan pola "prototipe" dan menggandakan instance Anda di setiap pengujian. Kami memutuskan untuk membuatnya lebih mudah: untuk menulis metode yang mengacak bidang kelas, seperti ini:



    public static User getUserRandomData() {
        User user = User.builder()
                .mail(getRandomEmail())
                .password(getShortLatinStr())
                .name(getShortLatinStr())
                .gender(getRandomFromEnum(User.Gender.class))
                .check11(getRandomBool())
                .check21(getRandomBool())
                .check22(getRandomBool())
                .check23(getRandomBool())
                .build();
//business-logic: 11 xor 12 must be selected
        if (!user.isCheck11()) user.setCheck12(true); 
        if (user.isCheck11()) user.setCheck12(false);
        return user;
    }


Pada saat yang sama, metode yang membuat keacakan langsung lebih baik ditempatkan di kelas terpisah, karena







metode tersebut juga akan digunakan di model lain: Dalam metode untuk mendapatkan pengguna acak, pola "pembangun" digunakan , yang diperlukan agar tidak membuat jenis konstruktor baru untuk setiap set yang diperlukan bidang. Sebagai gantinya, tentu saja, Anda cukup memanggil konstruktor yang diinginkan.



Metode penyimpanan data ini menggunakan pola Value Object, yang menjadi dasarnya Anda dapat menambahkan keinginan Anda, tergantung pada kebutuhan proyek. Anda dapat menambahkan objek penyimpanan ke database, dan dengan demikian mempersiapkan sistem sebelum pengujian. Anda tidak dapat mengacak pengguna, tetapi memuatnya dari file properti (dan pustaka keren lainnya). Anda dapat menggunakan pengguna yang sama di mana saja, tetapi buat yang disebut registri data untuk setiap jenis objek, di mana nilai penghitung ujung ke ujung akan ditambahkan ke nama atau bidang unik lain dari objek tersebut, dan pengujian akan selalu memiliki testUser_135 uniknya sendiri.



Anda dapat menulis Object Storage Anda sendiri (kumpulan objek google dan kelas terbang), tempat Anda dapat meminta entitas yang diperlukan di awal pengujian. Gudang memberikan salah satu objeknya yang siap pakai dan menandainya sibuk. Di akhir pengujian, benda dikembalikan ke penyimpanan, di mana ia dibersihkan jika perlu, diberi tanda kosong, dan diberikan ke pengujian berikutnya. Ini dilakukan jika operasi pembuatan objek sangat intensif sumber daya, dan dengan pendekatan ini, penyimpanan bekerja secara independen dari pengujian dan dapat menyiapkan data untuk kasus berikut.



Pembuatan data



Untuk kasus pengeditan pengguna, Anda pasti membutuhkan pengguna yang dibuat yang akan Anda edit, dan, secara umum, tes edit tidak peduli dari mana pengguna ini berasal. Ada beberapa cara untuk membuatnya:



  • tekan tombol dengan tangan Anda sebelum ujian,
  • tinggalkan data dari tes sebelumnya,
  • terapkan sebelum pengujian dari cadangan,
  • buat dengan mengklik tombol langsung di tes,
  • gunakan API.


Semua metode ini memiliki kekurangan: jika sebelum pengujian Anda perlu memasukkan sesuatu ke dalam sistem secara manual, maka ini adalah pengujian yang buruk, dan oleh karena itu disebut autotests, yang harus bertindak sebebas mungkin dari tangan manusia.



Menggunakan hasil tes sebelumnya melanggar prinsip atomicity dan tidak memungkinkan Anda menjalankan tes secara terpisah, Anda harus menjalankan seluruh batch, dan tes ui tidak secepat itu. Menulis tes sedemikian rupa sehingga masing-masing dapat dijalankan dalam isolasi yang baik dan tanpa tarian tambahan dianggap sebagai bentuk yang baik. Selain itu, bug dalam pembuatan objek yang membatalkan pengujian sebelumnya sama sekali tidak menjamin bug dalam pengeditan, dan dalam konstruksi seperti itu, pengujian pengeditan akan jatuh berikutnya, dan tidak mungkin untuk mengetahui apakah pengeditan berfungsi.



Menggunakan cadangan (gambar tersimpan dari database) dengan data yang diperlukan untuk pengujian sudah merupakan pendekatan yang kurang lebih baik, terutama jika cadangan diterapkan secara otomatis atau jika pengujian itu sendiri menempatkan data dalam database. Namun, mengapa objek khusus ini digunakan dalam pengujian tidak jelas, masalah persimpangan data juga dapat dimulai dengan sejumlah besar pengujian. Terkadang pencadangan berhenti berfungsi dengan benar karena pembaruan arsitektur basis data, misalnya, jika Anda perlu menjalankan pengujian pada versi lama, dan cadangan sudah berisi bidang baru. Anda dapat melawan ini dengan mengatur penyimpanan cadangan untuk setiap versi aplikasi. Terkadang cadangan tidak lagi valid karena pembaruan arsitektur basis data - bidang baru muncul secara teratur, sehingga cadangan perlu diperbarui secara teratur. Dan tiba-tiba mungkin sajabahwa persis satu pengguna-dari-cadangan tidak pernah crash, dan jika pengguna baru saja dibuat atau nama diberikan kepadanya secara acak, Anda akan menemukan bug. Ini disebut "efek pestisida", pengujian berhenti menangkap serangga, karena aplikasi "terbiasa" dengan data yang sama dan tidak jatuh, dan tidak ada penyimpangan ke samping.



Jika pengguna dibuat dalam pengujian melalui klik pada antarmuka yang sama, maka pestisida berkurang dan ketidakjelasan tampilan pengguna menghilang. Kekurangannya mirip dengan hasil tes sebelumnya: kecepatannya biasa saja, dan jika ada bug dalam pembuatan, bahkan yang terkecil (terutama bug tes, misalnya, locator tombol simpan akan berubah), maka kita tidak akan tahu apakah pengeditan berfungsi.



Terakhir, cara lain untuk membuat pengguna adalah melalui http-API dari pengujian, yaitu, alih-alih mengklik tombol, segera kirim permintaan untuk membuat pengguna yang diinginkan. Dengan demikian, pestisida dikurangi sebanyak mungkin, jelas dari mana pengguna berasal, dan kecepatan pembuatannya jauh lebih tinggi daripada saat mengklik tombol. Kerugian dari metode ini adalah tidak cocok untuk proyek tanpa json atau xml dalam protokol komunikasi antara klien dan server (misalnya, jika pengembang menulis menggunakan gwt dan tidak ingin menulis api tambahan untuk penguji). Dimungkinkan, saat menggunakan API, kehilangan bagian logika yang dieksekusi oleh panel admin, dan membuat entitas yang tidak valid. API dapat berubah, menyebabkan pengujian gagal, tetapi biasanya hal ini diketahui, dan tidak ada yang memerlukan perubahan demi perubahan, kemungkinan besar ini adalah logika baru yang masih harus diperiksa.Mungkin juga akan ada bug di level API, tetapi tidak ada satu metode pun selain cadangan siap pakai yang aman dari ini, jadi lebih baik untuk menggabungkan pendekatan untuk membuat data.



Tambahkan API tetesan



Di antara metode untuk menyiapkan data, http-API untuk kebutuhan pengujian terpisah saat ini dan menerapkan cadangan untuk data pengujian tambahan yang tidak berubah dalam pengujian, misalnya, ikon untuk objek, sehingga pengujian objek ini tidak macet saat ikon dimuat, paling cocok untuk kami.



Untuk membuat objek melalui API di Java, ternyata paling nyaman menggunakan pustaka restAssured, meskipun sebenarnya tidak dimaksudkan untuk ini. Saya ingin berbagi beberapa chip yang ditemukan, Anda tahu lebih banyak - tulis!



Rasa sakit pertama adalah otorisasi dalam sistem. Metodenya perlu dipilih secara terpisah untuk setiap proyek, tetapi ada satu hal yang sama - otorisasi perlu ditempatkan dalam spesifikasi permintaan, misalnya:



public class ApiSettings {
    private static String loginEndpoint="/login";

    public static RequestSpecification testApi() {
        RequestSpecBuilder tmp = new RequestSpecBuilder()
                .setBaseUri(testConfig.getSiteUrl())
                .setContentType(ContentType.JSON)
                .setAccept(ContentType.JSON)
                .addFilter(new BeautifulRest())
                .log(LogDetail.ALL);
        Map<String, String> cookies = RestAssured.given().spec(tmp.build())
                .body(admin)
                .post(loginEndpoint).then().statusCode(200).extract().cookies();
        return tmp.addCookies(cookies).build();
    }
}


Anda dapat menambahkan kemampuan untuk menyimpan cookie untuk pengguna tertentu, maka jumlah permintaan ke server akan berkurang. Perpanjangan kedua yang mungkin dari metode ini adalah menyimpan Cookies yang diterima untuk pengujian saat ini, dan melemparkannya ke driver browser, melewati langkah otorisasi. Kemenangannya hanya beberapa detik, tetapi jika Anda mengalikannya dengan jumlah tes, Anda bisa mempercepatnya dengan cukup baik!



Ada sanggul untuk gaya berjalan dan laporan indah, perhatikan barisnya .addFilter(new BeautifulRest()):



Kelas BeautifulRest


public class BeautifulRest extends AllureRestAssured {
        public BeautifulRest() {}

        public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext filterContext) {
            AllureLifecycle lifecycle = Allure.getLifecycle();
            lifecycle.startStep(UUID.randomUUID().toString(), (new StepResult()).setStatus(Status.PASSED).setName(String.format("%s: %s", requestSpec.getMethod(), requestSpec.getURI())));
            Response response;
            try {
                response = super.filter(requestSpec, responseSpec, filterContext);
            } finally {
                lifecycle.stopStep();
            }
            return response;
        }
}




Model objek sangat cocok dengan restAssured, karena library itu sendiri menangani serialisasi dan deserialisasi model dalam json / xml (konversi dari format json / xml ke objek dari kelas tertentu).



    @Step("create user")
    public static User createUser(User user) {
        String usersEndpoint = "/user";
        return RestAssured.given().spec(ApiSettings.testApi())
                .when()
                .body(user)
                .post(usersEndpoint)
                .then().log().all()
                .statusCode(200)
                .body("state",containsString("OK"))
                .extract().as(User.class);
    }


Jika Anda melihat beberapa langkah berturut-turut untuk membuat objek, Anda dapat melihat identitas kode. Untuk mengurangi kode yang sama, Anda dapat menulis metode umum untuk membuat objek.



    public static Object create(String endpoint, Object model) {
        return RestAssured.given().spec(ApiSettings.testApi())
                .when()
                .body(model)
                .post(endpoint)
                .then().log().all()
                .statusCode(200)
                .body("state",containsString("OK"))
                .extract().as(model.getClass());
    }

    @Step("create user")
    public static User createUser(User user) {
                  create(User.endpoint, user);
    }


Sekali lagi tentang operasi rutin



Sebagai bagian dari pemeriksaan pengeditan suatu objek, kami biasanya tidak peduli bagaimana objek tersebut muncul di sistem - melalui api atau dari cadangan, atau apakah itu dibuat oleh tes ui. Tindakan penting adalah menemukan objek, klik ikon "edit" di atasnya, kosongkan bidang dan isi dengan nilai baru, klik "simpan" dan periksa apakah semua nilai baru telah disimpan dengan benar. Semua informasi yang tidak perlu yang tidak terkait langsung dengan pengujian harus dihapus dalam metode terpisah, misalnya, di kelas langkah.



    @Test
    void checkUserVars() {        
//Arrange
        User userForTest = getUserRandomData();
       
 //         , 
 //      -  , 
 //   ,   
        usersSteps.createUser(userForTest);
        authSteps.login(userForTest);
       
 //Act
        mainMenuSteps
                .clickVariantsMenu();
       
 //Assert
        variantsSteps
                .checkAllVariantsArePresent(userForTest.getVars())
                .checkVariantsCount(userForTest.getVarsCount());
        
//Cleanup
        usersSteps.deleteUser(userForTest);
    }


Penting untuk tidak terbawa suasana, karena pengujian yang hanya terdiri dari tindakan "kompleks" menjadi kurang terbaca dan lebih sulit untuk direproduksi tanpa menggali kode.



    @Test
    void authAsAdmin() {
        authSteps.login(Users.admin);
//  ,    .     . 
//   ,   ? 


Jika secara praktis pengujian yang sama muncul dalam rangkaian, yang hanya berbeda dalam persiapan data (misalnya, Anda perlu memeriksa bahwa ketiga jenis pengguna "berbeda" dapat melakukan tindakan yang sama, atau terdapat jenis objek kontrol yang berbeda, yang masing-masing perlu Anda periksa pembuatan objek dependen yang identik, atau Anda perlu memeriksa pemfilteran dengan sepuluh jenis status objek), Anda masih tidak dapat memindahkan bagian berulang ke metode terpisah. Tidak sama sekali jika keterbacaan itu penting bagi Anda!



Sebagai gantinya, Anda perlu membaca tentang pengujian berbasis data, untuk Java + TestNG akan menjadi seperti ini:



    @Test(dataProvider = "usersWithDifferentVars")
    void checkUserDifferentVars(User userForTest) {
        //Arrange
        usersSteps.createUser(userForTest);
        authSteps.login(userForTest);
        //Act
        mainMenuSteps
                .clickVariantsMenu();
        //Assert
        variantsSteps
                .checkAllVariantsArePresent(userForTest.getVars())
                .checkVariantsCount(userForTest.getVarsCount());
    }

 //         . 
 // ,   -.
    @DataSupplier(name = "usersWithDifferentVars")
    public Stream<User> usersWithDifferentVars(){
        return Stream.of(
            getUserRandomData().setCheck21(false).setCheck22(false).setCheck23(false),
            getUserRandomData().setCheck21(true).setCheck22(false).setCheck23(false),
            getUserRandomData().setCheck21(false).setCheck22(true).setCheck23(false),
            getUserRandomData().setCheck21(false).setCheck22(false).setCheck23(true),
            getUserRandomData().setCheck21(true).setCheck22(true).setCheck23(false),
            getUserRandomData().setCheck21(true).setCheck22(false).setCheck23(true),
            getUserRandomData().setCheck21(false).setCheck22(true).setCheck23(true),
            getUserRandomData().setCheck21(true).setCheck22(true).setCheck23(true)
        );
    }


Ini menggunakan pustaka Pemasok Data , yang merupakan add-on di atas Penyedia Data TestNG yang memungkinkan Anda untuk menggunakan koleksi yang diketik alih-alih Objek [] [], tetapi intinya sama. Jadi, kami mendapatkan satu pengujian, yang dijalankan sebanyak itu menerima data masukan.



kesimpulan



Jadi, untuk membuat proyek autotest antarmuka pengguna yang besar namun nyaman, Anda memerlukan:



  • Jelaskan semua widget kecil yang ditemukan dalam aplikasi,
  • Kumpulkan widget ke dalam halaman,
  • Buat model untuk semua jenis entitas,
  • Tambahkan metode untuk menghasilkan semua jenis entitas berdasarkan model,
  • Pertimbangkan metode yang cocok untuk membuat entitas tambahan
  • Opsional: buat atau kumpulkan file langkah secara manual,
  • Tulis pengujian sehingga di bagian tindakan utama pengujian tertentu tidak ada tindakan yang rumit, hanya operasi yang jelas dengan widget.


Selesai, Anda telah membuat proyek berbasis PageElement dengan metode sederhana untuk menyimpan, menghasilkan, dan menyiapkan data. Anda sekarang memiliki arsitektur yang mudah dipelihara, dikelola, dan cukup fleksibel. Penguji berpengalaman dan pemula June dapat dengan mudah menavigasi proyek, karena autotests dalam format tindakan pengguna paling nyaman untuk dibaca dan dipahami.



Contoh kode dari artikel dalam bentuk proyek yang sudah selesai ditambahkan ke git .



All Articles