Scrivere i test dell'interfaccia utente

Completato

In questa sezione si aiuta Andy e Amita a scrivere test Selenium che verificano i comportamenti dell'interfaccia utente descritti da Amita.

Amita esegue normalmente test su Chrome, Firefox e Microsoft Edge. Qui, fai lo stesso. L'agente ospitato da Microsoft che verrà usato è preconfigurato per l'uso con ognuno di questi browser.

Recuperare il ramo da GitHub

In questa sezione si recupera il selenium ramo da GitHub. È quindi possibile eseguire il check-out o passare a tale ramo. Il contenuto del ramo consente di 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 da cui iniziare.

  1. Aprire il terminale integrato in Visual Studio Code.

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

    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 nuovamente.

    Tenere presente che upstream fa riferimento al repository Microsoft GitHub. La configurazione Git del progetto comprende l'upstream remoto perché è stata configurata tale relazione. È possibile configurarlo quando è stato creato un fork del progetto dal repository Microsoft e 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ù affidabile potrebbe specificare i rami che partecipano al 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 usata solo quando si ha una richiesta pull, quando si ha una versione finale candidata o quando si esegue il merge del codice in 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 di unit test

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

Lei e Andy lavoreranno insieme per scrivere i test di Selenium. Andy ha già configurato un progetto NUnit vuoto. Nel corso del processo, fanno riferimento alla documentazione di Selenium, alcune esercitazioni online e le note che hanno preso quando Amita ha eseguito manualmente i test. Alla fine di questo modulo sono disponibili altre risorse per facilitare l'esecuzione del processo.

Esaminiamo il processo usato da Andy e Amita per scrivere i test. È possibile seguire la 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. È possibile scegliere di seguire una delle diverse convenzioni di denominazione. Chiamiamo la classe 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: A questo punto ci serve una variabile membro IWebDriver. IWebDriver è l'interfaccia di programmazione usata per avviare un Web browser e interagire con il contenuto della pagina Web.

Amita: Ho sentito parlare di interfacce nella programmazione. Puoi dirmi di più?

Andy: si pensi a un'interfaccia come una specifica o un progetto per il comportamento di un componente. Un'interfaccia fornisce i metodi o i comportamenti di tale componente. L'interfaccia non fornisce tuttavia alcun dettaglio sottostante. 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 classi che implementano questa interfaccia:

Diagram of the IWebDriver interface, its methods, and concrete classes.

Il diagramma mostra tre dei metodi forniti IWebDriver : Navigate, FindElemente Close.

Le tre classi illustrate di seguito, ChromeDriver, FirefoxDrivere EdgeDriver, implementano IWebDriver e i relativi metodi. Esistono altre classi, ad esempio SafariDriver, che implementano IWebDriveranche . Ogni classe driver può controllare il Web browser rappresentato.

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 HomePageTest classe :

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

Andy: A questo punto dobbiamo definire un costruttore per la nostra classe di test. Il costruttore viene chiamato quando NUnit crea un'istanza di questa classe. Come argomento, il costruttore accetta la stringa associata alle fixture di test. Ecco l'aspetto del codice:

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

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

Andy: Aggiungiamo la variabile membro browser in modo da poter usare il nome del browser corrente nel codice di installazione. Scrivere quindi il codice di installazione.

Definire il metodo setup

Andy: A questo punto è necessario assegnare la variabile membro IWebDriver a un'istanza della classe che implementa questa interfaccia per il browser su cui viene eseguito il test. Le ChromeDriverclassi , FirefoxDrivere EdgeDriver implementano questa interfaccia rispettivamente per Chrome, Firefox e Edge.

Creiamo un metodo, denominato Setup, che imposta la variabile driver. Viene usato 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. A questo punto si aggiungerà 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 driver accetta un percorso facoltativo per il software driver Selenium deve controllare il Web browser. Successivamente, verrà illustrato il ruolo delle variabili di ambiente illustrate di seguito.

In questo esempio, il EdgeDriver costruttore 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

Scrivere due metodi helper, uno per ogni azione. Si inizierà con il metodo che trova un elemento nella pagina.

Scrivere il metodo helper FindElement

Quando si individua un elemento nella pagina, in genere è in risposta a 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 per attendere la WebDriverWait visualizzazione di un determinato elemento e per essere pronti per 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 FindElement metodo helper. 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: A questo punto scriviamo un metodo helper che fa clic sui collegamenti. Selenium offre alcuni modi per scrivere tale metodo. Uno di essi è l'interfaccia IJavaScriptExecutor . Con esso, è possibile fare clic su collegamenti a livello di codice usando JavaScript. Questo approccio funziona bene perché può fare clic su collegamenti senza prima scorrerli nella visualizzazione.

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

Andy e Amita codificano il ClickElement metodo helper. 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 generali da usare in quasi tutti i test. È possibile aggiungere altri metodi helper in un secondo momento in quanto sono necessari.

Definire il metodo di test

Andy: A questo punto possiamo definire il metodo di test. In base ai test manuali eseguiti in precedenza, chiamare questo metodo ClickLinkById_ShouldDisplayModalById. È consigliabile assegnare nomi descrittivi ai metodi di test che definiscono esattamente il risultato del test. Qui vogliamo fare clic su un collegamento, definito in base al relativo attributo id. 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, definiamo cosa deve fare questo test.

Amita: Posso gestire questa parte. Si vuole:

  1. Individuare il collegamento in base al relativo id attributo e quindi fare clic sul collegamento.
  2. Individuare l'oggetto modale risultante.
  3. Chiudere il modale.
  4. Verificare che il modale sia stato visualizzato correttamente.

Andy: Grande. Sarà anche necessario gestire alcune altre cose. Ad esempio, è necessario ignorare il test se il driver non è stato caricato ed è necessario chiudere il modale solo se il modale è stato visualizzato correttamente.

Dopo aver riempito le tazze di caffè, Andy e Amita aggiungono codice al loro metodo di test. Usano i metodi helper scritti 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. Questa operazione verrà gestita successivamente.

Definire i dati del test case

Andy: in NUnit è possibile fornire dati ai test in diversi modi. Qui usiamo l'attributo TestCase. Questo attributo accetta argomenti che in seguito passa di nuovo al metodo di test durante l'esecuzione. È possibile avere più TestCase attributi per ogni test di una funzionalità diversa dell'app. Ogni TestCase attributo produce un test case incluso nel report visualizzato alla fine di un'esecuzione della pipeline.

Andy aggiunge questi TestCase attributi 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 fare clic 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 su cui fare clic. Il secondo parametro è l'attributo id per la finestra modale che si prevede di visualizzare. Questi parametri corrispondono ai due argomenti stringa nel metodo di test.

Amita: Lo vedo. Con qualche pratica, penso di poter aggiungere i miei test. Quando è possibile visualizzare 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. Si eseguirà il commit e il push delle modifiche in GitHub e si noterà che si spostano nella pipeline solo dopo aver verificato che tutto funzioni. Eseguire ora i test in locale.