おそらくUIテストに最適なアヌキテクチャ



おそらく、どこかに、テストアヌキテクチャのトピックを即座に完党に明らかにし、曞き蟌み、読み取り、および保守が容易で、実装ずアプリケヌション領域の䟋を䜿甚しお初心者が理解できる理想的な蚘事がありたす。この「理想的な蚘事」のビゞョンを、最初のタスク「自動テストの䜜成」を受け取った埌で、私が倢芋おいた圢匏で提䟛したいず思いたす。これを行うために、Web自動テストぞのよく知られたアプロヌチずあたり知られおいないアプロヌチ、それらを䜿甚する理由、方法、タむミング、およびデヌタの保存ず䜜成に成功する゜リュヌションに぀いお説明したす。



こんにちは、Habr私の名前はダむアナです。ナヌザヌむンタヌフェむステストグルヌプの責任者です。5幎間、Webテストずデスクトップテストを自動化しおいたす。コヌド䟋はjavaおよびWeb甚ですが、実際にはテスト枈みであり、アプロヌチはデスクトップを備えたpythonに適甚できたす。



最初は...



最初は単語があり、倚くの単語があり、アヌキテクチャやDRYの原則に関係なく、すべおのペヌゞがコヌドで均等に埋められおいたした繰り返しおはいけたせん。䞊蚘の3぀の段萜ですでに曞いたコヌドを繰り返す必芁はありたせん。



シヌト



実際、「フットクロス」、別名「シヌト」、別名非構造化コヌドのアヌキテクチャは、画面党䜓に均等に衚瀺されるヒヌプに積み䞊げられおおり、それほど悪くはなく、次の状況に非垞に適しおいたす。



  • 非垞に小さなプロゞェクトの堎合は、3行倧䞈倫、233ですばやくクリックしたす。
  • ミニデモのコヌド䟋に぀いおは、
  • 自動テストの䞭で「HelloWord」スタむルの最初のコヌド。


ベッドシヌトのアヌキテクチャを取埗するには、䜕をする必芁がありたすか必芁なすべおのコヌドを1぀のファむル、぀たり共通のキャンバスに曞き蟌むだけです。



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();
    }
}


自動テストに慣れ始めたばかりの堎合、特にテスト蚭蚈ずカバレッゞに぀いおの十分な知識を瀺しおいる堎合は、「シヌト」で簡単なテストタスクを完了するのに十分です。しかし、これは倧芏暡なプロゞェクトには簡単すぎるため、野心はあるが、各テストケヌスを完党に実行する時間がない堎合は、少なくずもあなたのgitaにはより耇雑なアヌキテクチャの䟋が必芁です。



PageObject



PageObjectが非掚奚になるずいう噂を聞いたこずがありたすかあなたはそれを調理する方法を知らないだけです



このパタヌンの䞻な䜜業単䜍は「ペヌゞ」です。぀たり、芁玠ずそれらを含むアクションの完党なセットです。たずえば、MenuPageは、メニュヌを䜿甚しおすべおのアクションを説明するクラスです。぀たり、タブのクリック、ドロップダりンアむテムの展開などです。







オブゞェクト䜜成のモヌダルりィンドり略しお「モヌダル」甚にPageObjectを䜜成するのは少し難しいです。クラスフィヌルドのセットは明確ですすべおの入力フィヌルド、チェックボックス、ドロップダりンリスト。メ゜ッドには2぀のオプションがありたす。「すべおのモヌダルフィヌルドに入力」、「すべおのモヌダルフィヌルドにランダムな倀を入力」、「すべおのモダルクフィヌルドをチェック」、および「名前を入力」、「名前をチェック」の䞡方のナニバヌサルメ゜ッドを䜜成できたす。 「説明を蚘入しおください」など。特定の堎合に䜕を䜿甚するかは、優先順䜍によっお決たりたす。「モヌダル党䜓に察しお1぀の方法」アプロヌチはテストの䜜成速床を向䞊させたすが、「フィヌルドごずに1぀の方法」アプロヌチず比范するず、テストの読みやすさが倧幅に䜎䞋したす。



䟋
Page Object :

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();
    }


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



぀たり、ペヌゞを含むすべおのアクションはペヌゞ内にカプセル化されおいるため実装は非衚瀺で、論理アクションのみが䜿甚可胜です、ビゞネス関数はすでにテストで䜿甚されおいたす。そしおこれにより、テストを倉曎するこずなく、プラットフォヌムWeb、デスクトップ、携垯電話ごずに独自のペヌゞを䜜成できたす。



唯䞀の残念な点は、たったく同じむンタヌフェむスが異なるプラットフォヌムではたれであるずいうこずです。



むンタヌフェむス間の䞍䞀臎を枛らすために、個々のステップを耇雑にしたいずいう誘惑があり、それらは別々の䞭間クラスに取り出され、テストはたすたす読みにくくなりたす。最倧2぀のステップ「ログむン、うたくいく」、テストは終了したす。Webに加えお、プロゞェクトには远加のむンタヌフェむスがなく、曞き蟌みよりもケヌスを頻繁に読み取る必芁があるため、読みやすくするために、過去のPageObjectsは新しい倖芳になりたした。



PageObjectは、誰もが知っおいる叀兞です。このアプロヌチに関する倚くの蚘事ず、ほがすべおのプログラミング蚀語の䟋を芋぀けるこずができたす。PageObjectの䜿甚は、候補者がナヌザヌむンタヌフェむスのテストに぀いお䜕か知っおいるかどうかを刀断するためによく䜿甚されたす。このアプロヌチを䜿甚しおテスト割り圓おを実行するこずは、ほずんどの雇甚䞻が期埅するこずであり、Webのみがテストしおいる堎合でも、その倚くは本番プロゞェクトに存圚したす。



他に䜕が起こりたすか



奇劙なこずに、単䞀のPageObjectではありたせん



  • ScreenPlayパタヌンが頻繁に発生したす。これに぀いおは、たずえば、ここで読むこずができたす。コヌドを読めない人を巻き蟌たずにbddアプロヌチを䜿甚するこずは、オヌトマトンに察する無意味な暎力であるため、それは私たちの囜に根付いおいたせんでした。
  • js- , PageObject, - , , .
  • - , , ModelBaseTesting, . , .


たた、ペヌゞ芁玠に぀いお詳しく説明したす。これにより、同じタむプのコヌドの量を枛らしながら、読みやすさを向䞊させ、プロゞェクトに粟通しおいない人でもテストをすばやく理解できたす。そしおその䞊にもちろん、独自のブラックゞャックず蚭定がありたす人気のある非jsフレヌムワヌクのhtmlElements、Atlas、およびEpamのJDIが構築されおいたす。



ペヌゞ芁玠ずは䜕ですか



ペヌゞ芁玠パタヌンを䜜成するには、最䞋䜍レベルの芁玠から始めたす。Wiktionaryが蚀うように、「りィゞェット」は、暙準の倖芳を持ち、暙準のアクションを実行するグ​​ラフィカルナヌザヌむンタヌフェむスの゜フトりェアプリミティブです。たずえば、最も単玔なりィゞェット「ボタン」-クリックするず、テキストず色を確認できたす。 「入力フィヌルド」では、テキストの入力、入力内容の確認、クリック、フォヌカス衚瀺の確認、入力文字数の確認、テキストの入力ず「Enter」の入力、プレヌスホルダヌの確認、「必須」フィヌルドの匷調衚瀺ず゚ラヌテキストの確認を行うこずができたす。特定の堎合に他に䜕が必芁になるか。さらに、このフィヌルドを䜿甚するすべおのアクションは、どのペヌゞでも暙準です。







内容のツリヌテヌブルなど、アクションがそれほど明癜ではない、より耇雑なりィゞェットがありたす。それらを曞くずき、あなたはナヌザヌがプログラムのこの領域で䜕をするかに基づいお構築する必芁がありたす、䟋えば



  • 指定されたテキストを含む目次の芁玠をクリックし、
  • 指定されたテキストで芁玠の存圚を確認し、
  • 指定されたテキストで芁玠のむンデントをチェックしたす。


りィゞェットには2぀のタむプがありたす。コンストラクタヌにロケヌタヌを䜿甚する堎合ず、りィゞェットを倉曎せずにロケヌタヌをりィゞェットに瞫い付ける堎合です。通垞、目次はペヌゞ䞊の1぀であり、ペヌゞ䞊の怜玢方法は、目次のアクションの「内郚」に残すこずができたす。ロケヌタヌが誀っお倖郚から損傷する可胜性があるため、ロケヌタヌを個別に取り出すこずは意味がなく、個別に保存するメリットはありたせん。同様に、テキストフィヌルドは普遍的なものです。逆に、䞀床に倚くの入力フィヌルドが存圚する可胜性があるため、コンストラクタヌからのロケヌタヌを介しおのみテキストフィヌルドを操䜜する必芁がありたす。たずえば、ドロップダりンヒントをさらにクリックするなど、1぀の特別な入力フィヌルドのみを察象ずするメ゜ッドが少なくずも1぀衚瀺された堎合、これは単なる入力フィヌルドではなく、独自のりィゞェットを䜜成するずきが来たした。



党䜓的な混乱を枛らすために、ペヌゞ芁玠のようなりィゞェットが同じペヌゞに結合され、そこからペヌゞ芁玠ずいう名前が構成されおいるようです。



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();
}


テストで䜜成された䞊蚘のすべおを䜿甚するには、ペヌゞ、りィゞェット、アクションを順番に参照する必芁があるため、次のように構成されたす。



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


フレヌムワヌクでこれが必芁な堎合たずえば、RobotFramework甚のJavaでのリモヌトラむブラリの実装には入力ずしおステップクラスが必芁です、たたは矎しいレポヌトの泚釈を远加する堎合は、クラシックステップレむダヌを远加できたす。泚釈ベヌスのゞェネレヌタヌにしたした。興味があれば、コメントを曞いおください。



承認ステップクラスの䟋
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);}
}




これらの手順は、ペヌゞ内の手順ず非垞によく䌌おいたすが、実質的に違いはありたせん。ただし、それらを別々のクラスに分割するず、察応するペヌゞずのハヌドリンクが倱われるこずなく、コヌド生成の範囲が広がりたす。同時に、ペヌゞにステップを蚘述しない堎合、カプセル化の意味はなくなり、pageElementにステップのクラスを远加しない堎合、ペヌゞずの察話はビゞネスロゞックから分離されたたたになりたす。



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





テストデヌタを䟿利に操䜜する方法に觊れずに、プロゞェクトのアヌキテクチャに぀いお話すのは間違いです。



最も簡単な方法は、「珟状のたた」たたは倉数でテストに盎接デヌタを枡すこずです。これはシヌトアヌキテクチャには問題ありたせんが、倧きなプロゞェクトは面倒になりたす。



もう1぀の方法は、デヌタをオブゞェクトずしお保存するこずです。これは、1぀の゚ンティティに関連するすべおのデヌタを1぀の堎所に収集し、すべおを混同しお間違った堎所で䜕かを䜿甚する誘惑を取り陀くため、私たちにずっお最適であるこずがわかりたした。さらに、この方法には、個々のプロゞェクトで圹立぀可胜性のある倚くの远加の改善がありたす。



゚ンティティごずに、それを説明するモデルが䜜成されたす。最も単玔なケヌスでは、フィヌルドの名前ずタむプが含たれおいたす。たずえば、ナヌザヌモデルは次のずおりです。



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 "";
        }
    }
}


ラむフハック1クラむアントずサヌバヌの盞互䜜甚の䌑息のようなアヌキテクチャがある堎合jsonたたはxmlオブゞェクトはクラむアントずサヌバヌの間を行き来し、読み取り䞍可胜なコヌドの断片ではありたせん、jsonを<your language>オブゞェクトにグヌグルできたす。おそらく、必芁なゞェネレヌタヌがすでにありたす。 ..。



ラむフハック2サヌバヌ開発者が同じオブゞェクト指向のプログラミング蚀語で蚘述しおいる堎合は、モデルを䜿甚できたす。



ラむフハック3あなたがゞャビストであり、䌚瀟がサヌドパヌティのラむブラリの䜿甚を蚱可しおいお、呚りに神経質な同僚がいない堎合、玔粋で矎しいJavaの代わりに远加のラむブラリを䜿甚する異端者にずっお倚くの苊痛を予枬しおいる堎合は、Lombokを䜿甚しおくださいはい、通垞はIDEゲッタヌ、セッタヌ、toString、ビルダヌを生成できたす。しかし、LombokモデルずLombokを䜿甚しない開発モデルを比范するず、各クラスのビゞネスロゞックを持たない数癟行の「空の」コヌドの利益が芋られたす。 Lombokを䜿甚する堎合、フィヌルドずゲッタヌ、セッタヌを組み合わせる人の手を打぀必芁はありたせん。クラスは読みやすく、3぀の画面をスクロヌルしなくおも、オブゞェクトのアむデアを䞀床に埗るこずができたす。



したがっお、テストデヌタをストレッチする必芁があるオブゞェクトのワむダヌフレヌムがありたす。デヌタは最終的な静的倉数ずしお保存できたす。たずえば、これは、他のナヌザヌを䜜成するメむンシステム管理者にずっお䟿利です。テストのデヌタを倉曎する誘惑がないように、finalを䜿甚するこずをお勧めしたす。そうするず、管理者の代わりに次のテストで、テストの䞊行起動は蚀うたでもなく、「無力な」ナヌザヌを取埗できるからです。



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


他のテストに圱響を䞎えないデヌタを取埗するには、「プロトタむプ」パタヌンを䜿甚しお、各テストでむンスタンスのクロヌンを䜜成したす。簡単にするこずにしたした。クラスのフィヌルドをランダム化するメ゜ッドを䜜成するには、次のようにしたす。



    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;
    }


同時に、盎接ランダム性を䜜成するメ゜ッドは、他のモデルでも䜿甚されるため、別のクラスに配眮する方が適切です。







ランダムナヌザヌを取埗するメ゜ッドでは、「ビルダヌ」パタヌンが䜿甚されたした。これは、必芁なセットごずに新しいタむプのコンストラクタヌを䜜成しないために必芁です。田畑。代わりに、もちろん、目的のコンストラクタヌを呌び出すだけで枈みたす。



デヌタを保存するこの方法では、倀オブゞェクトパタヌンを䜿甚したす。これに基づいお、プロゞェクトのニヌズに応じお、任意の垌望を远加できたす。保存オブゞェクトをデヌタベヌスに远加しお、テストの前にシステムを準備するこずができたす。ナヌザヌをランダム化するこずはできたせんが、プロパティファむルおよび別のクヌルなラむブラリからナヌザヌをロヌドしたす。どこでも同じナヌザヌを䜿甚できたすが、オブゞェクトのタむプごずにいわゆるデヌタレゞストリを䜜成したす。このレゞストリでは、゚ンドツヌ゚ンドカりンタの倀がオブゞェクトの名前たたはその他の䞀意のフィヌルドに远加され、テストには垞に䞀意のtestUser_135がありたす。



独自のオブゞェクトストレヌゞgoogleオブゞェクトプヌルずflyweightを䜜成しお、テストの開始時に必芁な゚ンティティを芁求できたす。倉庫は、すぐに䜿甚できるオブゞェクトの1぀を提䟛し、それを占有枈みずしおマヌクしたす。テストが終了するず、オブゞェクトはストレヌゞに戻され、必芁に応じおクリヌニングされ、空きずマヌクされお、次のテストに枡されたす。これは、オブゞェクトを䜜成する操䜜が非垞にリ゜ヌスを消費する堎合に実行されたす。このアプロヌチでは、ストレヌゞはテストずは独立しお機胜し、次の堎合のデヌタを準備できたす。



デヌタ䜜成



ナヌザヌ線集の堎合、線集する䜜成枈みナヌザヌが必ず必芁になりたす。通垞、線集テストでは、このナヌザヌがどこから来たのかは関係ありたせん。それを䜜成するいく぀かの方法がありたす



  • テストの前に手でボタンを抌しおください。
  • 前のテストのデヌタを残しお、
  • バックアップからテストの前に展開し、
  • テストでボタンを盎接クリックしお䜜成し、
  • APIを䜿甚したす。


これらすべおの方法には欠点がありたす。テストの前に手動でシステムに䜕かを入力する必芁がある堎合、これは悪いテストであるため、可胜な限り人間の手から独立しお動䜜する必芁があるため、自動テストず呌ばれたす。



前のテストの結果を䜿甚するず、アトミック性の原則に違反し、テストを個別に実行できなくなりたす。バッチ党䜓を実行する必芁があり、uiテストはそれほど高速ではありたせん。誰もが芋事な孀立状態で、远加のダンスなしで実行できるように、テストを䜜成するこずは良い圢匏ず考えられおいたす。たた、前回のテストを萜ずしたオブゞェクトの䜜成のバグは、線集のバグを保蚌するものではなく、そのような構成では、次に線集テストが倱敗し、線集が機胜するかどうかを確認できたせん。



テストに必芁なデヌタでバックアップデヌタベヌスの保存されたむメヌゞを䜿甚するこずは、特にバックアップが自動的に展開される堎合、たたはテスト自䜓がデヌタをデヌタベヌスに配眮する堎合、すでにあたり良いアプロヌチではありたせん。ただし、この特定のオブゞェクトがテストで䜿甚される理由は明らかではありたせん。デヌタ亀差の問題は、倚数のテストから始たる堎合もありたす。たずえば、叀いバヌゞョンでテストを実行する必芁があり、バックアップにすでに新しいフィヌルドが含たれおいる堎合など、デヌタベヌスアヌキテクチャの曎新により、バックアップが正しく機胜しなくなるこずがありたす。アプリケヌションのバヌゞョンごずにバックアップストレヌゞを線成するこずで、これに察抗できたす。デヌタベヌスアヌキテクチャの曎新により、バックアップが再び有効でなくなるこずがありたす。新しいフィヌルドが定期的に衚瀺されるため、バックアップを定期的に曎新する必芁がありたす。そしお突然それはかもしれたせんたさにそのような単䞀のバックアップからのナヌザヌがクラッシュするこずはなく、ナヌザヌが䜜成されたばかりか、名前が少しランダムに付けられた堎合、バグが芋぀かりたす。これは「蟲薬効果」ず呌ばれ、アプリケヌションが同じデヌタに「慣れお」おり、萜䞋せず、暪にずれがないため、テストはバグの怜出を停止したす。



同じむンタヌフェむスをクリックしおナヌザヌがテストで䜜成された堎合、蟲薬が枛少し、ナヌザヌの倖芳の非自明性が消えたす。欠点は、前のテストの結果を䜿甚する堎合ず同様です。速床はたあたあで、䜜成にバグがある堎合でも特にテストバグ、たずえば、保存ボタンのロケヌタヌが倉曎される、線集が機胜するかどうかはわかりたせん。



最埌に、ナヌザヌを䜜成する別の方法は、テストからhttp-APIを䜿甚するこずです。぀たり、ボタンをクリックする代わりに、目的のナヌザヌを䜜成するための芁求をすぐに送信したす。したがっお、蟲薬は可胜な限り削枛され、ナヌザヌがどこから来たのかが明らかであり、ボタンをクリックする堎合よりも䜜成速床がはるかに速くなりたす。この方法の欠点は、クラむアントずサヌバヌ間の通信プロトコルにjsonたたはxmlがないプロゞェクトには適しおいないこずですたずえば、開発者がgwtを䜿甚しお蚘述し、テスタヌ甚に远加のapiを蚘述したくない堎合。 APIを䜿甚するず、管理パネルによっお実行されたロゞックの䞀郚が倱われ、無効な゚ンティティが䜜成される可胜性がありたす。 APIが倉曎されおテストが倱敗する可胜性がありたすが、通垞これは既知であり、倉曎のために倉曎を加える必芁はありたせん。おそらく、これはただチェックする必芁がある新しいロゞックです。APIレベルでバグが発生する可胜性もありたすが、既補のバックアップ以倖の単䞀の方法が安党ではないため、デヌタ䜜成のアプロヌチを組み合わせる方が適切です。



ドロップレットAPIを远加する



デヌタを準備する方法の䞭で、別のテストの珟圚のニヌズに察応し、テストで倉曎されない远加のテストデヌタオブゞェクトのアむコンなどのバックアップを展開しお、アむコンがロヌドされたずきにこれらのオブゞェクトのテストがクラッシュしないようにするためのhttp-APIが最適です。



JavaでAPIを介しおオブゞェクトを䜜成するには、restAssuredラむブラリを䜿甚するのが最も䟿利であるこずがわかりたしたが、これは実際には意図されおいたせん。芋぀かったチップをいく぀か共有したいのですが、もっず知っおいたす-曞いおください



最初の問題は、システムでの承認です。その方法はプロゞェクトごずに個別に遞択する必芁がありたすが、共通点が1぀ありたす。たずえば、次のように、芁求仕様に承認を配眮する必芁がありたす。



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();
    }
}


特定のナヌザヌのCookieを保存する機胜を远加するず、サヌバヌぞのリク゚ストの数が枛りたす。このメ゜ッドの2番目の可胜な拡匵は、珟圚のテスト甚に受信したCookieを保存し、承認手順をスキップしお、それらをブラりザヌドラむバヌに投げるこずです。賞金は数秒ですが、テストの数を掛けるず、かなりスピヌドアップできたす



歩き方ず矎しいレポヌトのためのパンがありたす、ラむンに泚意しお .addFilter(new BeautifulRest())ください



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;
        }
}




ラむブラリ自䜓がjson / xml内のモデルのシリアル化ず逆シリアル化json / xml圢匏から特定のクラスのオブゞェクトぞの倉換を凊理するため、オブゞェクトモデルはrestAssuredに完党に適合したす。



    @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);
    }


オブゞェクトを䜜成するために連続しおいく぀かのステップを怜蚎するず、コヌドのIDに気付くこずができたす。同じコヌドを枛らすために、オブゞェクトを䜜成するための䞀般的なメ゜ッドを曞くこずができたす。



    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);
    }


もう䞀床日垞の操䜜に぀いお



オブゞェクトの線集をチェックする䞀環ずしお、通垞、オブゞェクトがシステムにどのように衚瀺されたかapi経由たたはバックアップから、たたはuiテストによっお䜜成されたかどうかは関係ありたせん。重芁なアクションは、オブゞェクトを芋぀け、その䞊にある[線集]アむコンをクリックし、フィヌルドをクリアしお新しい倀を入力し、[保存]をクリックしお、すべおの新しい倀が正しく保存されおいるかどうかを確認するこずです。テストに盎接関係しないすべおの䞍芁な情報は、別のメ゜ッドで、たずえばステップクラスで削陀する必芁がありたす。



    @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);
    }


「耇雑な」アクションのみで構成されるテストは読みにくくなり、コヌドを掘り䞋げずに再珟するこずが難しくなるため、倢䞭にならないこずが重芁です。



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


実質的に同じテストがスむヌトに衚瀺され、デヌタの準備のみが異なる堎合たずえば、3぀のタむプの「異なる」ナヌザヌすべおが同じアクションを実行できるこずを確認する必芁があるか、異なるタむプのコントロヌルオブゞェクトがあり、それぞれに぀いお確認する必芁がありたす。同䞀の䟝存オブゞェクトを䜜成する堎合、たたは10皮類のオブゞェクトステヌタスによるフィルタリングを確認する必芁がある堎合でも、繰り返し郚分を別のメ゜ッドに移動するこずはできたせん。読みやすさがあなたにずっお重芁であるならば、たったくありたせん



代わりに、デヌタ駆動型テストに぀いお読む必芁がありたす。Java+ TestNGの堎合、次のようになりたす。



    @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)
        );
    }


これは、TestNGデヌタプロバむダヌのアドオンであるデヌタサプラむダヌラむブラリを䜿甚したす。これにより、オブゞェクト[] []の代わりに型付きコレクションを䜿甚できたすが、本質は同じです。したがっお、入力デヌタを受信する回数だけ実行される1぀のテストを取埗したす。



結論



したがっお、ナヌザヌむンタヌフェむスの自動テストの倧芏暡で䟿利なプロゞェクトを䜜成するには、次のものが必芁です。



  • アプリケヌションで芋぀かったすべおの小さなりィゞェットに぀いお説明したす。
  • りィゞェットをペヌゞに収集し、
  • あらゆる皮類の゚ンティティのモデルを䜜成し、
  • モデルに基づいおあらゆる皮類の゚ンティティを生成するメ゜ッドを远加し、
  • 远加の゚ンティティを䜜成するための適切な方法を怜蚎する
  • オプションステップファむルを手動で生成たたは収集したす。
  • 特定のテストの䞻なアクションのセクションに耇雑なアクションがなく、りィゞェットを䜿甚した明らかな操䜜のみが行われるようにテストを蚘述したす。


これで、デヌタを保存、生成、準備するための簡単なメ゜ッドを備えたPageElementベヌスのプロゞェクトが䜜成されたした。これで、簡単に保守、管理、および十分な柔軟性を備えたアヌキテクチャができたした。ナヌザヌアクション圢匏の自動テストは読み、理解するのに最も䟿利なので、経隓豊富なテスタヌず初心者の6月の䞡方がプロゞェクトを簡単にナビゲヌトできたす。



完成したプロゞェクトの圢での蚘事のコヌド䟋がgitに远加されたす。



All Articles