Vedlikeholdbare automatiserte brukergrensesnitt

For noen år siden var jeg veldig skeptisk til automatisert brukergrensesnitt og denne skepsisen ble født ut av noen mislykkede forsøk. Jeg ville skrive noen automatiserte brukergrensesnitt for desktop- eller webapplikasjoner, og noen uker senere ville jeg rive dem ut av codebase fordi kostnadene ved å opprettholde dem var for høye. Så jeg trodde at brukergrensesnittprøving var vanskelig, og det var best å holde det til et minimum, og bare test de mest komplekse arbeidsflytene i et system gjennom grensesnittprøving og la resten være til enhetstester. Jeg husker å fortelle teamet mitt om Mike Cohns testingspyramide, og at i et typisk system skal over 70% av testene være enhetstester, rundt 5% brukergrensesnitt og resten integreringstester.

Så jeg trodde at brukergrensesnittprøving var vanskelig, og at det var best å holde det til et minimum, mens det ga en stor fordel.

Jeg tok feil! Sikker på at brukergrensesnitt testing kan være vanskelig. Det tar litt tid å skrive UI-tester riktig. De er mye langsommere og mer sprø enn enhetstester fordi de krysser klasse- og prosessgrenser, de treffer nettleseren, de involverer brukergrensesnittelementer (for eksempel HTML, JavaScript) som stadig skifter, de rammer databasen, filsystemet og potensielt nettverkstjenester. Hvis noen av disse bevegelige delene ikke spiller pent, har du en ødelagt test; men det er også skjønnheten til brukergrensesnitttestene: de tester systemets ende-til-ende. Ingen annen test gir deg så mye eller som grundig dekning. Automatiserte brukergrensesnitt, hvis gjort riktig, kan være de beste elementene i regresjonspakken.

Så i de siste prosjektene har mine UI-tester dannet over 80% av testene mine! Jeg bør også nevne at disse prosjektene hovedsakelig har vært CRUD applikasjoner med ikke mye forretningslogikk og la oss innse det - det store flertallet av programvareprosjekter faller inn i denne kategorien. Forretningslogikken skal fortsatt være enhetstestet; men resten av søknaden kan bli grundig testet gjennom UI-automatisering.


UI-testing gikk feil

Jeg vil gjerne ta på hva jeg gjorde galt, noe som også synes å være veldig typisk blant utviklere og testere som starter med UI-automatisering.

Så hva går galt og hvorfor? Mange lag starter UI-automatisering med skjermopptakere. Hvis du gjør webautomatisering med Selen, har du mest sannsynlig brukt Selen IDE. Fra Selen IDE hjemmesiden:

Selenium-IDE (Integrated Development Environment) er verktøyet du bruker til å utvikle dine Selenium-testtilfeller.

Dette er faktisk en av grunnene til at UI-testing blir til en forferdelig opplevelse: du laster ned og brenner opp en skjermopptaker og navigerer til nettstedet ditt og klikker, klikker, skriver, klikker, skriver, faner, skriver, faner, skriver, klikker og hevde. Deretter spiller du på opptaket og det fungerer. Søt!! Så du eksporterer handlingene som et testskript, legg det inn i koden din, pakk det inn i en test og utfør testen og se at nettleseren blir levende før øynene dine og testene dine går veldig jevnt. Du blir veldig spent, dele funnene dine med dine kolleger og vise den til sjefen din, og de blir veldig glade og går: "Automatiser ALLE SINGENE"

En uke senere, og du har 10 automatiserte brukergrensesnitt, og alt ser bra ut. Deretter ber virksomheten deg om å erstatte brukernavnet med e-postadressen, da det har forårsaket forvirring blant brukerne, og det gjør du også. Så, som alle andre store programmerer, kjører du din UI-testpakke, bare for å finne 90% av testene dine er ødelagte fordi du for hver test logger brukeren inn med brukernavn og feltnavnet har endret seg, og det tar deg to timer å erstatte alle referanser til brukernavn i testene dine med e-post og for å få testene grønne igjen. Det samme skjer igjen og igjen, og på et tidspunkt finner du deg selv å tilbringe tid på en dag med å fikse brutte tester: tester som ikke brøt fordi noe gikk galt med koden din; men fordi du endret et feltnavn i databasen / modellen eller omstrukturert siden din litt. Noen uker senere slutter du å kjøre testene dine på grunn av denne enorme vedlikeholdskostnaden, og du konkluderer med at UI-testing suger.

Du bør IKKE bruke Selenium IDE eller annen skjermopptaker for å utvikle testtilfeller. Når det er sagt, er det ikke skjermopptakeren selv som fører til en sprø test suite; Det er koden de genererer som har inneboende vedlikeholdsproblemer. Mange utviklere ende opp med en sprø UI test suite selv uten å bruke skjermopptakere bare fordi deres tester har de samme egenskapene.

Alle testene i denne artikkelen er skrevet mot Mvc Music Store-nettsiden. Nettstedet som det har, har noen problemer som gjør UI-testing ganske vanskelig, så jeg portet koden og fikset problemene. Du finner den faktiske koden jeg skriver disse tester mot på GitHub repo for denne artikkelen her

Så hvordan ser en sprø test ut? Det ser noe ut som dette:

klasse BrittleTest [Test] public void Can_buy_an_Album_when_registered () var driver = Host.Instance.Application.Browser; . Driver.Navigate () GoToUrl (driver.Url); . Driver.FindElement (By.LinkText ( "Admin")) Klikk (); . Driver.FindElement (By.LinkText ( "Register")) Klikk (); driver.FindElement (By.Id ( "brukernavn")) Clear (.); driver.FindElement (By.Id ( "Username")) SendKeys ( "HJSimpson."); driver.FindElement (By.Id ( "Passord")) Clear (.); driver.FindElement (By.Id ( "Passord")) SendKeys ( "2345Qwert!."); driver.FindElement (By.Id ( "ConfirmPassword")) Clear (.); driver.FindElement (By.Id ( "ConfirmPassword")) SendKeys ( "2345Qwert!."); driver.FindElement (By.CssSelector ( "input [type = \" submit \ "]")) Klikk (.); . Driver.FindElement (By.LinkText ( "Disco")) Klikk (); driver.FindElement (By.CssSelector ("img [alt = \" Le Freak \ "]")). Klikk (); driver.FindElement (By.LinkText ("Legg til i handlekurven")). Klikk (); driver.FindElement (By.LinkText ("Checkout >>")). Klikk (); driver.FindElement (By.Id ( "Fornavn")) Clear (.); driver.FindElement (By.Id ( "fornavn")) SendKeys ( "Homer."); driver.FindElement (By.Id ( "Etternavn")) Clear (.); driver.FindElement (By.Id ( "Lastname")) SendKeys ( "Simpson").; driver.FindElement (By.Id ( "Adresse")) Clear (.); driver.FindElement (By.Id ("Address")). SendKeys ("742 Evergreen Terrace"); driver.FindElement (By.Id ( "By")) Clear (.); driver.FindElement (By.Id ( "By")) SendKeys ( "Springfield."); driver.FindElement (By.Id ( "stat")) Clear (.); driver.FindElement (By.Id ( "stat")) SendKeys ( "Kentucky."); driver.FindElement (By.Id ( "Post")) Clear (.); driver.FindElement (By.Id ( "Postkode")) SendKeys ( "123 456."); driver.FindElement (By.Id ( "land")) Clear (.); driver.FindElement (By.Id ("Country")). SendKeys ("United States"); driver.FindElement (By.Id ( "Phone")) Clear (.); driver.FindElement (By.Id ( "Phone")) SendKeys ( "2341231241."); driver.FindElement (By.Id ( "E-post")) Clear (.); driver.FindElement (By.Id ( "E-post")) SendKeys ( "[email protected]."); driver.FindElement (By.Id ( "Promosjonskode")) Clear (.); driver.FindElement (By.Id ( "Promosjonskode")) SendKeys ( "FREE."); driver.FindElement (By.CssSelector ( "input [type = \" submit \ "]")) Klikk (.); Assert.IsTrue (driver.PageSource.Contains ("Checkout Complete")); 

Du kan finne BrittleTest klassen her.

Verten er en statisk klasse, med en enkelt statisk egenskap: Forekomst, som ved instantiation bryr opp IIS Express på nettsiden under test og binder Firefox WebDriver til nettleserens forekomst. Når testen er ferdig, lukker den nettleseren og IIS Express automatisk.

Denne testen bryr opp en nettleser, går til hjemmesiden til Mvc Music Store-nettsiden, registrerer en ny bruker, blar til et album, legger det til handlekurven og sjekker ut.

Man kan argumentere for at denne testen gjør for mye, og det er derfor det er sprøtt; men størrelsen på denne testen er ikke grunnen til at den er sprø - det er hvordan det er skrevet som gjør det til et mareritt å opprettholde.

Det er ulike tankegang på UI-testing og hvor mye hver test skal dekke. Noen mener at denne testen gjør for mye, og noen mener at en test burde dekke et reelt scenario, ende til slutt, og betrakter dette til en perfekt test (vedlikeholdbarhet til side).

Så hva er galt med denne testen?

  • Dette er prosesskoden. En av hovedproblemene i denne typen koding er lesbarhet eller mangel på det. Hvis du vil endre testen, eller hvis den går i stykker fordi en av de involverte sidene har endret seg, vil du ha det vanskelig å finne ut hva du skal endre og å tegne en linje mellom funksjonalitetsseksjoner. fordi det er en stor haug med kode hvor vi får driveren til å finne et element på siden og å gjøre noe med det. Ingen modularitet.
  • Denne testen av seg selv kan ikke ha mye duplisering, men noen få flere tester som dette, og du vil ha mye duplisert velg og logikk for å samhandle med nettsider fra forskjellige tester. For eksempel By.Id ( "brukernavn") velgeren vil bli duplisert i alle tester som krever registrering, og driver.FindElement (By.Id ( "brukernavn")). Clear () og driver.FindElement (By.Id ( "brukernavn")). SendKeys ("") dupliseres hvor som helst du vil samhandle med UserName-tekstboksen. Deretter er det hele registreringsskjemaet, og kasseformular etc. som vil bli gjentatt i alle tester som trenger å samhandle med dem! Duplisert kode fører til vedlikeholdsbarerier.
  • Det er mange magiske strenger overalt, som igjen er et vedlikeholdsproblem.

Testkode er kode!

Det finnes også mønstre som lar deg skrive mer vedlikeholdsgrensesnitt.

Mye som din faktiske kode, må du opprettholde testene dine. Så gi dem samme behandling.

Hva er det med tester som får oss til å tro at vi kan avstå fra kvalitet i dem? Hvis noe, er en dårlig testpakke etter min mening mye vanskeligere å opprettholde enn dårlig kode. Jeg har hatt dårlige stykker arbeidskode i produksjon i år som aldri brøt, og jeg måtte aldri røre dem. Sikker på at det var stygg og vanskelig å lese og vedlikeholde, men det fungerte, og det behøvde ikke endring, så den virkelige vedlikeholdskostnaden var null. Situasjonen er ikke helt den samme for dårlige tester skjønt: fordi dårlige tester kommer til å bryte og fikse dem kommer til å bli vanskelig. Jeg kan ikke telle antall ganger jeg har sett utviklere unngår testing fordi de tror å skrive tester er et stort sløsing med tid fordi det tar for mye tid å opprettholde.

Testkode er kode: Bruker du SRP på koden din? Da bør du bruke det på testene dine også. Er koden DRY? Så tørk opp testene dine også. Hvis du ikke skriver gode tester (brukergrensesnitt eller annet), vil du kaste bort mye tid på å opprettholde dem.

Det finnes også mønstre som lar deg skrive mer vedlikeholdsgrensesnitt. Disse mønstrene er plattform agnostiske: Jeg har brukt disse samme ideene og mønstrene til å skrive UI-tester for WPF-applikasjoner og webapplikasjoner skrevet i ASP.Net og Ruby on Rails. Så uavhengig av teknologistakken din, bør du kunne gjøre brukergrensesnittene dine mye mer vedlikeholdsbare ved å følge noen få enkle trinn.

Innføring av sideobjektmønsteret

Mange av de ovennevnte problemene er forankret i prosedyren for testskriptet, og løsningen er enkel: Objektorientering.

Sideobjekt er et mønster som brukes til å bruke objektorientering til brukergrensesnitt. Fra Selenium wiki:

I webapplikasjonens brukergrensesnitt er det områder som testerne dine samhandler med. Et sidobjekt modellerer dem bare som objekter innenfor testkoden. Dette reduserer mengden duplisert kode og betyr at hvis brukergrensesnittet endres, må fikseringsbehovet bare brukes på ett sted.

Tanken er at for hver side i søknaden / nettstedet ditt vil du opprette ett sidobjekt. Sideobjekter er i utgangspunktet UI-automatisering tilsvarende websidene dine.

Jeg har gått videre og refactored logikken og samspillet ut av BrittleTest inn i noen få siderobjekter og opprettet en ny test som bruker dem i stedet for å slå webdriveren direkte. Du finner den nye testen her. Koden er kopiert her for din referanse:

offentlig klasse TestWithPageObject [Test] public void Can_buy_an_Album_when_registered () var registerPage = HomePage.Initiate () .GoToAdminForAnonymousUser () .GoToRegisterPage (); registerPage.Username = "HJSimpson"; registerPage.Email = "[email protected]"; registerPage.Password = "! 2345Qwert"; registerPage.ConfirmPassword = "! 2345Qwert"; var shippingPage = registerPage .SubmitRegistration () .SelectGenreByName ("Disco") .SelectAlbumByName ("Le Freak") .AddToCart () .Checkout (); shippingPage.FirstName = "Homer"; shippingPage.LastName = "Simpson"; shippingPage.Address = "742 Evergreen Terrace"; shippingPage.City = "Springfield"; shippingPage.State = "Kentucky"; shippingPage.PostalCode = "123456"; shippingPage.Country = "United States"; shippingPage.Phone = "2341231241"; shippingPage.Email = "[email protected]"; shippingPage.PromoCode = "FREE"; var orderPage = shippingPage.SubmitOrder (); Assert.AreEqual (orderPage.Title, "Checkout Complete"); 

Selvfølgelig har testkroppen ikke gått mye i størrelse, og jeg måtte faktisk lage syv nye klasser for å støtte denne testen. Til tross for at flere koden krevde, hadde vi bare løst mange problemer den opprinnelige sprø testen hadde (mer på dette lenger nede). For nå, la oss dykke litt dypere inn i sidobjektmønsteret og hva vi gjorde her.

Med Page Object-mønsteret oppretter du vanligvis en sideobjektklasse per nettside under test der klassemodellene og inkapsulerer interaksjoner med siden. Så en tekstboks på websiden din blir en strengegenskap på sidobjektet og for å fylle den tekstboksen, har du bare satt den tekstegenskapen til ønsket verdi, i stedet for:

driver.FindElement (By.Id ( "E-post")) Clear (.); driver.FindElement (By.Id ( "E-post")) SendKeys ( "[email protected].");

vi kan skrive:

registerPage.Email = "[email protected]";

hvor registerPage er en forekomst av RegisterPage-klassen. En avkrysningsboks på siden blir en bool-egenskap på sidobjektet, og tikkende og unticking avkrysningsruten er bare et spørsmål om å sette den boolske egenskapen til ekte eller falsk. På samme måte blir en lenke på nettsiden en metode på sidobjektet, og ved å klikke på koblingen blir det å kalle metoden på sidobjektet. Så i stedet for:

. Driver.FindElement (By.LinkText ( "Admin")) Klikk ();

vi kan skrive:

homepage.GoToAdminForAnonymousUser ();

Faktisk blir enhver handling på vår nettside en metode i vårt sideobjekt og som svar på å ta den handlingen (dvs. kaller metoden på sidenobjektet) får du en forekomst av et annet sideobjekt som peker på nettsiden du bare har navigerte til ved å ta handlingen (for eksempel sende inn et skjema eller klikke på en lenke). På denne måten kan du enkelt koble visningsinteraksjonene dine i testskriptet ditt:

var shippingPage = registerPage .SubmitRegistration () .SelectGenreByName ("Disco") .SelectAlbumByName ("Le Freak") .AddToCart () .Checkout ();

Her, etter å ha registrert brukeren, blir jeg tatt til hjemmesiden (en forekomst av sidenobjektet returneres av SubmitRegistration metode). Så på HomePage-instansen jeg ringer SelectGenreByName som klikker på en 'Disco' -link på siden som returnerer en forekomst av AlbumBrowsePage og deretter på den siden jeg ringer SelectAlbumByName som klikker på albumet 'Le Freak' og returnerer en forekomst av AlbumDetailsPage og så videre og så videre.

Jeg innrømmer det: det er mange klasser for det som pleide å være ingen klasse i det hele tatt; men vi fikk mange fordeler fra denne øvelsen. For det første er koden ikke lenger prosedyre. Vi har en godt innebygd testmodell der hvert objekt gir en god innkapsling av samspill med en side. Så for eksempel hvis noe endres i registreringslogikken, er det eneste stedet du må endre, RegisterPage-klassen din i stedet for å gå gjennom hele testpakken og endre hver enkelt samhandling med registreringsvisningen. Denne modulariteten gir også god gjenbruk: du kan gjenbruke din ShoppingCartPage overalt må du samhandle med handlekurven. Så i en enkel praksis med å flytte fra prosessor til objektorientert testkode fjernet vi nesten tre av de fire problemene med den første sprø testen som var prosesskoden, og logikk og selector duplisering. Vi har fortsatt litt duplisering, men som vi vil fikse snart.

Hvordan implementerte vi faktisk sidobjektene? Et sidobjekt i roten er ingenting annet enn et omslag rundt samhandlingene du har med siden. Her har jeg nettopp hentet UI-interaksjoner våre av de sprø testene og lagt dem inn i deres egne sideobjekter. For eksempel ble registreringslogikken hentet inn i sin egen klasse kalt RegisterPage som så ut som dette:

offentlig klasse RegisterPage: Page public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ( "input [type = 'submit']"));  offentlig streng Brukernavn set Execute (By.Name ("UserName"), e => e.Clear (); e.SendKeys (verdi););  offentlig streng e-post set Execute (By.Name ("Email"), e => e.Clear (); e.SendKeys (verdi););  offentlig streng ConfirmPassword set Execute (By.Name ("ConfirmPassword"), e => e.Clear (); e.SendKeys (verdi););  offentlig streng Passord set Execute (By.Name ("Password"), e => e.Clear (); e.SendKeys (verdi);); 

Jeg har laget en Side superklasse som tar vare på noen få ting, som Navigere til som hjelper å navigere til en ny side ved å ta en handling og Henrette som utfører noen handlinger på et element. De Side klassen så ut som:

offentlig klasse Side beskyttet RemoteWebDriver WebDriver get return Host.Instance.WebDriver;  offentlig streng Tittel get return WebDriver.Title;  offentlig TPage NavigateTo(By By) der TPage: Page, new () WebDriver.FindElement (by) .Click (); returnere Activator.CreateInstance();  Offentlig tomgang Utfør (By by, Action handling) var element = WebDriver.FindElement (ved); virkning (element); 

I BrittleTest, å samhandle med et element vi gjorde FindElement en gang per handling. De Henrette metode, bortsett fra abstraherende webdriverens interaksjon, har en ekstra fordel som gjør det mulig å velge et element, som kan være en dyr handling, en gang og ta flere handlinger på den:

driver.FindElement (By.Id ( "Passord")) Clear (.); driver.FindElement (By.Id ( "Passord")) SendKeys ( "2345Qwert!.");

ble erstattet med:

Utfør (By.Name ("Passord"), e => e.Clear (); e.SendKeys ("! 2345Qwert");)

Tar en ny titt på RegisterPage sideobjekt ovenfor, har vi fortsatt litt duplisering der inne. Testkode er kode og vi vil ikke ha duplisering i vår kode; så la oss reflektere det. Vi kan trekke ut koden som kreves for å fylle ut en tekstboks i en metode på Side klassen og bare ring det fra sideobjekter. Metoden kan implementeres som:

Offentlig tomt SetText (strengelementnavn, streng newText) Utfør (By.Name (elementName), e => e.Clear (); e.SendKeys (newText);); 

Og nå egenskapene på RegisterPage kan krympes til:

offentlig streng Brukernavn set SetText ("UserName", verdi); 

Du kan også lage en flytende API for å gjøre setter lese bedre (f.eks. Fill ( "brukernavn"). Med (verdi)) men jeg vil legge det til deg.

Vi gjør ikke noe ekstraordinært her. Bare enkel refactoring på vår testkode som vi alltid har gjort for vår, errrr, "andre" kode!!

Du kan se hele koden for Side og RegisterPage klasser her og her.

Sterkt skrevet sideobjekt

Vi løste prosessproblemer med den sprø testen som gjorde testen mer lesbar, modulær, DRYer og effektivt vedlikeholdt. Det er et siste problem vi ikke fikse: det er fortsatt mange magiske strenger overalt. Ikke helt et mareritt, men fortsatt et problem vi kunne fikse. Skriv sterkt skrevet sideobjekter!

Denne tilnærmingen er praktisk hvis du bruker et MV * -ramme for brukergrensesnittet ditt. I vårt tilfelle bruker vi ASP.Net MVC.

La oss ta en titt på RegisterPage:

offentlig klasse RegisterPage: Page public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ( "input [type = 'submit']"));  offentlig streng Brukernavn set SetText ("UserName", verdi);  offentlig streng e-post sett SetText ("Email", verdi);  offentlig streng Bekreft Passord sett SetText ("Bekreft Passord", verdi);  offentlig streng Passord sett SetText ("Passord", verdi); 

Denne siden modellerer Register-visningen i vår web-app (bare å kopiere toppbiten her for enkelhets skyld):

@model MvcMusicStore.Models.RegisterModel @ ViewBag.Title = "Register"; 

Hmmm, hva er det RegisterModel der? Det er View Model for siden: M i MVC. Her er koden (jeg fjernet attributter for å redusere støyen):

offentlig klasse RegisterModel public string UserName get; sett;  offentlig streng e-post get; sett;  offentlig streng Passord get; sett;  offentlig streng Bekreft passord get; sett; 

Det ser veldig kjent ut, ikke sant? Den har de samme egenskapene som RegisterPage klasse som ikke er overraskende vurderer RegisterPage ble opprettet basert på denne visningen og visningsmodellen. La oss se om vi kan dra nytte av visningsmodeller for å forenkle sideobjektene våre.

Jeg har laget en ny Side super; men en generisk en. Du kan se koden her:

offentlig klasse side : Side hvor TViewModel: klassen, ny () public void FillWith (TViewModel viewModel, IDictionary> propertyTypeHandling = null) // fjernet for korthet

De Side klassen underklasser den gamle Side klassen og gir all sin funksjonalitet; men det har også en ekstra metode kalt Fyll med som fyller inn siden med forhåndsvisning modell eksempel! Så nå min RegisterPage klassen ser ut som:

offentlig klasse RegisterPage: Side offentlig HomePage CreateValidUser (RegisterModel-modell) FillWith (modell); returner NavigateTo(By.CssSelector ( "input [type = 'submit']")); 

Jeg dupliserte alle sideobjekter for å vise begge variantene og også for å gjøre kodebasen enklere å følge for deg; men i virkeligheten trenger du en klasse for hvert sideobjekt.

Etter å ha konvertert min sideobjekter til generiske, ser testen ut som:

offentlig klasse StronglyTypedPageObjectWithComponent [Test] public void Can_buy_an_Album_when_registered () var orderedPage = HomePage.Initiate () .GoToAdminForAnonymousUser () .GoToRegisterPage () .CreateValidUser (ObjectMother.CreateRegisterModel ()) .SelectGenreByName ("Disco") .SelectAlbumByName Freak ") .AddAlbumToCart () .Checkout () .SubmitShippingInfo (ObjectMother.CreateShippingInfo ()," Free "); Assert.AreEqual ("Checkout Complete", orderedPage.Title); 

Det er det - hele testen! Mye mer lesbar, tørr og vedlikeholdsbar, er det ikke?

De ObjectMother klassen jeg bruker i testen er en Objektmor som gir testdata (kode finner du her), ikke noe fancy:

offentlig klasse ObjectMother offentlig statisk ordre CreateShippingInfo () var shippingInfo = ny bestilling FirstName = "Homer", LastName = "Simpson", Adresse = "742 Evergreen Terrace", By = "Springfield", Stat = "Kentucky", PostalCode = "123456", Land = "USA", Telefon = "2341231241", E-post = "[email protected]"; returner shippingInfo;  offentlige statiske RegisterModel CreateRegisterModel () var model = new RegisterModel UserName = "HJSimpson", Email = "[email protected]", Passord = "! 2345Qwert", ConfirmPassword = "! 2345Qwert"; retur modell; 

Ikke Stopp ved siden objektet

Noen nettsider er veldig store og komplekse. Tidligere sa jeg testkode er kode og vi bør behandle det som sådan. Vi bryter normalt store og komplekse nettsider inn i mindre og, i noen tilfeller, gjenbrukbare (delvise) komponenter. Dette gjør at vi kan komponere en nettside fra mindre, mer håndterbare komponenter. Vi bør gjøre det samme for våre tester. For å gjøre dette kan vi bruke Page Components.

En sidekomponent er ganske mye som et sidobjekt: Det er en klasse som inkapsler samspill med enkelte elementer på en side. Forskjellen er at den samhandler med en liten del av en nettside: den modellerer en brukerkontroll eller en delvis visning, hvis du vil. Et godt eksempel på en sidekomponent er en menylinje. En menylinje vises vanligvis på alle sider i et webprogram. Du ønsker ikke å fortsette å gjenta koden som kreves for å samhandle med menyen i hvert enkelt sideobjekt. I stedet kan du opprette en menysidekomponent og bruke den fra dine sidobjekter. Du kan også bruke sidekomponenter til å håndtere datalistene på sidene dine, og for å ta det et skritt videre, kan rutenettkomponenten selv bestå av rutenett-komponentene. I tilfelle av Mvc Music Store kunne vi ha en TopMenuComponent og a SideMenuComponent og bruk dem fra vår Homepage.

Som i webapplikasjonen din, kan du også lage en, si, LayoutPage sideobjekt som modellerer layout / master side og bruker det som en superklasse for alle dine andre sideobjekter. Oppsettsiden vil da være sammensatt av menysidekomponenter, slik at alle sider kan treffe menyene. Jeg antar at en god tommelfingerregel ville være å ha en sidekomponent per delvis visning, et layoutsideobjekt per layout og et sidobjekt per nettside. På den måten vet du at testkode er som granualar og godt sammensatt som kode.

Noen rammer for UI-testing

Det jeg viste ovenfor var en veldig enkel og konstruert prøve med noen få støttende klasser som infrastruktur for tester. I virkeligheten er kravene til UI-testing mye mer komplisert enn det: det er komplekse kontroller og interaksjoner, du må skrive til og lese fra sidene dine, du må håndtere nettverkslatenser og ha kontroll over AJAX og andre Javascript-interaksjoner, må brann av forskjellige nettlesere og så videre som jeg ikke forklarte i denne artikkelen. Selv om det er mulig å kode rundt alle disse, kan det være mye tid å bruke noen rammer. Her er rammene jeg anbefaler på det sterkeste:

Rammer for. Net:

  • Seleno er et åpen kildekode-prosjekt fra TestStack som hjelper deg med å skrive automatiske brukergrensesnitt med Selen. Det fokuserer på bruk av Page Objects og Page Components og ved å lese fra og skrive til nettsider ved hjelp av sterkt skrevet visningsmodeller. Hvis du likte det jeg gjorde i denne artikkelen, vil du også like Seleno da det meste av koden vist her var lånt fra Seleno kodebase.
  • White er et open source rammeverk fra TestStack for automatisering av klientprogrammer basert på Win32, WinForms, WPF, Silverlight og SWT (Java) plattformer.

Opplysning: Jeg er medstifter og medlem av utviklingslaget i TestStack-organisasjonen.

Rammer for Ruby:

  • Capybara er et rammeverk for godkjenningstest for webapplikasjoner som hjelper deg med å teste webapplikasjoner ved å simulere hvordan en ekte bruker vil samhandle med appen din.
  • Poltergeist er en driver for Capybara. Den lar deg kjøre Capybara-testene på en headless WebKit-nettleser, levert av PhantomJS.
  • sideobjekt (jeg har ikke personlig brukt denne perlen) er en enkel perle som hjelper til med å lage fleksible sideobjekter for testing av nettleserbaserte applikasjoner. Målet er å legge til rette for å lage abstraksjonslag i tester for å avkoble testene fra elementet de tester og for å gi et enkelt grensesnitt til elementene på en side. Den fungerer med både watir-webdriver og selen-webdriver.

Konklusjon

Vi startet med en typisk UI-automatiseringserfaring, forklart hvorfor UI-testing mislykkes, gitt et eksempel på en sprø test og diskutert dens problemer og løst dem ved å bruke noen ideer og mønstre.

Hvis du vil ta ett poeng fra denne artikkelen, bør det være: Testkode er kode. Hvis du tenker på det, gjorde alt jeg gjorde i denne artikkelen å anvende de gode kodings- og objektorienterte praksisene du allerede kjenner til en UI-test.

Det er fortsatt mye å lære om UI-testing, og jeg vil prøve å diskutere noen av de mer avanserte tipsene i en fremtidig artikkel.

Glad tester!