De UI-tests schrijven

Voltooid

In deze sectie helpt u Andy en Amita Selenium-tests te schrijven waarmee het gedrag van de gebruikersinterface wordt gecontroleerd dat Amita heeft beschreven.

Amita voert normaal gesproken tests uit op Chrome, Firefox en Microsoft Edge. Hier doe je hetzelfde. De door Microsoft gehoste agent die u gaat gebruiken, is vooraf geconfigureerd voor gebruik met elk van deze browsers.

De branch ophalen vanuit GitHub

In deze sectie haalt u de selenium vertakking op uit GitHub. Vervolgens checkt u die vertakking uit of schakelt u over naar die vertakking. De inhoud van de vertakking helpt u bij het volgen van de tests die Andy en Amita schrijven.

Deze vertakking bevat het Space Game-project waarmee u in eerdere modules hebt gewerkt. Het bevat ook een Azure Pipelines-configuratie om mee te beginnen.

  1. Open in Visual Studio Code de geïntegreerde terminal.

  2. Als u een vertakking wilt downloaden met de naam selenium uit de Microsoft-opslagplaats, schakelt u over naar die vertakking en voert u de volgende git fetch opdrachten git checkout uit:

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

    Tip

    Als u de handmatige test van Amita in de vorige les hebt gevolgd, hebt u deze opdrachten mogelijk al uitgevoerd. Als u ze al in de vorige les hebt uitgevoerd, kunt u ze nu nog steeds uitvoeren.

    Zoals u weet, verwijst upstream naar de Microsoft GitHub-opslagplaats. De Git-configuratie van uw project begrijpt de externe upstream omdat u die relatie hebt ingesteld. U stelt het in wanneer u het project hebt gesplitst vanuit de Microsoft-opslagplaats en het lokaal hebt gekloond.

    U gaat deze vertakking zo pushen naar uw GitHub-opslagplaats, ook bekend als origin.

  3. Open eventueel in Visual Studio Code het azure-pipelines.yml-bestand . Maak uzelf vertrouwd met de eerste configuratie.

    De configuratie lijkt op de configuratie die u in de vorige modules in dit leertraject hebt gemaakt. Hiermee wordt alleen de releaseconfiguratie van de toepassing geconfigureerd. Kortom, het laat ook de triggers, handmatige goedkeuringen en tests weg die u in eerdere modules hebt ingesteld.

    Notitie

    Een robuustere configuratie kan de vertakkingen opgeven die deelnemen aan het buildproces. Als u bijvoorbeeld de codekwaliteit wilt controleren, kunt u eenheidstests uitvoeren telkens wanneer u een wijziging op een vertakking pusht. U kunt de toepassing ook implementeren in een omgeving die uitgebreidere tests uitvoert. Maar u doet deze implementatie alleen wanneer u een pull-aanvraag hebt, wanneer u een releasekandidaat hebt of wanneer u code samenvoegt naar het hoofd.

    Zie Een codewerkstroom implementeren in uw build-pijplijn met behulp van Git- en GitHub - en Build-pijplijntriggers voor meer informatie.

De eenheidstestcode schrijven

Amita is verheugd om code te schrijven waarmee de webbrowser wordt bestuurd.

Ze en Andy werken samen om de Selenium-tests te schrijven. Andy heeft al een leeg NUnit-project ingesteld. Tijdens het proces verwijzen ze naar de Selenium-documentatie, een paar online zelfstudies en de notities die ze hebben gemaakt toen Amita de tests handmatig deed. Aan het einde van deze module vindt u meer resources om u te helpen bij het doorlopen van het proces.

Laten we het proces bekijken dat Andy en Amita gebruiken om hun tests te schrijven. U kunt dit volgen door HomePageTest.cs te openen in de map Tailspin.SpaceGame.Web.UITests in Visual Studio Code.

De klasse HomePageTest definiëren

Andy: Het eerste wat we moeten doen, is onze testklasse definiëren. We kunnen ervoor kiezen om een van de verschillende naamconventies te volgen. Laten we onze klas HomePageTestbellen. In deze klasse plaatsen we al onze tests die betrekking hebben op de startpagina.

Andy voegt deze code toe aan HomePageTest.cs:

public class HomePageTest
{
}

Andy: We moeten deze klasse markeren als public zodanig dat deze beschikbaar is voor het NUnit-framework.

De IWebDriver-lidvariabele toevoegen

Andy: Vervolgens hebben we een IWebDriver lidvariabele nodig. IWebDriver is de programmeerinterface die u gebruikt om een webbrowser te starten en te communiceren met webpagina-inhoud.

Amita: Ik heb gehoord van interfaces in programmeren. Kun je me meer vertellen?

Andy: U kunt een interface beschouwen als een specificatie of blauwdruk voor het gedrag van een onderdeel. Een interface biedt de methoden of gedragingen van dat onderdeel. Maar de interface biedt geen van de onderliggende details. U of iemand anders maakt een of meer concrete klassen die die interface implementeren. Selenium biedt de concrete klassen die we nodig hebben.

In dit diagram ziet u de IWebDriver interface en een paar van de klassen die deze interface implementeren:

Diagram van de IWebDriver-interface, de methoden en concrete klassen.

In het diagram ziet u drie van de methoden die IWebDriver : Navigate, FindElementen Close.

De drie klassen die hier worden weergegeven, ChromeDriveren FirefoxDriverEdgeDriverelke klasse implementeert IWebDriver en de bijbehorende methoden. Er zijn andere klassen, zoals SafariDriver, die ook implementeren IWebDriver. Elke stuurprogrammaklasse kan de webbrowser beheren die deze vertegenwoordigt.

Andy voegt een lidvariabele met de naam driver toe aan de HomePageTest klasse, zoals deze code:

public class HomePageTest
{
    private IWebDriver driver;
}

De testarmaturen definiëren

Andy: We willen de volledige reeks tests uitvoeren op Chrome, Firefox en Edge. In NUnit kunnen we testarmaturen gebruiken om de hele set tests meerdere keren uit te voeren, één keer voor elke browser waarop we willen testen.

In NUnit gebruikt u het TestFixture kenmerk om uw testarmaturen te definiëren. Andy voegt deze drie testarmaturen toe aan de HomePageTest klasse:

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

Andy: Vervolgens moeten we een constructor definiëren voor onze testklasse. De constructor wordt aangeroepen wanneer NUnit een exemplaar van deze klasse maakt. Als argument neemt de constructor de tekenreeks die we aan onze testarmaturen hebben gekoppeld. De code ziet er als volgt uit:

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

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

Andy: We hebben de browser lidvariabele toegevoegd, zodat we de huidige browsernaam in onze installatiecode kunnen gebruiken. Nu gaan we de installatiecode schrijven.

De installatiemethode definiëren

Andy: Vervolgens moeten we onze IWebDriver lidvariabele toewijzen aan een klasse-exemplaar waarmee deze interface wordt geïmplementeerd voor de browser waarop we testen. De ChromeDriver, FirefoxDriveren EdgeDriver klassen implementeren deze interface respectievelijk voor Chrome, Firefox en Edge.

We gaan een methode maken met de naam Setup waarmee de driver variabele wordt ingesteld. We gebruiken het OneTimeSetUp kenmerk om NUnit te vertellen deze methode eenmalig per testarmatur uit te voeren.

[OneTimeSetUp]
public void Setup()
{
}

In de Setup methode kunnen we een switch instructie gebruiken om de driver lidvariabele toe te wijzen aan de juiste concrete implementatie op basis van de browsernaam. Laten we die code nu toevoegen.

// 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");
}

De constructor voor elke stuurprogrammaklasse neemt een optioneel pad naar de stuurprogrammasoftware Selenium moet de webbrowser beheren. Later bespreken we de rol van de omgevingsvariabelen die hier worden weergegeven.

In dit voorbeeld vereist de EdgeDriver constructor ook aanvullende opties om op te geven dat we de Chromium-versie van Edge willen gebruiken.

De helpermethoden definiëren

Andy: Ik weet dat we tijdens de tests twee acties moeten herhalen:

  • Elementen zoeken op de pagina, zoals de koppelingen waarop we klikken en de modale vensters die we verwachten te verschijnen
  • Klikken op elementen op de pagina, zoals de koppelingen die de modale vensters weergeven en de knop waarmee elke modale knop wordt gesloten

Laten we twee helpermethoden schrijven, één voor elke actie. We beginnen met de methode waarmee een element op de pagina wordt gevonden.

De helpermethode FindElement schrijven

Wanneer u een element op de pagina zoekt, is dit meestal in reactie op een andere gebeurtenis, zoals het laden van de pagina of het invoeren van gegevens door de gebruiker. Selenium biedt de WebDriverWait klasse, waarmee u kunt wachten tot een voorwaarde waar is. Als de voorwaarde niet waar is binnen de opgegeven periode, WebDriverWait genereert u een uitzondering of fout. We kunnen de WebDriverWait klasse gebruiken om te wachten tot een bepaald element wordt weergegeven en klaar is om gebruikersinvoer te ontvangen.

Als u een element op de pagina wilt zoeken, gebruikt u de By klasse. De By klasse biedt methoden waarmee u een element kunt vinden op basis van de naam, de CSS-klassenaam, de HTML-tag of in ons geval op id het kenmerk.

Andy en Amita codeer de FindElement helpermethode. Deze code ziet er als volgt uit:

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;
        });
}

De helpermethode ClickElement schrijven

Andy: Vervolgens gaan we een helpermethode schrijven die op koppelingen klikt. Selenium biedt een aantal manieren om die methode te schrijven. Een daarvan is de IJavaScriptExecutor interface. Hiermee kunnen we programmatisch klikken op koppelingen met behulp van JavaScript. Deze aanpak werkt goed omdat het kan klikken op koppelingen zonder ze eerst in beeld te schuiven.

ChromeDriver, FirefoxDriveren EdgeDriver elke implementatie IJavaScriptExecutor. We moeten het stuurprogramma naar deze interface casten en vervolgens aanroepen ExecuteScript om de JavaScript-methode click() uit te voeren op het onderliggende HTML-object.

Andy en Amita codeer de ClickElement helpermethode. Deze code ziet er als volgt uit:

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: Ik vind het leuk om deze helpermethoden toe te voegen. Ze lijken algemeen genoeg te gebruiken in bijna elke test. We kunnen later meer helpermethoden toevoegen omdat we ze nodig hebben.

De testmethode definiëren

Andy: Nu zijn we klaar om de testmethode te definiëren. Op basis van de handmatige tests die we eerder hebben uitgevoerd, gaan we deze methode ClickLinkById_ShouldDisplayModalByIdaanroepen. Het is een goed idee om testmethoden beschrijvende namen te geven die precies bepalen wat de test doet. Hier willen we klikken op een koppeling die is gedefinieerd door het id kenmerk. Vervolgens willen we controleren of het juiste modale venster wordt weergegeven, ook met behulp van het id kenmerk.

Andy voegt starterscode toe voor de testmethode:

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

Andy: Voordat we meer code toevoegen, gaan we definiëren wat deze test moet doen.

Amita: Ik kan dit deel aan. We willen:

  1. Zoek de koppeling op basis van id het kenmerk en klik vervolgens op de koppeling.
  2. Zoek de resulterende modale.
  3. Sluit de modale.
  4. Controleer of de modale weergave is geslaagd.

Andy: Geweldig. We moeten ook enkele andere dingen afhandelen. We moeten de test bijvoorbeeld negeren als het stuurprogramma niet kan worden geladen en we moeten de modale alleen sluiten als de modale weergave is geslaagd.

Na het vullen van hun koffiemokken, voegen Andy en Amita code toe aan hun testmethode. Ze gebruiken de helpermethoden die ze hebben geschreven om pagina-elementen te zoeken en op koppelingen en knoppen te klikken. Dit is het resultaat:

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: De codering ziet er tot nu toe geweldig uit. Maar hoe verbinden we deze test met de id kenmerken die we eerder hebben verzameld?

Andy: Geweldige vraag. Dat gaan we nu doen.

Testcasegegevens definiëren

Andy: In NUnit kunt u op een aantal manieren gegevens aan uw tests verstrekken. Hier gebruiken we het TestCase kenmerk. Dit kenmerk gebruikt argumenten die later worden teruggegeven aan de testmethode wanneer het wordt uitgevoerd. We kunnen meerdere TestCase kenmerken hebben die elk een andere functie van onze app testen. Elk TestCase kenmerk produceert een testcase die is opgenomen in het rapport dat wordt weergegeven aan het einde van een pijplijnuitvoering.

Andy voegt deze TestCase kenmerken toe aan de testmethode. Deze kenmerken beschrijven de knop Game downloaden, een van de gameschermen en de bovenste speler op het leaderboard. Elk kenmerk bevat twee id kenmerken: één voor de koppeling om te klikken en één voor het bijbehorende modale venster.

// 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: Voor elk TestCase kenmerk is de eerste parameter het id kenmerk voor de koppeling waarop moet worden geklikt. De tweede parameter is het id kenmerk voor het modale venster dat we verwachten te verschijnen. U kunt zien hoe deze parameters overeenkomen met de argumenten van twee tekenreeksen in onze testmethode.

Amita: Ik zie dat wel. Met wat oefening denk ik dat ik mijn eigen tests kan toevoegen. Wanneer kunnen we deze tests zien die in onze pijplijn worden uitgevoerd?

Andy: Voordat we wijzigingen via de pijplijn pushen, gaan we eerst controleren of de code lokaal wordt gecompileerd en uitgevoerd. We voeren wijzigingen door naar GitHub en pushen ze pas door de pijplijn nadat we hebben gecontroleerd of alles werkt. We gaan de tests nu lokaal uitvoeren.