ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ 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

LANSAREA NUMĂRULUI 87

Prezentări articole și
Panel: Project management

Joi, 19 Septembrie, ora 18:00
Hugo (The Office), Cluj-Napoca

Înregistrează-te

Facebook Meetup

Conferință

Sponsori

  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects