ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 57
Abonament PDF

Test Factory – următorul nivel de abstractizare a testelor automate

Darius Bozga
Senior QA Engineer – Payments @ Betfair



PROGRAMARE

Fiecare modificare adusă aplicației este urmată de schimbări în unul sau mai multe teste. Ce se întâmplă când o nouă funcţionalitate aduce schimbări la mai multe data providere? În timp, data providerele din testele automate încep să devină tot mai încărcate, pline de date şi tot mai greu de urmărit.

Să presupunem că avem de testat un magazin virtual, care suportă un tip de cont ( cont normal) şi se poate accesa din trei domenii de internet diferite. Acest magazin este prezentat doar în limba engleză.

Un set primar de teste ar arăta în felul următor:

@BeforeClass()
public void beforeClass() {
    regularAccount = new Account();
    regularAccount.setUsername("Regular User");
    regularAccount.setLanguage("English");

}

@DataProvider(name = "getLoginDetails")
public Object[][] getLoginDetails(){
return new Object[][]{
            {"url .com",regularAccount},
            {"url .ro",regularAccount},
            {"url .uk",regularAccount},};
}

@DataProvider(name = "purchaseItemDetails")
public Object[][] getPurchaseItemDetails(){

    return new Object[][]{
            {"Phone",regularAccount},
            {"Sink",regularAccount},};
}

@Test(dataProvider = "getLoginDetails")
public void verifyLogin(String url, Account account) {
    System.out.println("Accessing the url "+url);
    System.out.println("Login with account " + 
     account.getUsername())
}

@Test(dataProvider = "purchaseItemDetails")
public void verifyUserCanPurchaseItems(String item, 
  Account account) {
    System.out.println("Login with username "
      + account.getUsername());
    System.out.println("Purchassing item: "+item);
}

@Test()
public void verifyStoreLocalization() {
    System.out.println("Login with user "
       +regularAccount.getUsername());

    System.out.println("Verify section is in "
    + "correct language " 
    +regularAccount.getLanguage().equals("English"));
}

@Test()
public void verifyItemIsDisplayedOnStore(){
    System.out.println("Verify desktop item is"
    + "displayed in store");
}

Pentru o viață cât mai lungă a produselor software se aduc modificări sau îmbunătățiri și de asemenea caracteristici noi. În cazul magazinului nostru virtual, următorul pas ar fi implementarea unei pagini de administrare la care au acces doar anumite tipuri de conturi special făcute pentru a adăuga sau modifica produse din magazin.

Cu această schimbare adusă, va apărea un nou tip de cont, contul de Administrator. Această îmbunătățire este necesară deoarece un client obişnuit nu are voie să intre pe această pagină de administrare şi de asemenea un administrator nu are voie să cumpere produse.

Dacă luăm ca referință primul Data Provider din teste, ar trebui să dublăm numărul de linii pentru a acoperi toate scenariile. După această modificare din trei linii, câte avea inițial, a ajuns să conțină șase linii:

@DataProvider(name = "getLoginDetails")
public Object[][] getLoginDetails(){
return new Object[][]{
            {"url .com",regularAccount},
            {"url .ro",regularAccount},
            {"url .uk",regularAccount},
            {"url .com",adminAccount},
            {"url .ro",adminAccount},
            {"url .uk",adminAccount},};
}

Ce s-ar întâmpla dacă s-ar implementa noi caracteristici care să deschidă magazinul virtual și pentru alte domenii sau alte tipuri de conturi sau chiar să suporte și alte limbi? Am ajunge să dublăm liniile din data provideri pentru fiecare modificare adusă pentru a acoperi scenariile noi.

Dacă abstractizăm testele putem să evităm schimbările acestea, care pot duce la modificări de comportament şi alterări de scenarii dacă urmăm câteva reguli simple:

  1. Identificarea de teste comune care vor fi rulate pentru toate modificările și de asemenea, identificarea de valori care sunt comune şi pot fi trimise scenariilor de test.

    a. În cazul nostru, putem crea un obiect de tip Account care să conţină detaliile de logare ale contului, paginile la care are acces contul şi limba pe care o suportă contul. Putem scoate o listă de domenii de unde au acces tipurile de conturi, listă de pagini care sunt implementate în aplicație.

  2. Eliminarea de valori predefinite din data provideri, determinând data providerii să își creeze datele dinamic bazat pe liste de valori sau matrice.

    a. Ne ajută pentru a putea defini un set de reguli care se aplică în general pentru toate tipurile de conturi.

După ce am urmat regulile de mai sus, am reuşit să obţin următorii parametri necesari:

După ce am mutat toate particularitățile fiecărui tip de cont în interiorul obiectului de Account, putem să trimitem un număr nelimitat de tipuri de conturi, fiecare cu diferite particularități, iar testele vor fi rulate pentru fiecare cont în parte acoperind toate scenariile posibile. Cu ajutorul acestei modificări, am eliminat nevoia de a interveni în cadrul fiecărui Data provider la orice modificare minoră a aplicației sau eliminarea nevoii de a duplica clasele de test pentru fiecare tip de cont.

Ca urmare a schimbărilor descrise mai sus, trimitem ca parametri de intrare la nivelul constructorului obiecte de tip Account, lista de domenii şi lista de pagini disponibile ne rezultă potenţialul de a rula testele pentru fiecare modificare în parte deoarece datele se creează dinamic.

Clasa de teste se transformă în următoarea clasă:

public OnlineStoreSanityTests(Account account, List listOfDomains, List listOfPages) {
    this.account = account;
    this.listOfDomains = listOfDomains;
    this.listOfPages = listOfPages;
}

@DataProvider(name = "getLoginDetails")
public Object[][] getLoginDetails()
{
    Object[][] objectArray = new Object[listOfDomains.size()][];
    for (int i = 0; i < listOfDomains.size(); i++) {
        objectArray[i] = new Object[2];
        objectArray[i][0] = listOfDomains.get(i);
        objectArray[i][1] = account;
    }
    return objectArray;
}

@DataProvider(name = "getPermissionDetails")
public Object[][] getPermissions()
{
    Object[][] objectArray = 
        new Object[listOfPages.size()][];
    for (int i = 0; i < listOfPages.size(); i++) {
        objectArray[i] = new Object[2];
        objectArray[i][0] = listOfPages.get(i);
        objectArray[i][1] = account;
    }
    return objectArray;
}

@DataProvider(name = "purchaseItemDetails")
public Object[][] getPurchaseItemDetails()
{
    return new Object[][]{
            {"Phone",account},
            {"Sink",account},
    };
}

@Test(dataProvider = "getLoginDetails")
public void verifyLogin(String url, Account account) {
    System.out.println("Accessing the url "+url);
    System.out.println("Login with account " 
      + account.getUsername());
    System.out.println("Login done with success");
}

@Test(dataProvider = "purchaseItemDetails")
public void verifyUserCanPurchaseItems(String item, Account account) {
    System.out.println("Login with username "
     + account.getUsername());
    System.out.println("Purhcasing item: "+item);
}

@Test()
public void verifyStoreLocalization() {
    System.out.println("Login with user "
     +account.getUsername());
    System.out.println("Verify section is in"
    + " correct language " +account.getLanguage());

}
@Test(dataProvider = "getPermissionDetails")
public void verifyUserPermissionsToSection(
String section, Account account) {
    System.out.println("Login with user "
    + account.getUsername());
    System.out.println("Access section "  
    + isUserAllowedToAccessSection(section,account));

}

@Test()
public void verifyItemIsDisplayedOnStore(){
    System.out.println("Verify desktop is" +
    +" displayed in store");

private boolean isUserAllowedToAccessSection(String section, Account account){
    return section.contains(
      account.getAccessLevel());
}

Întrebările care sunt evidente în acest moment sunt următoarele: cum rulăm aceste teste? unde setăm tipurile de conturi, listele de domenii şi pagini?

Cu ajutorul modelului de Test Factory, avem posibilitatea de a crea dinamic teste bazate pe valori diferite şi scenarii identice. Modelul Test factory este similar cu funcţionalitatea Data Providerilor pentru scenarii individuale de teste doar extinse la nivel de clasă de scenarii test. După ce am ajuns la acest nivel, pentru fiecare implementare nouă de tipuri de cont sau domenii sau pagini, trebuie doar adăugată valoarea nouă în cadrul anotaţie de tip \@Factory, iar testele vor lua în considerare aceste modificări la rulare generând câte un set de teste pentru fiecare valoare nouă.

O clasă de tip Test Factory arată în felul următor:

@Test(groups = "run")
public class OnlineStoreTestFactory {
  private static List listOfDomains= 
      new ArrayList(); 

private static List listOfPages= 
      new ArrayList();

 static {
        listOfDomains.add("url .com");
        listOfDomains.add("url .ro");
        listOfDomains.add("url .uk");
        listOfPages.add("Store Section");
        listOfPages.add("Management Section");
        listOfPages.add("Admin Section");
    }

@Factory()
public Object[] factoryMethod() {
return new Object[]{
   new OnlineStoreSanityTests(
      generateAccountForTestOne(),
      listOfDomains,listOfPages),
      new OnlineStoreSanityTests (
         generateAccountForTestTwo(),
         listOfDomains,listOfPages),
      };
    }
    public Account generateAccountForTestOne() {
        Account account = new Account();
        account.setUsername("Regular User");
        account.setLanguage("English");
        account.setAccessLevel("Store");
        return account;
    }
    public Account generateAccountForTestTwo() {
        Account account = new Account();
        account.setUsername("Admin User");
        account.setLanguage("Spanish");
        account.setAccessLevel("Admin");
        return account;
    }

}

În felul acesta am izolat clasa de scenarii şi datele de test. Ca urmare a acestor modificări avem o imagine corectă asupra a ceea ce vrem să vizualizăm, date sau scenarii de test. Când vrem să vedem un scenariu, ne uităm în clasa de test şi când vrem să vedem datele cu care vor rula testele, ne uităm în clasa de Factory, fiind foarte uşor de urmărit. Astfel, eliminăm nevoia de a scrola prin multiple data providere.

Un exemplu de cont cu care vor rula testele:

Account account = new Account();
account.setUsername("Regular User");
account.setLanguage("English");
account.setAccessLevel("Store");
return account;

Putem rula clasele de test factory ca orice altă clasă de teste, iar dacă le punem în fişiere de suite se pot rula în paralel cu alte teste pentru a reduce timpul total de rulare.

Un exemplu de fişier de suite care vor rula testele noastre :

<suite name="Suite-A" verbose="1">
    <test name="test" group-by-instances="true" parallel="classes" >
        <classes>
            <class name="com.testFactory.example.OnlineStoreTestFactory"></class>
        </classes>
    </test>
</suite>

Codul folosit pentru exemplul acesta poate fi urmărit pe git hub.

Bibliografie

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects