Scrivere i test dell'interfaccia utente

Completato

In questa sezione, Andy e Amita verranno aiutati a scrivere i test di Selenium che verificano i comportamenti dell'interfaccia utente descritti da Amita.

In genere, Amita esegue i test su Chrome, Firefox e Microsoft Edge. In questa sezione, si eseguirà la stessa operazione. L'agente ospitato da Microsoft usato è preconfigurato per l'uso con ognuno di questi browser.

Recuperare il ramo da GitHub

In questa sezione si recupererà il ramo selenium da GitHub, quindi si eseguirà il check out o il passaggio a tale ramo. Il contenuto del ramo sarà utile per seguire i test scritti da Andy e Amita.

Questo ramo contiene il progetto Space Game usato nei moduli precedenti. Contiene anche una configurazione di Azure Pipelines per iniziare.

  1. Aprire il terminale integrato in Visual Studio Code.

  2. Per scaricare un ramo denominato selenium dal repository Microsoft, passare a quel ramo ed eseguire i comandi git fetch e git checkout seguenti:

    git fetch upstream selenium
    git checkout -B selenium upstream/selenium
    

    Suggerimento

    Se è stato seguito insieme al test manuale di Amita nell'unità precedente, è possibile che siano già stati eseguiti questi comandi. Se sono già stati eseguiti nell'unità precedente, è comunque possibile eseguirli di nuovo ora.

    Tenere presente che upstream fa riferimento al repository Microsoft GitHub. La configurazione Git del progetto riconosce il repository upstream remoto perché tale relazione è stata configurata quando è stata creata una copia del progetto tramite fork dal repository Microsoft ed è stato clonato in locale.

    In breve, si eseguirà il push di questo ramo al repository GitHub personale, noto come origin.

  3. Facoltativamente, in Visual Studio Code aprire il file azure-pipelines.yml . Acquisire familiarità con la configurazione iniziale.

    La configurazione è simile a quelle create nei moduli precedenti in questo percorso di apprendimento. Esegue la compilazione solo della configurazione Release dell'applicazione. Per brevità, omette anche i trigger, le approvazioni manuali e i test configurati nei moduli precedenti.

    Nota

    Una configurazione più solida potrebbe specificare i rami che fanno parte del processo di compilazione. Ad esempio, per verificare la qualità del codice, è possibile eseguire unit test ogni volta che si esegue il push di una modifica in qualsiasi ramo. È anche possibile distribuire l'applicazione in un ambiente che esegue test più completi. Questa distribuzione viene tuttavia eseguita solo quando si dispone di una richiesta pull, quando si ha un candidato per la versione o quando si unisce il codice a main.

    Per altre informazioni, vedere Implementare un flusso di lavoro di codice nella pipeline di compilazione usando Git e GitHub e i trigger della pipeline di compilazione.

Scrivere il codice degli unit test

Amita è entusiasta di imparare a scrivere il codice che controlla il Web browser.

Lei e Andy collaborano per scrivere i test di Selenium. Andy ha già configurato un progetto NUnit vuoto. Nel corso del processo, consultano la documentazione di Selenium, alcune esercitazioni online e gli appunti che hanno preso quando Amita ha eseguito manualmente i test. Alla fine del modulo sono disponibili altre risorse utili per completare il processo.

Esaminiamo il processo usato da Andy e Amita per scrivere i test. È possibile seguire questa procedura aprendo HomePageTest.cs nella directory Tailspin.SpaceGame.Web.UITests in Visual Studio Code.

Definire la classe HomePageTest

Andy: La prima cosa da fare è definire la classe di test. Possiamo scegliere tra diverse convenzioni di denominazione. Per la nostra classe usiamo il nome HomePageTest. In questa classe verranno inseriti tutti i test correlati alla home page.

Andy aggiunge questo codice a HomePageTest.cs:

public class HomePageTest
{
}

Andy: È necessario contrassegnare questa classe in public modo che sia disponibile per il framework NUnit.

Aggiungere la variabile membro IWebDriver

Andy: Successivamente, è necessaria una IWebDriver variabile membro. IWebDriver è l'interfaccia di programmazione utilizzata per avviare un Web browser e interagire con il contenuto delle pagine Web.

Amita: Ho sentito parlare di interfacce nella programmazione. Mi puoi spiegare meglio?

Andy: Si consideri un'interfaccia come una specifica o un progetto per il comportamento di un componente. Un'interfaccia fornisce i metodi, o comportamenti, di quel componente. L'interfaccia, però, non fornisce i dettagli sottostanti. L'utente o un altro utente creerebbe una o più classi concrete che implementano tale interfaccia. Selenium fornisce le classi concrete necessarie.

Questo diagramma mostra l'interfaccia IWebDriver e alcune delle classi che implementano questa interfaccia:

Diagramma dell'interfaccia IWebDriver, dei relativi metodi e delle classi concrete.

Il diagramma mostra tre dei metodi forniti da IWebDriver: Navigate, FindElement e Close.

Le tre classi illustrate di seguito,ChromeDriver , FirefoxDrivere EdgeDriver, implementano IWebDriver e i relativi metodi. Esistono anche altre classi, ad esempio SafariDriver, che implementano IWebDriver. Ogni classe driver è in grado di controllare il Web browser che rappresenta.

Andy aggiunge una variabile membro denominata driver alla HomePageTest classe, come segue:

public class HomePageTest
{
    private IWebDriver driver;
}

Definire le fixture di test

Andy: Vogliamo eseguire l'intero set di test su Chrome, Firefox e Edge. In NUnit è possibile usare le fixture di test per eseguire l'intero set di test più volte, una volta per ogni browser su cui si vuole eseguire il test.

In NUnit si usa l'attributo TestFixture per definire le fixture di test. Andy aggiunge queste tre fixture di test alla classe HomePageTest:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private IWebDriver driver;
}

Andy: Successivamente, è necessario definire un costruttore per la classe di test. Il costruttore viene chiamato quando NUnit crea un'istanza di questa classe. Come argomento, il costruttore accetta la stringa collegata alle fixture di test. Ecco il codice risultante:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private string browser;
    private IWebDriver driver;

    public HomePageTest(string browser)
    {
        this.browser = browser;
    }
}

Andy: È stata aggiunta la browser variabile membro in modo da poter usare il nome del browser corrente nel codice di installazione. Quindi, scriviamo il codice di installazione.

Definire il metodo di installazione

Andy: Successivamente, è necessario assegnare la IWebDriver variabile membro a un'istanza di classe che implementa questa interfaccia per il browser in cui si sta testando. Le classi ChromeDriver, FirefoxDriver e EdgeDriver implementano questa interfaccia rispettivamente per Chrome, Firefox ed Edge.

Creiamo un metodo, denominato Setup, che imposta la variabile driver. Usiamo l'attributo OneTimeSetUp per indicare a NUnit di eseguire questo metodo una volta per ogni fixture di test.

[OneTimeSetUp]
public void Setup()
{
}

Nel metodo Setup è possibile usare un'istruzione switch per assegnare la variabile membro driver all'implementazione reale appropriata, in base al nome del browser. Ora si aggiunge il codice:

// Create the driver for the current browser.
switch(browser)
{
    case "Chrome":
    driver = new ChromeDriver(
        Environment.GetEnvironmentVariable("ChromeWebDriver")
    );
    break;
    case "Firefox":
    driver = new FirefoxDriver(
        Environment.GetEnvironmentVariable("GeckoWebDriver")
    );
    break;
    case "Edge":
    driver = new EdgeDriver(
        Environment.GetEnvironmentVariable("EdgeWebDriver"),
        new EdgeOptions
        {
            UseChromium = true
        }
    );
    break;
    default:
    throw new ArgumentException($"'{browser}': Unknown browser");
}

Il costruttore per ogni classe di driver accetta un percorso facoltativo per il software del driver di cui Selenium ha bisogno deve controllare il Web browser. In seguito verrà spiegato il ruolo delle variabili di ambiente mostrate qui.

In questo esempio, il costruttore EdgeDriver richiede anche opzioni aggiuntive per specificare che si vuole usare la versione Chromium di Edge.

Definire i metodi helper

Andy: So che dobbiamo ripetere due azioni durante i test:

  • Ricerca di elementi nella pagina, ad esempio i collegamenti su cui si fa clic e le finestre modali che si prevede di visualizzare
  • Clic sugli elementi della pagina, ad esempio i collegamenti che aprono le finestre modali e il pulsante che chiude le singole finestre modali

Scriviamo due metodi helper, uno per ogni azione, iniziando da quello che trova un elemento nella pagina.

Scrivere il metodo helper FindElement

Quando si individua un elemento nella pagina, è in genere è il risultato di un altro evento, ad esempio il caricamento della pagina o l'immissione di informazioni da parte dell'utente. Selenium fornisce la classe WebDriverWait, che consente di attendere che una condizione sia vera. Se la condizione non è vera entro il periodo di tempo specificato, WebDriverWait genera un'eccezione o un errore. È possibile usare la classe WebDriverWait per attendere che un determinato elemento venga visualizzato e che sia pronto a ricevere l'input dell'utente.

Per individuare un elemento nella pagina, si usa la classe By. La classe By fornisce metodi che consentono di trovare un elemento in base al suo nome, al nome della sua classe CSS, al suo tag HTML o, in questo caso, in base al suo attributo id.

Andy e Amita codificano il metodo helper FindElement. Sembra questo codice:

private IWebElement FindElement(By locator, IWebElement parent = null, int timeoutSeconds = 10)
{
    // WebDriverWait enables us to wait for the specified condition to be true
    // within a given time period.
    return new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds))
        .Until(c => {
            IWebElement element = null;
            // If a parent was provided, find its child element.
            if (parent != null)
            {
                element = parent.FindElement(locator);
            }
            // Otherwise, locate the element from the root of the DOM.
            else
            {
                element = driver.FindElement(locator);
            }
            // Return true after the element is displayed and is able to receive user input.
            return (element != null && element.Displayed && element.Enabled) ? element : null;
        });
}

Scrivere il metodo helper ClickElement

Andy: Scrivere quindi un metodo helper che fa clic sui collegamenti. Selenium offre alcuni modi per scrivere questo metodo. Uno di essi è l'interfaccia di IJavaScriptExecutor. Consente di fare clic sui collegamenti a livello di programmazione tramite JavaScript. Questo è un approccio funzionale perché consente di fare clic sui collegamenti senza prima scorrere per visualizzarli.

ChromeDriver, FirefoxDriver e EdgeDriver implementano IJavaScriptExecutor. È necessario eseguire il cast del driver a questa interfaccia, quindi chiamare ExecuteScript per eseguire il metodo JavaScript click() sull'oggetto HTML sottostante.

Andy e Amita codificano il metodo helper ClickElement. Sembra questo codice:

private void ClickElement(IWebElement element)
{
    // We expect the driver to implement IJavaScriptExecutor.
    // IJavaScriptExecutor enables us to execute JavaScript code during the tests.
    IJavaScriptExecutor js = driver as IJavaScriptExecutor;

    // Through JavaScript, run the click() method on the underlying HTML object.
    js.ExecuteScript("arguments[0].click();", element);
}

Amita: Mi piace l'idea di aggiungere questi metodi helper. Sembrano abbastanza generici da poter essere usati in quasi tutti i test. Possiamo aggiungere altri metodi helper in un secondo momento, in base alle necessità.

Definire il metodo di test

Andy: A questo momento, è possibile definire il metodo di test. In base ai test manuali eseguiti in precedenza, chiamiamo questo metodo ClickLinkById_ShouldDisplayModalById. È buona norma assegnare ai metodi di test nomi descrittivi che definiscano esattamente ciò che fa il test. In questo caso si vuole selezionare un collegamento definito dal relativo id attributo. E vogliamo verificare che venga visualizzata la finestra modale corretta, mediante il relativo attributo id.

Andy aggiunge il codice di avvio per il metodo di test:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
}

Andy: Prima di aggiungere altro codice, è possibile definire le operazioni che il test deve eseguire.

Amita: Posso gestire questa parte. Dobbiamo:

  1. Individuare il collegamento in base al relativo id attributo e selezionare il collegamento.
  2. Individuare la finestra modale risultante.
  3. Chiudere la finestra modale.
  4. Verificare che la finestra modale sia stata visualizzata correttamente.

Andy: Benissimo. Dovremo anche gestire alcune altre cose. Ad esempio, dobbiamo ignorare il test se non è stato possibile caricare il driver e dobbiamo chiudere la finestra modale solo se è stata visualizzata correttamente.

Andy e Amita si mettono all'opera e aggiungono codice al metodo di test. Usano i metodi helper che hanno scritto per individuare gli elementi della pagina e fare clic su collegamenti e pulsanti. Il risultato è il seguente:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
    // Skip the test if the driver could not be loaded.
    // This happens when the underlying browser is not installed.
    if (driver == null)
    {
        Assert.Ignore();
        return;
    }

    // Locate the link by its ID and then click the link.
    ClickElement(FindElement(By.Id(linkId)));

    // Locate the resulting modal.
    IWebElement modal = FindElement(By.Id(modalId));

    // Record whether the modal was successfully displayed.
    bool modalWasDisplayed = (modal != null && modal.Displayed);

    // Close the modal if it was displayed.
    if (modalWasDisplayed)
    {
        // Click the close button that's part of the modal.
        ClickElement(FindElement(By.ClassName("close"), modal));

        // Wait for the modal to close and for the main page to again be clickable.
        FindElement(By.TagName("body"));
    }

    // Assert that the modal was displayed successfully.
    // If it wasn't, this test will be recorded as failed.
    Assert.That(modalWasDisplayed, Is.True);
}

Amita: La scrittura di codice ha un aspetto ottimale finora, ma come si connette questo test agli id attributi raccolti in precedenza?

Andy: Grande domanda. È il prossimo aspetto di cui ci occuperemo.

Definire i dati del test case

Andy: In NUnit è possibile fornire dati ai test in alcuni modi. Qui usiamo l'attributo TestCase. Questo attributo accetta argomenti che in seguito restituisce al metodo di test durante l'esecuzione. Può avere più attributi TestCase, ognuno dei quali testa una funzionalità diversa dell'app. Ogni attributo TestCase produce un test case che viene incluso nel report visualizzato alla fine di un'esecuzione della pipeline.

Andy aggiunge questi attributi TestCase al metodo di test. Questi attributi descrivono il pulsante Scarica gioco , una delle schermate di gioco e il giocatore più in alto nella classifica. Ogni attributo specifica due id attributi: uno per il collegamento da selezionare e uno per la finestra modale corrispondente.

// Download game
[TestCase("download-btn", "pretend-modal")]
// Screen image
[TestCase("screen-01", "screen-modal")]
// // Top player on the leaderboard
[TestCase("profile-1", "profile-modal-1")]
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{

...

Andy: Per ogni TestCase attributo, il primo parametro è l'attributo id per il collegamento da selezionare. Il secondo parametro è l'attributo id per la finestra modale che dovrebbe comparire. Questi parametri corrispondono ai due argomenti stringa nel metodo di test.

Amita: Lo vedo. Con un po' di pratica, credo di riuscire ad aggiungere i miei test. Quando potremo vedere questi test in esecuzione nella pipeline?

Andy: Prima di eseguire il push delle modifiche nella pipeline, verificare prima di tutto che il codice venga compilato ed eseguito in locale. Eseguiremo il commit e il push delle modifiche in GitHub per visualizzarle nella pipeline solo dopo aver verificato che tutto funzioni. Adesso eseguiamo i test in locale.