Test Automation

L'importanza dei regression test

In questo articolo parleremo di qualcosa che nel mondo dell’informatica si fa fatica a far recepire come un’attività indispensabile nel ciclo di vita di un progetto software.  

La storia di ogni singolo progetto narra sempre di una fase in cui il progetto stesso potrebbe subire delle perdite: la fase di test e, in particolare, i famigerati “regression test”.

Quante volte ci siamo trovati a rilasciare una change request o una modifica presso il cliente facendo fallire qualcosa che prima del nostro rilascio funzionava egregiamente da un'altra parte?

Bene, per venire incontro al team ideale, che afferma di lavorare secondo metodologie ben definite dal SDL (Software Development Lifecycle), esistono diversi strumenti o framework che possono aiutarci nel testare sia le parti software lato codice, gli unit test, che lato interfaccia, gli UI test.

Noi prenderemo in considerazione la fase di test della UI parlando di Selenium, un engine che aiuta a testare in maniera automatizzata l’interfaccia grafica di applicazioni Web e non solo.

L’home page di Selenium afferma:

Selenium automates browser.

Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) be automated as well.

Quindi, come possiamo immaginare, con Selenium non si parla solo di uso legato allo sviluppo di test, ma è nato soprattutto dalla necessità di creare test automatizzati, riutilizzabili, adattabili e robusti, supportati dai browser più importanti, che è possibile successivamente impacchettare e rendere scalabili quanto si vuole.

I linguaggi supportati da Selenium sono molteplici e data la sua natura estendibile e flessibile, nel corso degli anni sono stati sviluppati molti framework basati su questo engine. Proprio per questo motivo nulla ci vieta di creare framework ad-hoc per il testing delle nostre applicazioni.

Il team Selenium ha affidato il compito di implementare le azioni del browser ai vari vendor in circolazione, in questo modo è riuscito a concentrarsi anche sull’aspetto del testing distribuito e parallelo Cross-Browser, dando la possibilità di utilizzare tool come SeleniumGrid, che è un hub che controlla N nodi remoti sui quali è installato l’engine. Questo hub riesce a gestire l’esecuzione di test in maniera parallela su varie macchine remote.

Selenium si basa sulle seguenti componenti:

  • BrowserDriver
  • Actions
  • Locators
  • Implicit/Explicit Waits
  • JavascriptExecutor
  • ExpectedConditions

BrowserDrivers

Come detto in precedenza, ogni vendor sviluppa una propria implementazione di Selenium IWebDriver. L’implementazione permette di richiedere un listener che si occupa di fare da ponte tra il codice e le azioni eseguite sul browser, passando da eventi di sistema a basso livello.

Ogni BrowserDriver offre la possibilità di aggiungere opzioni di avvio.

es.

ChromeOptions co = new ChromeOptions();

co.AddArgument("--start-maximized");

IWebDriver driver = new ChromeDriver(co);

Esempi di BrowserDrivers sono:

  • Google – ChromeDriver
  • Microsoft – InternetExplorerDriver
  • Mozilla – FirefoxDriver
  • Safari – Apple

Actions

Sono le principali azioni che si eseguono normalmente all’interno di un qualsiasi browser, ad esempio

  • Navigate(“www.google.com”)
  • FindElement()
  • Click()
  • SendKeys()
  • IsVisible()
  • Submit()

Locators

Vengono utilizzati da Selenium per capire su quali elementi eseguire azioni comuni. Come facciamo però a capire quali Locators utilizzare? Un buon locator è unico, descrittivo e difficile che cambi nel tempo, inoltre, esistono Locators che rendono la vita più facile a Selenium rispetto ad altri, perché vengono trovati più facilmente sulla pagina. Ovviamente possono capitare casi in cui ci si trova costretti ad utilizzarne alcuni rispetto ad altri.

Detto questo possiamo così suddividere le varie categorie di locators:

  • Locators ottimi: Id, ClassName
  • Locators buoni: CssSelector, XPath
  • Locators scarsi: Link Text, Partial Link Text, Tag Name

Non è complicato trovarli, bisogna ispezionare la pagina, trovare l’elemento con il quale si vuole interagire e verificarlo tramite console del browser, oppure utilizzando altri tools.

Sono identificati da una classe statica "By”. La ricerca dell’elemento può partire sia dall’intera pagina, sia da un sottoelemento della pagina, come risultato possono esserci singoli elementi oppure collezioni di elementi.

Es.

public void Login(string username, string password)

{

    IWebDriver driver = new ChromeDriver();

 

    IWebElement loginContainer = driver.FindElement(By.Id("loginContainerId"));

 

    IWebElement usernameTextbox = loginContainer.FindElement(By.Id("usernameTextboxId"));

    usernameTextbox.Clear();

    usernameTextbox.SendKeys(username);

 

    IWebElement passwordTextbox = loginContainer.FindElement(By.Id("passwordTextboxId"));

    passwordTextbox.Clear();

    passwordTextbox.SendKeys(password);

 

    IWebElement loginButton = loginContainer.FindElement(By.Id("loginButtonId"));

    loginButton.Click();

}

Implicit/Explicit Waits

Esistono tre modi per far sì che la navigazione resti in attesa dei nostri test:

  • Sleep() – SCONSIGLIATO! Inserendo un Thread.Sleep() forziamo i nostri test ad aspettare sempre l’intero lasso di tempo specificato.
  • Implicit Wait – dopo aver creato un’istanza del WebDriver si può specificare un tempo massimo di attesa per Selenium. Questo vuol dire che ogni volta che Selenium fallisce un’azione eseguita, ci riprova ogni 200 millisecondi, se l’azione va a buon fine passa alla successiva, altrimenti, se raggiunge il tempo limite, restituisce un errore di Timeout .
  • Explicit Waits – sono molto simili agli Implicit Wait, in questo caso però possono essere applicati limiti di tempo diversi per singola azione. 

Es.

// Implicit Wait

IWebDriver driver = new ChromeDriver();

driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);

// Explicit Wait

IWebDriver driver = new ChromeDriver();

WebDriverWait shortWait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));

WebDriverWait mediumWait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

WebDriverWait longWait = new WebDriverWait(driver, TimeSpan.FromMinutes(1));

 

// Riprova ogni 200 millisecondi per 2 secondi

shortWait.Until((d) =>

{

    return d.FindElement(By.Id("textboxId"));

});

// Riprova ogni 200 millisecondi per 10 secondi

mediumWait.Until((d) =>

{

    return d.FindElement(By.Id("textboxId"));

});

// Riprova ogni 200 millisecondi per 60 secondi

longWait.Until((d) =>

{

    return d.FindElement(By.Id("textboxId"));

});

Javascript Executor

Selenium offre la possibilità di iniettare ed eseguire codice Javascript all’interno delle pagine. È molto utile anche per effettuare controlli quando è usato insieme ad Explicit Waits.

Es. 

// Javascript Executor, controlla se il document.ReadyState = complete

IWebDriver driver = new ChromeDriver();

WebDriverWait shortWait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));

IJavaScriptExecutor jsExecutor = null;

if (driver is IJavaScriptExecutor)

    jsExecutor = driver as IJavaScriptExecutor;

 

shortWait.Until(d =>

    jsExecutor.ExecuteScript("return document.readyState").Equals("complete"));

ExpectedConditions

Set di condizioni comuni. Sono utilizzate all’interno degli Explicit Waits.

// Verifica ogni 200 millisecondi (fino a 5 secondi) se il pulsante è visibile e cliccabile

IWebDriver driver = new ChromeDriver();

WebDriverWait shortWait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));

 

shortWait.Until(ExpectedConditions.ElementToBeClickable(By.Id("buttonId")));

Test_GoogleSearch

In questo Unit Test verificheremo che la pagina di React Consulting sia il primo risultato di Google.

Effettueremo i seguenti passi:

  • Navigheremo su http://www.google.com
  • Scriveremo “React Consulting” nella textbox di ricerca
  • Cliccheremo pulsante Cerca
  • Navigheremo sul primo risultato
  • Verificheremo che l’url dopo la navigazione sia http://www.reactconsulting.it/

Prerequisiti:

  • Browser Chrome installato
  • Visual Studio dalla versione 2010

Step 1: Aggiungere un progetto di tipo “Unit Test Project (.Net Framework)”

  • Aprire Visual Studio
  • File -> New -> Project

.Netframework

Step 2: Aggiungere i package necessari tramite Nuget

  • Tasto destro sulla Solution
  • Manage NugetPackages for Solution
  • Aggiungere
    • WebDriver (Selenium WebDriver)
    • Chrome.WebDriver (BrowserDriver)
    • WaitHelpers (ExpectedConditions)

Nuget

Step 3: Scrivere un test che esegua le seguenti azioni

using System;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using OpenQA.Selenium;

using OpenQA.Selenium.Chrome;

using OpenQA.Selenium.Support.UI;

using SeleniumExtras.WaitHelpers;

 

namespace Selenium.Test

{

    [TestClass]

    public class GoogleSearchTests

    {

        private IWebDriver _driver;

        private WebDriverWait _wait;

 

        [TestInitialize]

        public void Setup()

        {

            ChromeOptions co = new ChromeOptions();

            co.AddArgument("--start-maximized");

            _driver = new ChromeDriver(co);

            _wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(5));

            _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);

        }

 

        [TestCleanup]

        public void ShutDown()

        {

            _driver.Quit();

        }

 

        [TestMethod]

        public void GoogleSearch()

        {

            _driver.Navigate().GoToUrl("http://www.google.com");

 

            IWebElement searchTextbox = _driver.FindElement(By.Id("lst-ib"));

            searchTextbox.Clear();

            searchTextbox.SendKeys("React Consulting");

 

            IWebElement searchButton = _driver.FindElement

                (By.CssSelector("#tsf > div.tsf-p > div.jsb > center > input[type='submit']:nth-child(1)"));

            searchButton.Submit();

 

            IWebElement anchorFirstResult = _driver.FindElement

                (By.CssSelector("#rso > div:nth-child(1) > div > div > div > div > div.r > a"));

            _driver.Navigate().GoToUrl(anchorFirstResult.GetAttribute("href"));

 

            _wait.Until(ExpectedConditions.UrlToBe("http://www.reactconsulting.it/"));

        }

    }

}

Step 4: Run Test

  • Dal menu di Visual Studio, Test -> Run -> AllTests

Run-test

Step 5: Verificare se il test è andato a buon fine

Test-buon-fine

Design Patterns

PageObjects

La strategia più comune è quella di interporre un layer (PageObjects) tra l’applicazione che si sta testando ed i test, in questo modo, se ci dovessero essere cambiamenti all’applicazione, non bisognerà modificare i test direttamente, ma il layer PageObjects. Questi ultimi identificano la pagina che si sta testando ed il mapping dei locators e dei metodi di utility utilizzati per i test.

All’interno dei test sarà sufficiente istanziare un PageObject ed utilizzare i suoi metodi.
Questa strategia favorisce anche la leggibilità del codice, basti pensare ad un PageObject di esempio denominato GmailPage, nella quale sono mappati “textboxusername”, “textboxpassword” e “buttonLogin”. A questo punto, se volessimo sviluppare un test di login di Gmail, basterebbe avviare il driver, istanziare un oggetto di tipo GmailPage e chiamare il metodo Login passandogli come parametro password ed username.

Facade

Questo design pattern è basato sulla strategia PageObject, con l’aggiunta dell’applicazione di un meccanismo di Dependency Inversion. Le Facade contengono N interfacce che rappresentano i PageObject, a seconda della pagina che passiamo alle interfacce il flusso rimane uguale ma il mapping dei locators e delle validazioni cambia. Basti pensare a 100 pagine di login diverse che hanno sempre lo stesso flusso, ogni pagina implementerà l’interfaccia contenuta nella facade (ad esempio ILoginPage), a questo punto all’interno delle classi di test sarà sufficiente chiamare la facade istanziando ogni volta una pagina diversa. In questo caso non abbiamo riscritto 100 volte il flusso di login ma abbiamo lavorato unicamente sul mapping dei locators.

Conclusioni

Sebbene Selenium offra molte funzionalità semplici ed utili per la scrittura di test automatizzati, è da considerare comunque uno strumento, sta a noi saperlo sfruttare. La sua peculiarità infatti non è solo “l’automagia” che ci offre ma la sua estendibilità e flessibilità. Inoltre, la scelta della strategia di testing è la parte più complessa ed importante del Testing Automation.

 

Riferimenti:

https://www.seleniumhq.org/

comments powered by Disqus