Underhållsautomatiska UI-test

För några år sedan var jag väldigt skeptisk till automatiserad UI-testning och denna skepticism föddes av några misslyckade försök. Jag skulle skriva några automatiska användargränssnittstest för skrivbords- eller webbapplikationer och några veckor senare skulle jag rippa dem ur kodbasen eftersom kostnaden för att behålla dem var för hög. Så jag trodde att gränssnittstestning var svårt och att det var bäst att behålla det så mycket som möjligt, och det var bara bäst att hålla det på ett minimum och bara testa de mest komplexa arbetsflödena i ett system genom UI-testning och lämna resten till enhetstester. Jag kommer ihåg att jag berättade för mitt team om Mike Cohns testpyramid, och att i ett typiskt system ska över 70% av testen vara enhetstest, cirka 5% UI-test och övriga integrationsprov.

Så jag trodde att gränssnittstestning var svårt och att det var bäst att behålla det så länge det gav stor nytta ...

Jag hade fel! Visst kan UI-test vara svårt. Det tar lite tid att skriva UI-tester på rätt sätt. De är mycket långsammare och sprödare än enhetsprov, eftersom de passerar klass- och processgränser, de slår i webbläsaren, de involverar UI-element (t.ex. HTML, JavaScript) som ständigt ändras, de slår på databasen, filsystemet och eventuellt nätverkstjänster. Om någon av dessa rörliga delar inte spelar snyggt har du ett brutet test; men det är också skönheten hos UI-testen: de testar ditt system från slutet till slutet. Inget annat test ger dig lika mycket eller noggrann täckning. Automatiserade gränssnittstester, om de är gjort rätt, kan vara de bästa elementen i din regressionssats.

Så i de senaste projekten har mina UI-tester bildat över 80% av mina tester! Jag ska också nämna att dessa projekt mest har varit CRUD-applikationer med inte mycket affärslogik och låt oss möta det - de allra flesta programprojekten faller i denna kategori. Affärslogiken ska fortfarande vara enhetstestad; men resten av ansökan kan testas noggrant genom UI-automation.


UI-testen gick fel

Jag skulle vilja beröra vad jag gjorde fel, vilket också verkar vara mycket typiskt bland utvecklare och testare som börjar med UI-automation.

Så vad går fel och varför? Många lag startar UI-automation med skärminspelare. Om du gör webbautomatisering med Selen har du mest sannolikt använt Selen IDE. Från Selen IDE hemsida:

Selenium-IDE (Integrated Development Environment) är det verktyg du använder för att utveckla dina Selen-testfall.

Det här är faktiskt en av anledningarna till att användargränssnittet blir en hemsk upplevelse: du laddar ner och skjuter upp en skärminspelare och navigerar till din webbplats och klickar, klickar, skriver, klickar, skriver, flikar, skriver, flikar, skriver, klickar och hävda. Sedan spelar du upp inspelningen och den fungerar. Ljuv!! Så du exporterar åtgärderna som ett testskript, lägger in det i din kod, lägger det i ett test och kör testet och ser att webbläsaren lever före ögonen och dina test körs mycket smidigt. Du blir väldigt exalterad, dela dina resultat med dina kollegor och visa det på din chef och de blir mycket glada och går: "Automatisera alla saker"

En vecka senare och du har 10 automatiska UI-tester och allt verkar bra. Då frågar verksamheten dig om att ersätta användarnamnet med e-postadress eftersom det har orsakat viss förvirring bland användarna, och så gör du. Därefter, som någon annan stor programmerare, kör du din UI-testpaket, för att bara hitta 90% av dina test är trasiga eftersom för varje test du loggar användaren in med användarnamn och fältnamnet har ändrats och det tar dig två timmar att ersätta alla referenser till Användarnamn i dina tester med e-post och för att få testen grön igen. Samma sak händer om och om igen och du befinner dig vid en tidpunkt att du spenderar timmar på en dag som fastställer brutna tester: Tester som inte bryts eftersom något gick fel med din kod. men för att du ändrade ett fältnamn i din databas / modell eller omstrukturerar din sida något. Några veckor senare slutar du att köra dina tester på grund av denna enorma underhållskostnad, och du slår fast att UI-test suger.

Du bör INTE använda Selenium IDE eller någon annan skärminspelare för att utveckla dina testfall. Med detta sagt är det inte själva skärminspelaren som leder till en spröd testpaket; Det är koden de genererar som har inneboende underhållsproblem. Många utvecklare slutar ändå med en spröd UI test suite även utan att använda skärminspelare bara för att deras tester uppvisar samma attribut.

Alla tester i den här artikeln är skrivna mot Mvc Music Store hemsida. Webbplatsen som den har har några problem som gör UI-testning ganska svårt så jag portade koden och åtgärda problemen. Du kan hitta den faktiska koden som jag skriver mot dessa test på GitHub repo för den här artikeln här

Så hur ser ett sprött test ut? Det ser något ut så här ut:

klass 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")) Klicka på (); . Driver.FindElement (By.LinkText ( "Registrera")) Klicka på (); driver.FindElement (By.Id ( "Användarnamn")) Klar ().; driver.FindElement (By.Id ( "Användarnamn")) Sendkeys ( "HJSimpson."); driver.FindElement (By.Id ( "Lösenord")) Clear (). driver.FindElement (By.Id ( "Lösenord")) Sendkeys ( "2345Qwert!"). driver.FindElement (By.Id ( "ConfirmPassword")) Klar ().; driver.FindElement (By.Id ( "ConfirmPassword")) Sendkeys ( "2345Qwert!"). driver.FindElement (By.CssSelector ( "input [type = \" submit \ "]")) Klicka ().; . Driver.FindElement (By.LinkText ( "Disco")) Klicka på (); driver.FindElement (By.CssSelector ("img [alt = \" Le Freak \ "]")). Klicka på (); driver.FindElement (By.LinkText ("Lägg i kundvagn")). Klicka på (); driver.FindElement (By.LinkText ("Checkout >>")). Klicka på (); driver.FindElement (By.Id ( "Firstname")) Klar ().; driver.FindElement (By.Id ( "Firstname")) Sendkeys ( "Homer."); driver.FindElement (By.Id ( "Lastname")) Klar ().; driver.FindElement (By.Id ( "Lastname")) Sendkeys ( "Simpson").; driver.FindElement (By.Id ( "Adress")) Clear (). driver.FindElement (By.Id ("Address")). SendKeys ("742 Evergreen Terrace"); driver.FindElement (By.Id ( "City")) Clear (). driver.FindElement (By.Id ( "City")) Sendkeys ( "Springfield"). driver.FindElement (By.Id ( "State")) Clear (). driver.FindElement (By.Id ( "stat")) Sendkeys ( "Kentucky."); driver.FindElement (By.Id ( "Postal")) Klar ().; driver.FindElement (By.Id ( "Postal")) Sendkeys ( "123456").; driver.FindElement (By.Id ( "land")) Clear (). driver.FindElement (By.Id ("Country")). SendKeys ("United States"); driver.FindElement (By.Id ( "Telefon")) Clear (). driver.FindElement (By.Id ( "Telefon")) Sendkeys ( "2341231241").; driver.FindElement (By.Id ( "Skicka")) Clear (). driver.FindElement (By.Id ( "Skicka")) Sendkeys ( "[email protected]."); driver.FindElement (By.Id ( "promocode")) Klar ().; driver.FindElement (By.Id ( "promocode")) Sendkeys ( "FREE"). driver.FindElement (By.CssSelector ( "input [type = \" submit \ "]")) Klicka ().; Assert.IsTrue (driver.PageSource.Contains ("Checkout Complete")); 

Du kan hitta BrittleTest klass här.

Värden är en statisk klass, med en enda statisk egenskap: Exempel, som vid instantiering brinner upp IIS Express på webbplatsen som testas och binder Firefox WebDriver till webbläsarens instans. När testet är klart stänger det automatiskt webbläsaren och IIS Express.

Det här testet brinner upp en webbläsare, går till hemsidan på Mvc Music Store hemsida, registrerar en ny användare, bläddrar till ett album, lägger till det i vagnen och checkar ut.

Man kan argumentera för att detta test gör för mycket och det är därför det är sprött; men storleken på detta test är inte anledningen till att det är skört - det är hur det skrivs som gör det en mardröm att behålla.

Det finns olika tankskolor vid UI-testning och hur mycket varje test ska täcka. Vissa tror att det här testet gör för mycket och vissa tycker att ett test borde täcka ett verkligt scenario, slutet till slutet, och betrakta detta ett perfekt test (underhållbarhet åt sidan).

Så vad är fel med detta test?

  • Detta är processkod. En av huvudproblemen i denna kodstämpel är läsbarhet eller brist på det. Om du vill ändra testet eller om det bryts eftersom en av de inblandade sidorna har ändrats, kommer du svårt att ta reda på vad du ska ändra och att dra en linje mellan funktionssektionerna. eftersom det är allt en stor hög med kod där vi får "föraren" för att hitta ett element på sidan och att göra något med det. Ingen modularitet.
  • Det här testet i sig kan inte ha mycket dubbelarbete men några fler tester så här och du kommer ha en hel del duplicerad väljare och logik för att interagera med webbsidor från olika tester. Till exempel By.Id ( "Användarnamn") väljaren kommer att dupliceras i alla tester som kräver registrering, och driver.FindElement (By.Id ( "Användarnamn")). Clear () och driver.FindElement (By.Id ( "Användarnamn")). Sendkeys ("") dupliceras var som helst du vill interagera med UserName textbox. Då finns det hela registreringsformuläret, och utbetalningsformulär etc. som kommer att upprepas i alla tester som behöver interagera med dem! Duplicerade koden leder till underhålls mardrömmar.
  • Det finns många magiska strängar överallt, vilket återigen är en underhållsfråga.

Testkod är kod!

Det finns också mönster som låter dig skriva mer underhållbara gränssnittstester.

På samma sätt som din faktiska kod måste du behålla dina test. Så ge dem samma behandling.

Vad handlar det om test som får oss att tro att vi kan förlora kvalitet i dem? Om något är en dålig testpaket enligt min åsikt mycket svårare att behålla än dålig kod. Jag har haft dåliga delar av arbetskoden i produktion i år som aldrig bröt och jag fick aldrig röra dem. Visst var det fult och svårt att läsa och underhålla men det fungerade och det behövde inte ändras så att den verkliga underhållskostnaden var noll. Situationen är inte riktigt densamma för dåliga tester men: för dåliga tester kommer att bryta och fixa dem kommer att bli svåra. Jag kan inte räkna hur många gånger jag har sett utvecklare undviker testning eftersom de tycker att skriva tester är ett stort slöseri med tid eftersom det tar för mycket tid att behålla.

Testkod är kod: Använder du SRP på din kod? Då ska du också använda det på dina test. Är din kod DRY? Torka sedan upp dina tester också. Om du inte skriver bra test (användargränssnitt eller annat) kommer du att slösa mycket tid att behålla dem.

Det finns också mönster som låter dig skriva mer underhållbara gränssnittstester. Dessa mönster är plattform agnostic: Jag har använt dessa mycket samma idéer och mönster för att skriva UI-tester för WPF-applikationer och webbapplikationer skrivna i ASP.Net och Ruby on Rails. Så oavsett din teknikstack borde du kunna göra dina UI-test mycket mer underhållbara genom att följa några enkla steg.

Presentation av sidobjektmönstret

Många av de ovan nämnda problemen är förankrade i testprocessens processuella karaktär och lösningen är lätt: Objektorientering.

Sidobjekt är ett mönster som används för att tillämpa objektorientering på gränssnittstester. Från Selenium wiki:

Inom din webbapps användargränssnitt finns områden som dina test interagerar med. Ett sidobjekt modellerar dem bara som objekt i testkoden. Detta minskar mängden duplicerad kod och innebär att om användargränssnittet ändras behöver fixningsbehovet endast tillämpas på ett ställe.

Tanken är att för varje sida i din ansökan / hemsida vill du skapa ett sidobjekt. Sidobjekt är i princip UI-automation som motsvarar dina webbsidor.

Jag har gått vidare och refactored logiken och växelverkan från BrittleTest till några sidobjekt och skapat ett nytt test som använder dem istället för att träffa webdrivrutinen direkt. Du kan hitta det nya testet här. Koden kopieras här för din referens:

public class 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") .VäljAlbumByName ("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"); 

Givetvis har testkroppen inte minskat mycket i storlek och i själva verket var jag tvungen att skapa sju nya klasser för att stödja detta test. Trots att fler koden krävs, fixade vi en hel del problem som det ursprungliga spröda testet hade (mer på detta längre ner). För nu, låt oss dyka lite djupare in i sidobjektmönstret och vad vi gjorde här.

Med sidobjektmönstret skapar du vanligtvis en sidobjektklass per webbsida under test där klassmodellerna och inkapslar interaktioner med sidan. Så en textruta på din webbsida blir en strängegenskap på sidobjektet och för att fylla den textrutan har du bara ställt in den här textegenskapen till önskat värde istället för:

driver.FindElement (By.Id ( "Skicka")) Clear (). driver.FindElement (By.Id ( "Skicka")) Sendkeys ( "[email protected].");

vi kan skriva:

registerPage.Email = "[email protected]";

var registerPage är en förekomst av RegisterPage-klassen. En kryssruta på sidan blir en boolegenskap på sidobjektet och kryssrutan och avmarkera kryssrutan handlar bara om att ställa in den booleska egenskapen till sann eller falsk. På samma sätt blir en länk på webbsidan en metod på sidobjektet och genom att klicka på länken blir det att anropa metoden på sidobjektet. Så istället för:

. Driver.FindElement (By.LinkText ( "Admin")) Klicka på ();

vi kan skriva:

homepage.GoToAdminForAnonymousUser ();

Faktum är att en åtgärd på vår webbsida blir en metod i vårt sidobjekt och som svar på att den åtgärden (dvs. att anropa metoden på sidobjektet) får du en förekomst av ett annat sidobjekt som pekar på den webbsida du bara har navigerat till genom att vidta åtgärden (t.ex. inlämna en blankett eller klicka på en länk). På så vis kan du enkelt kedja dina visningsinteraktioner i ditt testskript:

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

Här, efter att ha registrerat användaren, tas jag till hemsidan (en förekomst av dess sidobjekt returneras av SubmitRegistration metod). Så på HomePage-förekomsten jag ringer SelectGenreByName som klickar på en "Disco" länk på sidan som returnerar en instans av AlbumBrowsePage och sedan på den sidan jag ringer SelectAlbumByName som klickar på albumet "Le Freak" och returnerar en instans av AlbumDetailsPage och så vidare och så vidare.

Jag erkänner det: det är många klasser för vad som brukade vara någon klass alls; men vi fick många fördelar med denna praxis. För det första är koden inte längre processuell. Vi har en väl inbyggd testmodell där varje objekt ger bra inkapsling av interaktion med en sida. Så till exempel om något ändras i din registreringslogik, är det enda du behöver ändra på din RegisterPage-klass i stället för att behöva gå igenom hela testpaketet och ändra varje enskild interaktion med registreringsvyn. Denna modularitet ger också bra återanvändbarhet: du kan återanvända din ShoppingCartPage överallt behöver du interagera med kundvagnen. Så i en enkel övning av att flytta från procedur till objektorienterad testkod eliminerade vi nästan tre av de fyra frågorna med det första spröda testet som var procedurkod, och logik och väljare duplicering. Vi har fortfarande lite dubbelarbete men som vi kommer att fixa inom kort.

Hur genomförde vi faktiskt dessa sidobjekt? Ett sidobjekt i sin rot är inget annat än en omslag runt interaktionerna med sidan. Här har jag bara extraherat UI-interaktioner våra av de spröda testen och sätter dem i sina egna sidobjekt. Till exempel extraherades registreringslogiken till sin egen klass som heter RegisterPage som såg ut så här:

public class RegisterPage: Page public HomePage SubmitRegistration () returnera NavigateTo(By.CssSelector ( "input [type = 'Skicka']"));  public string Användarnamn set Execute (By.Name ("UserName"), e => e.Clear (); e.SendKeys (värde););  public string Email set Execute (By.Name ("Email"), e => e.Clear (); e.SendKeys (värde););  public string BekräftaPassword set Execute (By.Name ("ConfirmPassword"), e => e.Clear (); e.SendKeys (värde););  allmän sträng Lösenord set Execute (By.Name ("Password"), e => e.Clear (); e.SendKeys (värde);); 

Jag har skapat en Sida superklass som tar hand om några saker, som Navigera till vilket hjälper navigera till en ny sida genom att ta en åtgärd och Kör som utför vissa åtgärder på ett element. De Sida klassen såg ut:

Public Class Page skyddad RemoteWebDriver WebDriver get returnera Host.Instance.WebDriver;  allmän sträng Titel get return WebDriver.Title;  offentlig TPage NavigateTo(By By) där TPage: Page, new () WebDriver.FindElement (by) .Click (); returnera Activator.CreateInstance();  public void Execute (By by, Action åtgärd) var element = WebDriver.FindElement (by); åtgärden (element); 

I BrittleTest, att interagera med ett element som vi gjorde FindElement en gång per åtgärd. De Kör Metod, förutom att abstrahera webbdrivrutins interaktion, har en extra fördel som gör det möjligt att välja ett element, vilket kan vara en dyr handling, en gång och vidta flera åtgärder på den:

driver.FindElement (By.Id ( "Lösenord")) Clear (). driver.FindElement (By.Id ( "Lösenord")) Sendkeys ( "2345Qwert!").

ersattes med:

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

Tar en andra titt på RegisterPage Sidobjekt ovanför har vi fortfarande en bit dubbelarbete där inne. Testkoden är kod och vi vill inte göra dubbelarbete i vår kod; så låt oss reflektera det. Vi kan extrahera koden som krävs för att fylla i en textruta i en metod på Sida klass och ring bara det från sidobjekt. Metoden kan implementeras som:

public void SetText (strängelementnamn, sträng newText) Execute (By.Name (elementName), e => e.Clear (); e.SendKeys (newText);); 

Och nu fastigheterna på RegisterPage kan krympas till:

allmän sträng Användarnamn set SetText ("UserName", värde); 

Du kan också skapa ett flytande API för att göra setter läs bättre (t.ex.. Fill ( "Användarnamn"). Med (värde)) men jag lämnar det till dig.

Vi gör inget extra här här. Bara enkel refactoring på vår testkod som vi alltid gjort för vår, errrr, "annan" kod!!

Du kan se hela koden för Sida och RegisterPage klasser här och här.

Starkt typat sidobjekt

Vi löste processuella problem med det spröda testet som gjorde testet mer läsbart, modulärt, torkare och effektivt underhållbart. Det finns en sista fråga som vi inte åtgärda: det finns fortfarande många magiska strängar överallt. Inte riktigt en mardröm men fortfarande ett problem vi kunde fixa. Ange starkt skrivna sidobjekt!

Detta tillvägagångssätt är praktiskt om du använder en MV * -ram för ditt användargränssnitt. I vårt fall använder vi ASP.Net MVC.

Låt oss ta en titt på RegisterPage:

public class RegisterPage: Page public HomePage SubmitRegistration () returnera NavigateTo(By.CssSelector ( "input [type = 'Skicka']"));  allmän sträng Användarnamn set SetText ("UserName", värde);  public string Email set SetText ("Email", värde);  public string BekräftaPassword set SetText ("ConfirmPassword", värde);  allmän sträng lösenord set SetText ("Password", värde); 

Den här sidan modellerar Register-vyn i vår webbapp (bara kopiering av toppbiten här för din bekvämlighet):

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

Hmmm, vad är det där RegisterModel där? Det är View Model för sidan: M i MVC. Här är koden (jag tog bort attributen för att minska bruset):

public class RegisterModel public string UserName get; uppsättning;  public string E-post get; uppsättning;  allmän sträng lösenord get; uppsättning;  public string BekräftaPassword get; uppsättning; 

Det ser väldigt bekant ut, eller hur? Den har samma egenskaper som RegisterPage klass som inte är överraskande överväger RegisterPage skapades baserat på denna vy och visningsmodell. Låt oss se om vi kan dra nytta av visningsmodeller för att förenkla våra sidobjekt.

Jag har skapat en ny Sida superklass; men en generisk. Du kan se koden här:

offentlig klass sidan : Sida där TViewModel: klass, ny () public void FillWith (TViewModel viewModel, IDictionary> propertyTypeHandling = null) // borttagen för korthet

De Sida klass underklasser den gamla Sida klass och ger all sin funktionalitet; men det har också en extra metod som heter Fyll med vilket fyller i sidan med visad modellmodell! Så nu min RegisterPage klassen ser ut som:

public class RegisterPage: Page public HomePage CreateValidUser (RegisterModel-modell) FillWith (modell); returnera NavigateTo(By.CssSelector ( "input [type = 'Skicka']")); 

Jag duplicerade alla sidobjekt för att visa båda varianterna och även att göra kodbasen enklare att följa för dig; men i verkligheten behöver du en klass för varje sidobjekt.

Efter att ha konverterat mina sidobjekt till generiska så ser testet ut:

public class StronglyTypedPageObjectWithComponent [Test] public void Can_buy_an_Album_when_registered () var orderedPage = HomePage.Initiate () .GoToAdminForAnonymousUser () .GoToRegisterPage () .CreateValidUser (ObjectMother.CreateRegisterModel ()) .VäljGenreByName ("Disco") .VäljAlbumByName Freak ") .AddAlbumToCart () .Checkout () .SubmitShippingInfo (ObjectMother.CreateShippingInfo ()," Free "); Assert.AreEqual ("Checkout Complete", orderedPage.Title); 

Det är det - hela testet! Mycket mer läsbar, torr och underhållbar, är det inte?

De ObjectMother klass som jag använder i testet är en Objektmor som tillhandahåller testdata (kod kan hittas här), inget fancy:

public class ObjectMother public static Order CreateShippingInfo () var shippingInfo = ny order FirstName = "Homer", LastName = "Simpson", Adress = "742 Evergreen Terrace", Stad = "Springfield", Stat = "Kentucky", PostalCode = "123456", Land = "Förenta staterna", Telefon = "2341231241", E-post = "[email protected]"; returnera fraktInfo;  offentliga statiska RegisterModel CreateRegisterModel () var model = new RegisterModel UserName = "HJSimpson", Email = "[email protected]", Lösenord = "! 2345Qwert", ConfirmPassword = "! 2345Qwert"; returmodell; 

Stoppa inte vid sidobjektet

Vissa webbsidor är väldigt stora och komplexa. Tidigare sa jag att testkoden är kod och vi borde behandla den som sådan. Vi brukar bryta stora och komplexa webbsidor till mindre och i vissa fall återanvändbara (partiella) komponenter. Detta gör det möjligt för oss att komponera en webbsida från mindre, mer hanterbara komponenter. Vi borde göra samma för våra test. För att göra detta kan vi använda sidkomponenter.

En sidkomponent är ungefär som ett sidobjekt: det är en klass som inkapslar interaktion med vissa element på en sida. Skillnaden är att den interagerar med en liten del av en webbsida: det modellerar en användarkontroll eller en partiell vy, om du vill. Ett bra exempel på en sidkomponent är en menyrade. En menyrad visas vanligen på alla sidor i en webbapplikation. Du vill verkligen inte fortsätta att upprepa den kod som krävs för att interagera med menyn i varje enskilt sidobjekt. I stället kan du skapa en menysida komponent och använda den från dina sidobjekt. Du kan också använda sidkomponenter för att hantera datanät på dina sidor, och för att ta det ett steg längre kan nätverkssidkomponenten själv bestå av rader sidkomponenter. När det gäller Mvc Music Store kunde vi ha en TopMenuComponent och a SideMenuComponent och använd dem från vår Hemsida.

Som i din webbapplikation kan du också skapa en, säg, Layout sidobjekt som modellerar din layout / mastersida och använder det som en superklass för alla dina andra sidobjekt. Layoutsidan skulle då bestå av menysida komponenter så att alla sidor kan träffa menyerna. Jag antar att en bra tumregel är att ha en sidkomponent per delvy, ett layoutsidobjekt per layout och ett sidobjekt per webbsida. På så vis vet du att din testkod är som granualar och väl komponerad som din kod.

Några ramar för UI-testning

Vad jag visade ovan var ett mycket enkelt och konstruerat prov med några stödjande klasser som infrastruktur för test. I verkligheten är kraven för UI-testning mycket mer komplex än det: det finns komplexa kontroller och interaktioner, du måste skriva till och läsa från dina sidor, du måste hantera nätverkslatenser och ha kontroll över AJAX och andra Javascript-interaktioner, måste avfyra olika webbläsare och så vidare som jag inte förklarade i den här artikeln. Även om det är möjligt att koda runt alla dessa kan du spara mycket tid med hjälp av vissa ramar. Här är de ramar som jag starkt rekommenderar:

Ramar för. Net:

  • Seleno är ett open source-projekt från TestStack som hjälper dig att skriva automatiska gränssnittstest med Selen. Det fokuserar på användningen av sidobjekt och sidkomponenter och genom att läsa från och skriva till webbsidor med hjälp av starkt skrivna visningsmodeller. Om du gillade vad jag gjorde i den här artikeln kommer du också att vilja Seleno eftersom det mesta av koden som visas här lånades från Seleno-kodbasen.
  • White är en öppen källkod från TestStack för att automatisera rika klientprogram baserat på Win32, WinForms, WPF, Silverlight och SWT (Java) -plattformar.

Upplysningar: Jag är medstifter och medlem i utvecklingslaget i TestStack-organisationen.

Ramar för Ruby:

  • Capybara är ett godkännande test ram för webbapplikationer som hjälper dig att testa webbapplikationer genom att simulera hur en riktig användare skulle interagera med din app.
  • Poltergeist är en förare för Capybara. Det låter dig köra dina Capybara-test på en huvudlös WebKit-webbläsare, som tillhandahålls av PhantomJS.
  • sidobjekt (jag har inte personligen använt denna pärla) är en enkel pärla som hjälper till att skapa flexibla sidobjekt för att testa webbläsarbaserade applikationer. Målet är att underlätta att skapa abstraktionslager i dina test för att koppla bort testen från objektet som de testar och för att ge ett enkelt gränssnitt till elementen på en sida. Det fungerar med både watir-webdriver och selen-webdriver.

Slutsats

Vi började med en typisk UI-automationsupplevelse, förklarade varför UI-test misslyckades, gav ett exempel på ett sprött test och diskuterade dess problem och löst dem med några idéer och mönster.

Om du vill ta en poäng från den här artikeln ska det vara: Testkod är kod. Om du tänker på det var allt jag gjorde i den här artikeln att tillämpa de goda kodnings- och objektorienterade metoderna du redan vet för ett användargränssnitt.

Det finns fortfarande mycket att lära sig om UI-testning och jag kommer att försöka diskutera några av de mer avancerade tipsen i en framtida artikel.

Glad testning!