De UI-tests schrijven
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.
Open in Visual Studio Code de geïntegreerde terminal.
Als u een vertakking wilt downloaden met de naam
selenium
uit de Microsoft-opslagplaats, schakelt u over naar die vertakking en voert u de volgendegit fetch
opdrachtengit 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
.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 HomePageTest
bellen. 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:
In het diagram ziet u drie van de methoden die IWebDriver
: Navigate
, FindElement
en Close
.
De drie klassen die hier worden weergegeven, ChromeDriver
en FirefoxDriver
EdgeDriver
elke 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
, FirefoxDriver
en 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
, FirefoxDriver
en 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_ShouldDisplayModalById
aanroepen. 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:
- Zoek de koppeling op basis van
id
het kenmerk en klik vervolgens op de koppeling. - Zoek de resulterende modale.
- Sluit de modale.
- 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.