Onderhoudbare geautomatiseerde gebruikersinterfests

Een paar jaar geleden was ik zeer sceptisch over geautomatiseerde UI-tests en deze scepsis was het gevolg van een paar mislukte pogingen. Ik zou een aantal geautomatiseerde UI-tests schrijven voor desktop- of webapplicaties en een paar weken later rukte ik ze uit de codebase omdat de onderhoudskosten te hoog waren. Dus ik dacht dat UI testen moeilijk was en dat, hoewel het veel voordelen bood, het het beste was om het tot een minimum te beperken en alleen de meest complexe workflows in een systeem testte met behulp van UI-testen en de rest aan unit tests over te laten. Ik herinner me dat ik mijn team vertelde over de testpiramide van Mike Cohn en dat in een typisch systeem meer dan 70% van de tests eenheidstests, ongeveer 5% UI-tests en de restintegratietests zouden moeten zijn.

Dus ik dacht dat UI testen moeilijk was en dat, hoewel het veel voordelen bood, het het beste was om het tot een minimum te beperken ...

Ik had het mis! Natuurlijk, UI-testen kunnen moeilijk zijn. Het kost veel tijd om UI-tests correct te schrijven. Ze zijn veel langzamer en brosser dan unit-tests omdat ze grenzen overschrijden en grenzen verwerken, ze raken de browser, het gaat om UI-elementen (bijvoorbeeld HTML, JavaScript) die voortdurend veranderen, ze raken de database, het bestandssysteem en mogelijk netwerkservices. Als een van deze bewegende delen niet goed speelt, heb je een gebroken test; maar dat is ook het mooie van UI-tests: ze testen uw systeem end-to-end. Geen andere test geeft u zoveel als of een grondige dekking. Geautomatiseerde gebruikersinterfacetests kunnen, indien goed gedaan, de beste elementen in uw regressiesuite zijn.

Dus in de afgelopen paar projecten hebben mijn UI-tests meer dan 80% van mijn tests gevormd! Ik moet ook vermelden dat deze projecten meestal CRUD-applicaties zijn met niet veel bedrijfslogica en laten we eerlijk zijn - de overgrote meerderheid van softwareprojecten vallen in deze categorie. De bedrijfslogica moet nog steeds eenheidsgetest zijn; maar de rest van de applicatie kan grondig worden getest via UI-automatisering.


UI-tests zijn verkeerd uitgevoerd

Ik zou willen ingaan op wat ik fout heb gedaan, wat ook heel typerend lijkt te zijn tussen ontwikkelaars en testers die beginnen met UI-automatisering.

Dus wat gaat er mis en waarom? Veel teams starten UI-automatisering met schermrecorders. Als je webautomatisering met Selenium doet, heb je waarschijnlijk Selenium IDE gebruikt. Van de Selenium IDE homepage:

De Selenium-IDE (Integrated Development Environment) is de tool die u gebruikt om uw Selenium-testcases te ontwikkelen.

Dit is eigenlijk een van de redenen waarom UI-testen een vreselijke ervaring worden: je downloadt en start een schermrecorder op en navigeert naar je website en gaat op klik, klik, type, klik, type, tabblad, type, tabblad, type, klik en doen gelden. Daarna herhaal je de opname en het werkt. Zoet!! Dus je exporteert de acties als een testscript, stopt het in je code, verpakt het in een test en voert de test uit en ziet de browser tot leven komen voor je ogen en je tests verlopen heel soepel. Je raakt heel opgewonden, deelt je bevindingen met je collega's en laat het aan je baas zien en ze raken heel enthousiast en gaan: "Automatiseer ALLE DINGEN"

Een week later en je hebt 10 geautomatiseerde UI-tests en alles lijkt geweldig. Vervolgens wordt u gevraagd om de gebruikersnaam te vervangen door het e-mailadres omdat dit voor verwarring bij gebruikers heeft gezorgd, en dat doet u ook. Vervolgens voer je, net als elke andere geweldige programmeur, je UI-testsuite uit, om te zien dat 90% van je tests wordt verbroken omdat je voor elke test de gebruiker aanmeldt met gebruikersnaam en de veldnaam is gewijzigd en het twee uur kost om alle de verwijzingen naar gebruikersnaam in je tests met e-mail en om de tests weer groen te krijgen. Hetzelfde gebeurt keer op keer en op een gegeven moment vind je jezelf uren per dag bezig met het repareren van kapotte tests: tests die niet zijn afgebroken omdat er iets fout is gegaan met je code; maar omdat je een veldnaam in je database / model hebt gewijzigd of je pagina enigszins hebt geherstructureerd. Een paar weken later stop je met het uitvoeren van je tests vanwege deze enorme onderhoudskosten, en concludeer je dat UI-tests slecht zijn.

Gebruik Selenium IDE of een andere schermrecorder NIET om uw testcases te ontwikkelen. Dat gezegd hebbende, het is niet de schermrecorder zelf die leidt tot een broos testsysteem; het is de code die ze genereren met inherente onderhoudsproblemen. Veel ontwikkelaars hebben nog steeds een broze UI-testsuite, zelfs zonder schermrecorders te gebruiken, alleen omdat hun tests dezelfde kenmerken vertonen.

Alle tests in dit artikel zijn geschreven tegen de Mvc Music Store-website. De website zoals deze heeft enkele problemen waardoor testen in de gebruikersinterface nogal moeilijk gaat, daarom heb ik de code geporteerd en de problemen verholpen. Je kunt de code die ik aan het schrijven ben testen op de GitHub repo voor dit artikel hier vinden

Dus hoe ziet een brosse test eruit? Het ziet er ongeveer zo uit:

class 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")) Klik op (); . Driver.FindElement (By.LinkText ( "register")) Klik op (); driver.FindElement (By.Id ( "username")) Clear (.); driver.FindElement (By.Id ( "UserName")) SendKeys ( "HJSimpson."); driver.FindElement (By.Id ( "Password")) Clear (.); driver.FindElement (By.Id ( "Password")) SendKeys ( "2345Qwert!."); driver.FindElement (By.Id ( "confirmPassword")) Clear (.); driver.FindElement (By.Id ( "confirmPassword")) SendKeys ( "2345Qwert!."); driver.FindElement (By.CssSelector ( "input [type = \" submit \ "]")) Instructies ().; . Driver.FindElement (By.LinkText ( "Disco")) Klik op (); driver.FindElement (By.CssSelector ("img [alt = \" Le Freak \ "]")). Klik (); driver.FindElement (By.LinkText ("Toevoegen aan winkelwagentje")) Klik op (); driver.FindElement (By.LinkText ("Checkout >>")). Klik op (); driver.FindElement (By.Id ( "Voornaam")) Clear (.); driver.FindElement (By.Id ( "Voornaam")) SendKeys ( "Homer").; driver.FindElement (By.Id ( "LastName")) Clear (.); driver.FindElement (By.Id ( "achternaam")) SendKeys ( "Simpson."); driver.FindElement (By.Id ( "Address")) 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 ( "State")) SendKeys ( "Kentucky."); driver.FindElement (By.Id ( "PostalCode")) Clear (.); driver.FindElement (By.Id ( "PostalCode")) SendKeys ( "123456").; driver.FindElement (By.Id ( "Land")) Clear (.); driver.FindElement (By.Id ("Land")). SendKeys ("Verenigde Staten"); driver.FindElement (By.Id ( "telefoon")) Clear (.); driver.FindElement (By.Id ( "telefoon")) SendKeys ( "2341231241."); driver.FindElement (By.Id ( "E-mail")) Clear (.); driver.FindElement (By.Id ( "E-mail")) SendKeys ( "[email protected]."); driver.FindElement (By.Id ( "promocode")) Clear (.); driver.FindElement (By.Id ( "promocode")) SendKeys ( "FREE."); driver.FindElement (By.CssSelector ( "input [type = \" submit \ "]")) Instructies ().; Assert.IsTrue (driver.PageSource.Contains ("Checkout voltooid")); 

Je kunt de BrittleTest klasse hier.

Host is een statische klasse met één statische eigenschap: Aanleg, die bij instantiatie IIS Express op de te testen website activeert en Firefox WebDriver aan de browserinstantie bindt. Wanneer de test is voltooid, worden de browser en IIS Express automatisch gesloten.

Deze test activeert een webbrowser, gaat naar de startpagina van de Mvc Music Store-website, registreert een nieuwe gebruiker, bladert naar een album, voegt het toe aan de winkelwagen en checkt uit.

Je zou kunnen beweren dat deze test te veel doet en daarom is het broos; maar de grootte van deze test is niet de reden waarom het broos is - het is hoe het is geschreven dat het een nachtmerrie maakt om te onderhouden.

Er zijn verschillende stromingen over UI-testen en hoeveel elke test zou moeten dekken. Sommigen geloven dat deze test te veel doet en sommigen denken dat een test een reëel scenario moet omvatten, van begin tot eind, en beschouw dit als een perfecte test (onderhoudbaarheid terzijde).

Dus wat is er mis met deze test?

  • Dit is procedurele code. Een van de belangrijkste problemen van deze codering is de leesbaarheid, of het ontbreken daarvan. Als u de test wilt wijzigen of als deze breekt omdat een van de betrokken pagina's is gewijzigd, zult u moeite hebben om uit te zoeken wat u moet wijzigen en een lijn te trekken tussen de functionaliteitsecties; omdat het allemaal een grote stapel code is waar we de 'bestuurder' een element op de pagina kunnen vinden en er iets mee kunnen doen. Geen modulariteit.
  • Deze ene test zelf heeft misschien niet veel duplicatie, maar nog een paar tests zoals deze en je hebt veel gedupliceerde selector en logica om te communiceren met webpagina's van verschillende tests. Bijvoorbeeld By.Id ( "username") selector wordt gedupliceerd in alle tests waarvoor registratie vereist is, en driver.FindElement (By.Id ( "Username")). Wissen () en driver.FindElement (By.Id ( "Username")). SendKeys ("") worden overal waar u wilt communiceren gedupliceerd met het tekstvak UserName. Dan is er het hele registratieformulier en het afrekenformulier, enz. Dat in alle tests zal worden herhaald, die met hen moeten communiceren! Gedupliceerde code leidt tot nachtmerries over onderhoudbaarheid.
  • Er zijn overal heel wat magische snaren, wat opnieuw een onderhoudsprobleem is.

Testcode is code!

Er zijn ook patronen waarmee u onderhoudsvriendelijke gebruikersinterventietests kunt schrijven.

Net als je eigenlijke code, moet je je testen behouden. Dus geef ze dezelfde behandeling.

Waar gaat het om testen die ons doen denken dat we kunnen afzien van kwaliteit in hen? Als er iets is, is een slechte testsuite naar mijn mening een stuk moeilijker te onderhouden dan slechte code. Ik heb jarenlang slechte stukjes werkcode in productie gehad die nooit gebroken zijn en ik heb ze nooit hoeven aan te raken. Natuurlijk was het lelijk en moeilijk te lezen en te onderhouden, maar het werkte en het had geen verandering nodig, dus de echte onderhoudskosten waren nul. De situatie is echter niet helemaal hetzelfde voor slechte tests: omdat slechte tests zullen breken en het oplossen ervan moeilijk zal zijn. Ik kan het aantal keren tellen dat ik heb gezien dat ontwikkelaars testen vermijden omdat ze denken dat schrijftests een enorme verspilling van tijd zijn omdat het te veel tijd kost om te onderhouden.

Testcode is code: past u SRP toe op uw code? Dan zou je het ook op je tests moeten toepassen. Is uw code DROOG? Verdrink dan ook je testen. Als u geen goede tests schrijft (UI of anderszins), verspilt u veel tijd aan het onderhoud ervan.

Er zijn ook patronen waarmee u onderhoudsvriendelijke gebruikersinterventietests kunt schrijven. Deze patronen zijn platformonafhankelijk: ik heb dezelfde ideeën en patronen gebruikt om UI-tests voor WPF-applicaties en webtoepassingen geschreven in ASP.Net en Ruby on Rails te schrijven. Dus ongeacht uw technologische stack, moet u uw UI-tests een stuk beter kunnen onderhouden door een paar eenvoudige stappen te volgen.

Introductie van het pagina-objectpatroon

Veel van de bovengenoemde problemen zijn geworteld in de procedurele aard van het testscript en de oplossing is eenvoudig: Objectoriëntatie.

Page Object is een patroon dat wordt gebruikt om objectgerichtheid toe te passen op UI-tests. Van de Selenium-wiki:

In de gebruikersinterface van uw web-app zijn er gebieden waar uw tests op reageren. Een Page Object modelleert deze eenvoudig als objecten binnen de testcode. Dit vermindert het aantal gedupliceerde code en houdt in dat als de gebruikersinterface verandert, de fixatie slechts op één plaats hoeft te worden toegepast.

Het idee is dat u voor elke pagina in uw toepassing / website één Page-object wilt maken. Pagina-objecten zijn in feite het UI-automatiseringsequivalent van uw webpagina's.

Ik heb de logica en interacties uit de BrittleTest doorgevoerd in enkele pagina-objecten en een nieuwe test gemaakt die ze gebruikt in plaats van direct op de webdriver te slaan. Je kunt de nieuwe test hier vinden. De code wordt hier voor uw referentie gekopieerd:

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") .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 = "Verenigde Staten"; shippingPage.Phone = "2341231241"; shippingPage.Email = "[email protected]"; shippingPage.PromoCode = "GRATIS"; var orderPage = shippingPage.SubmitOrder (); Assert.AreEqual (orderpagina.Titel, "Afhandeling voltooid"); 

Toegegeven, de testinstantie is niet veel afgenomen en eigenlijk moest ik zeven nieuwe klassen maken om deze test te ondersteunen. Ondanks de vereiste coderegels, hebben we een hoop problemen opgelost die de oorspronkelijke brosse test had (meer hierover verderop). Laten we voorlopig wat dieper in het patroon van het paginaobject duiken en wat we hier hebben gedaan.

Met het patroon van het Pagina-object maakt u meestal een pagina-objectklasse per geteste webpagina waarnaar de klassenmodellen en interacties met de pagina inkapselen. Een tekstvak in uw webpagina wordt dus een eigenschap string op het pagina-object en om dat tekstvak in te vullen, hoeft u alleen die eigenschap in te stellen op de gewenste waarde in plaats van:

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

we kunnen schrijven:

registerPage.Email = "[email protected]";

waar registerPage is een instantie van de klasse RegisterPage. Een selectievakje op de pagina wordt een bool-eigenschap op het pagina-object. Als u dit selectievakje in- of uitschakelt, hoeft u alleen maar die boolean-eigenschap in te stellen op true of false. Evenzo wordt een koppeling op de webpagina een methode voor het pagina-object en klikken op de koppeling wordt de methode op het pagina-object. Dus in plaats van:

. Driver.FindElement (By.LinkText ( "Admin")) Klik op ();

we kunnen schrijven:

homepage.GoToAdminForAnonymousUser ();

Elke actie op onze webpagina wordt een methode in ons paginaobject en als reactie op het nemen van die actie (dwz de methode aanroepen bij het pagina-object) krijgt u een exemplaar van een ander pagina-object terug dat naar de webpagina verwijst genavigeerd door de actie te ondernemen (bijv. een formulier inzenden of op een link klikken). Op deze manier kunt u uw interacties in de weergave eenvoudig in uw testscript ketenen:

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

Hier word ik na het registreren van de gebruiker naar de startpagina gebracht (een exemplaar van zijn pagina-object wordt geretourneerd door SubmitRegistration methode). Dus op de HomePage-instantie die ik bel SelectGenreByName die op een 'Disco'-link klikt op de pagina die een exemplaar van AlbumBrowsePage retourneert en vervolgens op die pagina die ik bel SelectAlbumByName die op het album 'Le Freak' klikt en een exemplaar van AlbumDetailsPage retourneert, enzovoort enzovoort.

Ik geef het toe: het zijn veel klassen voor wat helemaal geen klas was; maar we hebben veel voordelen behaald uit deze praktijk. Ten eerste is de code niet langer procedureel. We hebben een goed beveiligd testmodel waarbij elk object een goede inkapseling van de interactie met een pagina biedt. Dus als er bijvoorbeeld iets verandert in uw registratielogica, is de enige plaats die u moet wijzigen uw klasse RegisterPage in plaats van dat u uw hele testsuite doorloopt en elke interactie met de registratieweergave verandert. Deze modulariteit zorgt ook voor mooie herbruikbaarheid: je kunt je hergebruiken ShoppingCartPage overal waar je moet communiceren met het winkelwagentje. Dus in een eenvoudige praktijk van het overschakelen van procedurele naar object georiënteerde testcode elimineerden we bijna drie van de vier problemen met de initiële brosse test, die procedurele code waren, en logica en selector duplicatie. We hebben nog steeds een beetje duplicatie, maar dat zullen we binnenkort oplossen.

Hoe hebben we die pagina-objecten daadwerkelijk geïmplementeerd? Een paginaobject in de hoofdmap is niets anders dan een omslag om de interacties die u met de pagina hebt. Hier haalde ik alleen UI-interacties uit onze broze tests en plaatste ze in hun eigen pagina-objecten. De registratielogica is bijvoorbeeld geëxtraheerd naar zijn eigen klasse genaamd RegisterPage dat zag er zo uit:

public class RegisterPagina: Pagina public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ( "input [type = 'voorleggen']"));  public string Gebruikersnaam set Execute (By.Name ("UserName"), e => e.Clear (); e.SendKeys (value););  openbare reeks E-mail set Execute (By.Name ("Email"), e => e.Clear (); e.SendKeys (value););  openbare tekenreeks ConfirmPassword set Execute (By.Name ("ConfirmPassword"), e => e.Clear (); e.SendKeys (value););  public string Wachtwoord set Execute (By.Name ("Password"), e => e.Clear (); e.SendKeys (value);); 

Ik heb een gemaakt Pagina superklasse die voor een paar dingen zorgt, zoals Navigeren naar die helpt bij het navigeren naar een nieuwe pagina door een actie te ondernemen en uitvoeren die sommige acties op een element uitvoert. De Pagina klas leek op:

public class Page beschermd RemoteWebDriver WebDriver krijg return Host.Instance.WebDriver;  openbare tekenreeks Titel krijg return WebDriver.Title;  public TPage NavigateTo(By by) waarbij TPage: Page, nieuw () WebDriver.FindElement (by) .Click (); breng Activator.CreateInstance terug();  public void Execute (By by, Action actie) var element = WebDriver.FindElement (door); Actie (element); 

In de BrittleTest, om te communiceren met een element dat we deden FindElement een keer per actie. De uitvoeren De methode, afgezien van het abstraheren van de interactie tussen webstuurprogramma's, heeft een extra voordeel dat het mogelijk maakt om een ​​element, dat een dure actie zou kunnen zijn, één keer te selecteren en er meerdere acties op te ondernemen:

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

werd vervangen door:

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

Een tweede blik op de RegisterPage pagina object hierboven hebben we nog steeds een beetje duplicatie daar. Testcode is code en we willen geen duplicatie in onze code; dus laten we dat refactiveren. We kunnen de code die nodig is om een ​​tekstvak in te vullen in een methode op de Pagina klasse en noem dat gewoon vanuit pagina-objecten. De methode zou kunnen worden geïmplementeerd als:

public void SetText (string elementName, string newText) Execute (By.Name (elementName), e => e.Clear (); e.SendKeys (newText);); 

En nu zijn de eigendommen aan RegisterPage kan worden verkleind tot:

public string Gebruikersnaam set SetText ("UserName", waarde); 

Je zou er ook een vloeiende API voor kunnen maken om de setter beter te laten lezen (bijv. Fill ( "Username"). Met (waarde)) maar ik laat dat aan jou over.

We doen hier niets buitengewoons. Eenvoudigweg refactoren op onze testcode zoals we altijd hebben gedaan voor onze, errrr, "andere" code!!

U kunt de volledige code voor zien Pagina en RegisterPage lessen hier en hier.

Sterk getypeerd pagina-object

We hebben procedurele problemen opgelost met de brosse test waardoor de test beter leesbaar, modulair, DRYer en effectief te onderhouden was. Er is nog een laatste probleem dat we niet hebben opgelost: er zijn nog steeds veel magische snaren. Niet echt een nachtmerrie maar toch een probleem dat we konden oplossen. Voer sterk getypte pagina-objecten in!

Deze aanpak is praktisch als u een MV * -raamwerk voor uw gebruikersinterface gebruikt. In ons geval gebruiken we ASP.Net MVC.

Laten we nog een keer kijken naar de RegisterPage:

public class RegisterPagina: Pagina public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ( "input [type = 'voorleggen']"));  public string Gebruikersnaam set SetText ("UserName", waarde);  openbare reeks E-mail set SetText ("Email", waarde);  openbare tekenreeks ConfirmPassword set SetText ("ConfirmPassword", waarde);  public string Password set SetText ("Password", value); 

Op deze pagina wordt de weergave Registreren gemodelleerd in onze webapp (alleen het bovenste gedeelte kopiëren voor uw gemak):

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

Hmmm, wat is dat RegisterModel er? Het is het View-model voor de pagina: de M in de MVC. Hier is de code (ik heb de attributen verwijderd om de ruis te verminderen):

public class RegisterModel public string UserName get; vast te stellen;  openbare tekenreeks E-mail get; vast te stellen;  public string Wachtwoord get; vast te stellen;  openbare tekenreeks ConfirmPassword get; vast te stellen; 

Dat ziet er heel bekend uit, is het niet? Het heeft dezelfde eigenschappen als de RegisterPage klasse die niet verrassend is RegisterPage is gemaakt op basis van deze weergave en het weergavemodel. Laten we kijken of we kunnen profiteren van weergavemodellen om onze pagina-objecten te vereenvoudigen.

Ik heb een nieuwe gemaakt Pagina superklasse; maar een generieke. Je kunt de code hier zien:

openbare klaspagina : Pagina waar TViewModel: class, nieuw () public void FillWith (TViewModel viewModel, IDictionary> propertyTypeHandling = null) // ter verkorting verwijderd

De Pagina class subclasses the old Pagina klasse en biedt al zijn functionaliteit; maar het heeft ook een extra methode genaamd FillWith die de pagina invult met het gegeven viewmodel-exemplaar! Dus nu mijn RegisterPage klasse ziet eruit als:

openbare klasse RegisterPagina: pagina public HomePage CreateValidUser (RegisterModel-model) FillWith (model); return NavigateTo(By.CssSelector ( "input [type = 'voorleggen']")); 

Ik heb alle pagina-objecten gedupliceerd om beide varianten te tonen en ook om de codebase gemakkelijker voor u te kunnen volgen; maar in werkelijkheid heb je voor elk paginaobject een klasse nodig.

Na het converteren van mijn pagina-objecten naar generieke nu ziet de test er als volgt uit:

public class StronglyTypedPageObjectWithComponent [Test] public void Can_buy_an_Album_when_registered () var orderedPage = HomePage.Initiate (). GoToAdminForAnonymousUser () .GoToRegisterPage () .CreateValidUser (ObjectMother.CreateRegisterModel ()). SelecteerGenreByName ("Disco") .SelectAlbumByName ("Le Freak ") .AddAlbumToCart () .Checkout () .SubmitShippingInfo (ObjectMother.CreateShippingInfo ()," Free "); Assert.AreEqual ("Afhandeling voltooid", orderedPage.Title); 

Dat is alles - de hele test! Veel leesbaarder, DROOG en onderhoudbaar, is het niet?

De ObjectMother Klasse die ik gebruik in de test is een Object-moeder die testgegevens levert (code is hier te vinden), niets speciaals:

public class ObjectMother public static Order CreateShippingInfo () var shippingInfo = new Order FirstName = "Homer", LastName = "Simpson", Address = "742 Evergreen Terrace", City = "Springfield", State = "Kentucky", PostalCode = "123456", Land = "Verenigde Staten", Telefoon = "2341231241", E-mail = "[email protected]"; retourzendingInfo;  public static RegisterModel CreateRegisterModel () var model = new RegisterModel UserName = "HJSimpson", Email = "[email protected]", Password = "! 2345Qwert", ConfirmPassword = "! 2345Qwert"; retourmodel; 

Stop niet bij het pagina-object

Sommige webpagina's zijn erg groot en complex. Eerder zei ik dat testcode code is en we moeten het als zodanig behandelen. Normaal breken we grote en complexe webpagina's in kleinere en, in sommige gevallen, herbruikbare (gedeeltelijke) componenten. Hiermee kunnen we een webpagina samenstellen uit kleinere, beter beheersbare componenten. We zouden hetzelfde moeten doen voor onze tests. Om dit te doen kunnen we pagina-onderdelen gebruiken.

Een paginacomponent lijkt op een Pagina-object: het is een klasse die interactie met sommige elementen op een pagina inkapselt. Het verschil is dat het samenwerkt met een klein deel van een webpagina: het modelleert een gebruikerscontrole of een gedeeltelijke weergave, als je wilt. Een goed voorbeeld voor een pagina-onderdeel is een menubalk. Een menubalk verschijnt meestal op alle pagina's van een webtoepassing. U wilt niet echt de code herhalen die nodig is om met het menu in elk enkel paginaobject te werken. In plaats daarvan kunt u een component van de menupagina maken en deze gebruiken van uw paginaobjecten. U kunt ook paginaonderdelen gebruiken om met gegevensrasters op uw pagina's om te gaan, en om nog een stapje verder te gaan, zou de component van de rasterpagina zelf kunnen bestaan ​​uit rasterpaginaonderdelen. In het geval van Mvc Music Store kunnen we een TopMenuComponent en een SideMenuComponent en gebruik ze van onze Startpagina.

Net als in uw webapplicatie kunt u ook een, zeg, maken, LayoutPage pagina-object dat uw lay-out / stramienpagina modelleert en gebruik dit als een superklasse voor al uw andere pagina-objecten. De lay-outpagina zou dan samengesteld zijn uit componenten van de menupagina zodat alle pagina's de menu's kunnen raken. Ik denk dat een goede vuistregel zou zijn om een ​​pagina-component per deelweergave, een opmaakpagina-object per opmaak en een pagina-object per webpagina te hebben. Op die manier weet u dat uw testcode zo granualar is en goed is samengesteld als uw code.

Enkele frameworks voor UI-testen

Wat ik hierboven liet zien was een zeer eenvoudige en geforceerde steekproef met een paar ondersteunende klassen als infrastructuur voor tests. In werkelijkheid zijn de vereisten voor UI-testen een stuk ingewikkelder dan dat: er zijn complexe besturingselementen en interacties, u moet schrijven naar en lezen van uw pagina's, u hebt te maken met netwerklatenties en hebt controle over AJAX- en andere Javascript-interacties, moet verschillende browsers activeren, enzovoort, die ik in dit artikel niet heb uitgelegd. Hoewel het mogelijk is om al deze codes te coderen, kan het gebruik van sommige frameworks u veel tijd besparen. Hier zijn de kaders die ik ten zeerste aanbeveel:

Kaders voor .Net:

  • Seleno is een open source-project van TestStack waarmee u geautomatiseerde UI-tests met Selenium kunt schrijven. Het richt zich op het gebruik van pagina-objecten en pagina-componenten en door lezen van en schrijven naar webpagina's met behulp van sterk getypte weergavemodellen. Als je het leuk vond wat ik in dit artikel heb gedaan, dan zul je Seleno ook leuk vinden, omdat de meeste code die hier wordt getoond, is geleend van de codebase van Seleno..
  • White is een open source framework van TestStack voor het automatiseren van rich client-applicaties op basis van Win32, WinForms, WPF, Silverlight en SWT (Java) platforms.

Openbaarmaking: ik ben mede-oprichter en lid van het ontwikkelingsteam in de TestStack-organisatie.

Kaders voor Ruby:

  • Capybara is een acceptatietestraamwerk voor webtoepassingen waarmee u webtoepassingen kunt testen door te simuleren hoe een echte gebruiker met uw app zou werken.
  • Poltergeist is een piloot voor Capybara. Hiermee kunt u uw Capybara-tests uitvoeren op een headless WebKit-browser, geleverd door PhantomJS.
  • page-object (ik heb dit juweel niet persoonlijk gebruikt) is een eenvoudig juweeltje dat helpt bij het maken van flexibele pagina-objecten voor het testen van browsergebaseerde applicaties. Het doel is om het maken van abstractielagen in uw tests te vergemakkelijken om de tests los te koppelen van het item dat ze testen en om een ​​eenvoudige interface voor de elementen op een pagina te bieden. Het werkt met zowel watir-webdriver en selenium-webdriver.

Conclusie

We zijn begonnen met een typische UI-automatiseringservaring, legden uit waarom UI-tests mislukken, leverden een voorbeeld van een brosse test en bespraken de problemen en verhielden deze met een paar ideeën en patronen.

Als u één punt uit dit artikel wilt halen, moet dit zijn: Testcode Is Code. Als je erover nadenkt, deed ik in dit artikel alleen maar de goede codering en objectgeoriënteerde praktijken die je al kent, toe te passen op een UI-test.

Er is nog veel te leren over UI-testen en ik zal proberen een aantal van de meer geavanceerde tips in een toekomstig artikel te bespreken.

Happy Testing!