Test UI automatizzati manutenibili

Alcuni anni fa ero molto scettico riguardo ai test automatici dell'interfaccia utente e questo scetticismo è nato da alcuni tentativi falliti. Scriverei alcuni test dell'interfaccia utente automatici per applicazioni desktop o web e qualche settimana dopo li strapperei dal codebase perché il costo per mantenerli era troppo alto. Quindi pensavo che il test dell'interfaccia utente fosse difficile e che, sebbene fornisse molti benefici, era meglio tenerlo al minimo e testare solo i flussi di lavoro più complessi in un sistema attraverso i test dell'interfaccia utente e lasciare il resto ai test unitari. Ricordo di aver detto al mio team della piramide di test di Mike Cohn e che in un sistema tipico oltre il 70% dei test doveva essere unit test, circa il 5% dei test dell'interfaccia utente e il resto dei test di integrazione.

Quindi pensavo che il test dell'interfaccia utente fosse difficile e che, sebbene fornisse molti vantaggi, era meglio tenerlo al minimo ...

Mi sbagliavo! Certo, i test dell'interfaccia utente possono essere difficili. Ci vuole un bel po 'di tempo per scrivere correttamente i test dell'interfaccia utente. Sono molto più lenti e più fragili dei test unitari perché attraversano i confini di classe e processo, colpiscono il browser, coinvolgono elementi dell'interfaccia utente (ad esempio HTML, JavaScript) che cambiano continuamente, colpiscono il database, il file system e potenzialmente i servizi di rete. Se una qualsiasi di queste parti mobili non funziona bene, hai un test rotto; ma questo è anche il bello dei test dell'interfaccia utente: testano il tuo sistema end-to-end. Nessun altro test offre una copertura tanto ampia quanto completa. I test dell'interfaccia utente automatizzati, se eseguiti correttamente, potrebbero essere i migliori elementi della suite di regressione.

Quindi nei miei ultimi progetti i miei test dell'interfaccia utente hanno formato oltre l'80% dei miei test! Vorrei anche ricordare che questi progetti sono stati per lo più applicazioni CRUD con poca logica di business e ammettiamolo - la stragrande maggioranza dei progetti software rientra in questa categoria. La logica di business dovrebbe ancora essere testata da unità; ma il resto dell'applicazione può essere accuratamente testato tramite l'automazione dell'interfaccia utente.


Test dell'interfaccia utente andato male

Vorrei soffermarmi su cosa ho sbagliato, che sembra essere molto tipico tra sviluppatori e tester che iniziano con l'automazione dell'interfaccia utente.

Quindi cosa va storto e perché? Molti team avviano l'automazione dell'interfaccia utente con i registratori dello schermo. Se stai facendo automazione web con Selenium hai probabilmente usato l'IDE Selenium. Dalla home page IDE del selenio:

Il Selenium-IDE (Integrated Development Environment) è lo strumento che utilizzi per sviluppare i tuoi casi di test del selenio.

Questo è in realtà uno dei motivi per cui la verifica dell'interfaccia utente si trasforma in un'esperienza orribile: scarichi e accendi uno screen recorder e vai al tuo sito web e fai clic, clic, digita, fai clic, digita, etichetta, tipo, scheda, tipo, clic e affermare. Quindi riproduci la registrazione e funziona. Dolce!! Quindi esporti le azioni come script di test, inseriscile nel tuo codice, avvolgilo in un test ed esegui il test e vedi il browser animarsi davanti ai tuoi occhi ei tuoi test funzionano molto bene. Sei molto emozionato, condividi le tue scoperte con i tuoi colleghi e mostrali al tuo capo e loro si emozionano molto e vanno: "Automatizza TUTTE LE COSE"

Una settimana dopo hai 10 test dell'interfaccia utente automatizzati e tutto sembra perfetto. Quindi l'azienda ti chiede di sostituire il nome utente con l'indirizzo e-mail in quanto ha causato una certa confusione tra gli utenti, e così fai. Quindi, come qualsiasi altro grande programmatore, esegui la tua suite di test dell'interfaccia utente, solo per scoprire che il 90% dei test sono interrotti perché per ogni test stai registrando l'utente con nome utente e il nome del campo è cambiato e sono necessarie due ore per sostituire tutto i riferimenti a nome utente nei tuoi test con e-mail e per rendere nuovamente i test verdi. La stessa cosa accade più e più volte e ad un certo punto ti ritrovi a passare ore al giorno a correggere i test non funzionanti: test che non si sono interrotti perché qualcosa è andato storto con il tuo codice; ma poiché hai cambiato il nome di un campo nel tuo database / modello o hai ristretto leggermente la tua pagina. Qualche settimana dopo smetti di eseguire i test a causa di questo enorme costo di manutenzione e concludi che il test dell'interfaccia utente fa schifo.

NON utilizzare Selenium IDE o qualsiasi altro registratore per lo schermo per sviluppare i casi di test. Detto questo, non è lo stesso registratore a schermo che porta a una suite di test fragili; è il codice che genera che ha problemi di manutenibilità intrinseca. Molti sviluppatori hanno ancora una fragile suite di test dell'interfaccia utente, anche senza utilizzare i registratori dello schermo solo perché i loro test mostrano gli stessi attributi.

Tutti i test in questo articolo sono scritti contro il sito Web di Mvc Music Store. Il sito Web presenta alcuni problemi che rendono difficile l'esecuzione dell'interfaccia utente, pertanto ho eseguito il porting del codice e risolto i problemi. È possibile trovare il codice effettivo su cui sto scrivendo questi test sul repository GitHub per questo articolo qui

Che aspetto ha un test fragile? Sembra qualcosa del genere:

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")) Fare clic su (); . Driver.FindElement (By.LinkText ( "Registro")) Fare clic su (); 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 \ "]")) Fare clic su ().; . Driver.FindElement (By.LinkText ( "Disco")) Fare clic su (); driver.FindElement (By.CssSelector ("img [alt = \" Le Freak \ "]")). Click (); driver.FindElement (By.LinkText ("Aggiungi al carrello")). Fare clic su (); driver.FindElement (By.LinkText ("Checkout >>")). Click (); driver.FindElement (By.Id ( "FirstName")) Clear ().; driver.FindElement (By.Id ( "FirstName")) SendKeys ( "Homer."); driver.FindElement (By.Id ( "Cognome")) Clear ().; driver.FindElement (By.Id ( "Cognome")) SendKeys ( "Simpson."); driver.FindElement (By.Id ( "Indirizzo")) Clear ().; driver.FindElement (By.Id ("Indirizzo")). SendKeys ("742 Evergreen Terrace"); driver.FindElement (By.Id ( "City")) Clear ().; driver.FindElement (By.Id ( "City")) SendKeys ( "Springfield."); driver.FindElement (By.Id ( "Stato")) Clear ().; driver.FindElement (By.Id ( "Stato")) SendKeys ( "Kentucky."); driver.FindElement (By.Id ( "PostalCode")) Clear ().; driver.FindElement (By.Id ( "PostalCode")) SendKeys ( "123456").; driver.FindElement (By.Id ( "Country")) Clear ().; driver.FindElement (By.Id ("Paese")). SendKeys ("Stati Uniti"); driver.FindElement (By.Id ( "telefono")) Clear ().; driver.FindElement (By.Id ( "telefono")) SendKeys ( "2.341.231,241 mila."); 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 ( "libero").; driver.FindElement (By.CssSelector ( "input [type = \" submit \ "]")) Fare clic su ().; Assert.IsTrue (driver.PageSource.Contains ("Checkout completato")); 

Puoi trovare il BrittleTest classe qui.

L'host è una classe statica, con una singola proprietà statica: Esempio, che dopo l'istanziazione attiva IIS Express sul sito Web sottoposto a test e associa Firefox WebDriver all'istanza del browser. Al termine del test, chiude automaticamente il browser e IIS Express.

Questo test attiva un browser web, passa alla home page del sito Web di Mvc Music Store, registra un nuovo utente, sfoglia un album, lo aggiunge al carrello e controlla.

Si potrebbe obiettare che questo test sta facendo troppo ed è per questo che è fragile; ma la dimensione di questo test non è la ragione per cui è fragile - è come è scritto che lo rende un incubo da mantenere.

Ci sono diverse scuole di pensiero sui test dell'interfaccia utente e su quanto ogni test dovrebbe coprire. Alcuni credono che questo test stia facendo troppo e alcuni pensano che un test dovrebbe coprire uno scenario reale, end to end, e considerarlo un test perfetto (manutenibilità a parte).

Quindi cosa c'è di sbagliato in questo test?

  • Questo è un codice procedurale. Uno dei problemi principali di questo stile di codifica è la leggibilità o la mancanza di esso. Se vuoi cambiare il test, o se si rompe perché una delle pagine coinvolte è cambiata, avrai difficoltà a capire cosa cambiare e tracciare una linea tra le sezioni della funzionalità; perché è tutto un mucchio di codice in cui otteniamo il 'driver' per trovare un elemento nella pagina e fare qualcosa con esso. Nessuna modularità.
  • Questo test di per sé potrebbe non avere molta duplicazione, ma alcuni altri test come questo e avrai un sacco di selettore e logica duplicati per interagire con le pagine web di diversi test. Per esempio By.Id ( "username") il selettore verrà duplicato in tutti i test che richiedono la registrazione e driver.FindElement (By.Id ( "username")). Clear () e driver.FindElement (By.Id ( "username")). SendKeys ("") sono duplicati ovunque tu voglia interagire con la casella di testo UserName. Poi c'è l'intero modulo di registrazione, e il modulo di checkout ecc. Che sarà ripetuto in tutte le prove che necessitano di interagire con loro! Il codice duplicato porta a incubi di manutenibilità.
  • C'è un sacco di stringhe magiche ovunque, che ancora una volta è un problema di manutenibilità.

Il codice di prova è codice!

Esistono anche schemi che consentono di scrivere più test dell'interfaccia utente gestibili.

Proprio come il tuo codice attuale, dovrai mantenere i tuoi test. Quindi dai loro lo stesso trattamento.

Che cosa si tratta di test che ci fanno pensare che possiamo rinunciare a qualità in loro? Se non altro, una cattiva suite di test, a mio parere, è molto più difficile da gestire di un codice errato. Ho avuto pezzi cattivi di codice funzionante in produzione per anni che non si sono mai rotti e non ho mai dovuto toccarli. Certo era brutto e difficile da leggere e mantenere, ma ha funzionato e non ha bisogno di cambiare, quindi il costo di manutenzione reale era pari a zero. La situazione però non è la stessa per i test negativi: perché i test errati si interrompono e il loro fissaggio sarà difficile. Non riesco a contare il numero di volte in cui ho visto gli sviluppatori evitare di testare perché pensano che scrivere test sia un'enorme perdita di tempo perché ci vuole troppo tempo per mantenere.

Il codice di prova è codice: applichi SRP sul tuo codice? Quindi dovresti applicarlo anche ai tuoi test. Il tuo codice è ASCIUTTO? Quindi asciuga anche i tuoi test. Se non scrivi buoni test (UI o altro) perderai un sacco di tempo per mantenerli.

Esistono anche schemi che consentono di scrivere più test dell'interfaccia utente gestibili. Questi modelli sono indipendenti dalla piattaforma: ho utilizzato queste stesse idee e modelli per scrivere i test dell'interfaccia utente per le applicazioni WPF e le applicazioni Web scritte in ASP.Net e Ruby on Rails. Quindi, indipendentemente dal tuo stack tecnologico, dovresti essere in grado di rendere i tuoi test dell'interfaccia utente molto più gestibili seguendo alcuni semplici passaggi.

Presentazione del modello oggetto pagina

Molti dei problemi sopra menzionati sono radicati nella natura procedurale dello script di test e la soluzione è semplice: Orientamento agli oggetti.

L'oggetto pagina è un modello utilizzato per applicare l'orientamento dell'oggetto ai test dell'interfaccia utente. Dal wiki del selenio:

Nell'interfaccia utente della tua app Web ci sono aree con cui i test interagiscono. Un oggetto Page semplicemente modella questi come oggetti all'interno del codice di test. Ciò riduce la quantità di codice duplicato e significa che se l'interfaccia utente cambia, la correzione deve essere applicata solo in un unico punto.

L'idea è che per ogni pagina della tua applicazione / sito web desideri creare un oggetto Page. Gli oggetti Page sono fondamentalmente l'equivalente di automazione dell'interfaccia utente delle tue pagine web.

Sono andato avanti e ho rifattorizzato la logica e le interazioni dal BrittleTest in alcuni oggetti di pagina e ho creato un nuovo test che li utilizza invece di colpire direttamente il driver web. Puoi trovare il nuovo test qui. Il codice è copiato qui per il vostro riferimento:

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 = "Stati Uniti"; shippingPage.Phone = "2341231241"; shippingPage.Email = "[email protected]"; shippingPage.PromoCode = "FREE"; var orderPage = shippingPage.SubmitOrder (); Assert.AreEqual (orderPage.Title, "Checkout Complete"); 

Certo, il corpo del test non è diminuito molto di dimensioni e in effetti ho dovuto creare sette nuove classi per supportare questo test. Nonostante ci siano più linee di codice richieste, abbiamo corretto un sacco di problemi che il test originale aveva avuto (più su questo più in basso). Per ora, analizziamo un po 'più a fondo il pattern dell'oggetto pagina e quello che abbiamo fatto qui.

Con il pattern Oggetto pagina si crea in genere una classe oggetto di pagina per pagina Web sottoposta a test in cui la classe modella e incapsula le interazioni con la pagina. Quindi una casella di testo nella tua pagina web diventa una proprietà stringa sull'oggetto Page e per riempire quella casella di testo hai appena impostato quella proprietà di testo sul valore desiderato, invece di:

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

possiamo scrivere:

registerPage.Email = "[email protected]";

dove registerPage è un'istanza della classe RegisterPage. Una casella di controllo nella pagina diventa una proprietà bool dell'oggetto Pagina e spuntando e deselezionando la casella di controllo si tratta solo di impostare la proprietà booleana su true o false. Allo stesso modo, un collegamento sulla pagina Web diventa un metodo sull'oggetto Page e facendo clic sul collegamento si trasforma in chiamare il metodo sull'oggetto Page. Quindi, invece di:

. Driver.FindElement (By.LinkText ( "Admin")) Fare clic su ();

possiamo scrivere:

homepage.GoToAdminForAnonymousUser ();

Infatti, qualsiasi azione sulla nostra pagina web diventa un metodo nel nostro oggetto di pagina e in risposta a tale azione (cioè chiamando il metodo sull'oggetto di pagina) si ottiene un'istanza di un altro oggetto di pagina che punti sulla pagina Web che hai appena navigato fino a prendere l'azione (ad esempio, inviare un modulo o fare clic su un collegamento). In questo modo puoi facilmente concatenare le interazioni di visualizzazione nello script di test:

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

Qui, dopo aver registrato l'utente, vengo indirizzato alla home page (un'istanza del suo oggetto page viene restituita da SubmitRegistration metodo). Quindi sull'istanza HomePage che chiamo SelectGenreByName che fa clic su un link "Disco" sulla pagina che restituisce un'istanza di AlbumBrowsePage e quindi su quella pagina che chiamo SelectAlbumByName che fa clic sull'album "Le Freak" e restituisce un'istanza di AlbumDetailsPage e così via e così via.

Lo ammetto: sono un sacco di classi per quello che prima non era affatto una classe; ma abbiamo guadagnato molti benefici da questa pratica. Innanzitutto il codice non è più procedurale. Abbiamo un modello di test ben contenuto in cui ogni oggetto fornisce un piacevole incapsulamento dell'interazione con una pagina. Ad esempio, se qualcosa cambia nella tua logica di registrazione, l'unico posto che devi modificare è la tua classe RegisterPage invece di dover passare attraverso l'intera suite di test e modificare ogni singola interazione con la vista di registrazione. Questa modularità offre anche una buona riusabilità: puoi riutilizzare il tuo ShoppingCartPage ovunque tu abbia bisogno di interagire con il carrello della spesa. Quindi, in una semplice pratica di passaggio dal codice di prova procedurale a quello orientato agli oggetti, abbiamo quasi eliminato tre dei quattro problemi con il test fragile iniziale che erano codice procedurale e logica e duplicazione selettore. Abbiamo ancora un po 'di duplicazione, tuttavia, che verranno risolti a breve.

Come abbiamo effettivamente implementato quegli oggetti di pagina? Un oggetto di pagina nella sua radice non è altro che un involucro intorno alle interazioni che hai con la pagina. Qui ho appena estratto le interazioni dell'interfaccia utente dei nostri test fragili e li ho inseriti negli oggetti della pagina. Ad esempio, la logica di registrazione è stata estratta nella sua classe chiamata RegisterPage che assomigliava a questo:

public class RegisterPage: Pagina public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ( "input [type = 'submit']"));  public string Username set Execute (By.Name ("UserName"), e => e.Clear (); e.SendKeys (valore););  public string Email set Execute (By.Name ("Email"), e => e.Clear (); e.SendKeys (valore););  public string ConfirmPassword set Execute (By.Name ("ConfirmPassword"), e => e.Clear (); e.SendKeys (valore););  public string Password set Execute (By.Name ("Password"), e => e.Clear (); e.SendKeys (valore);); 

Ho creato un Pagina superclasse che si prende cura di alcune cose, come Navigare verso che aiuta a navigare in una nuova pagina prendendo un'azione e Eseguire che esegue alcune azioni su un elemento. Il Pagina classe sembrava:

public class Page protected RemoteWebDriver WebDriver get return Host.Instance.WebDriver;  public stringa Title get return WebDriver.Title;  pubblico TPage NavigateTo(Di by) dove TPage: Page, new () WebDriver.FindElement (by) .Click (); return Activator.CreateInstance();  public void Execute (By by, Action action) var element = WebDriver.FindElement (by); azione (elemento); 

Nel BrittleTest, per interagire con un elemento che abbiamo fatto FindElement una volta per azione. Il Eseguire Il metodo, oltre a sottrarre l'interazione del web driver, ha un ulteriore vantaggio che consente di selezionare un elemento, che potrebbe essere un'azione costosa, una volta e di intraprendere più azioni su di esso:

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

è stato sostituito con:

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

Dando una seconda occhiata al RegisterPage oggetto della pagina sopra abbiamo ancora un po 'di duplicazione. Il codice di prova è codice e non vogliamo la duplicazione nel nostro codice; quindi facciamo un refactoring Possiamo estrarre il codice richiesto per riempire una casella di testo in un metodo su Pagina classe e basta chiamarlo dagli oggetti della pagina. Il metodo potrebbe essere implementato come:

pubblico vuoto SetText (stringa elementName, stringa newText) Execute (By.Name (elementName), e => e.Clear (); e.SendKeys (newText);); 

E ora le proprietà su RegisterPage può essere ridotto a:

public string Username set SetText ("UserName", valore); 

Potresti anche creare un'API fluente per fare in modo che il setter legga meglio (ad es. Fill ( "username"). Con (valore)) ma lo lascerò a te.

Non stiamo facendo nulla di straordinario qui. Semplice refactoring sul nostro codice di test come abbiamo sempre fatto per il nostro, errrr, "altro" codice!!

Puoi vedere il codice completo per Pagina e RegisterPage classi qui e qui.

Oggetto pagina fortemente tipizzato

Abbiamo risolto i problemi procedurali con il test fragile che ha reso il test più leggibile, modulare, DRYer ed efficacemente mantenibile. C'è un ultimo problema che non abbiamo risolto: ci sono ancora molte stringhe magiche ovunque. Non è un incubo ma è ancora un problema che potremmo risolvere. Inserisci oggetti pagina fortemente tipizzati!

Questo approccio è pratico se si utilizza un framework MV * per l'interfaccia utente. Nel nostro caso stiamo usando ASP.Net MVC.

Diamo un'altra occhiata al RegisterPage:

public class RegisterPage: Pagina public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ( "input [type = 'submit']"));  public string Username set SetText ("UserName", valore);  public string Email set SetText ("Email", valore);  public string ConfirmPassword set SetText ("ConfirmPassword", valore);  public string Password set SetText ("Password", valore); 

Questa pagina modella la vista Registra nella nostra app Web (copiando solo la parte superiore qui per comodità):

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

Hmmm, che cos'è RegisterModel Là? È il modello di visualizzazione per la pagina: il M nel MVC. Ecco il codice (ho rimosso gli attributi per ridurre il rumore):

public class RegisterModel public string UserName get; impostato;  public string Email get; impostato;  public string Password get; impostato;  stringa pubblica ConfirmPassword get; impostato; 

Sembra molto familiare, vero? Ha le stesse proprietà del RegisterPage classe che non è sorprendente considerando RegisterPage è stato creato in base a questa vista e al modello di visualizzazione. Vediamo se possiamo sfruttare i modelli di vista per semplificare i nostri oggetti di pagina.

Ho creato un nuovo Pagina superclasse; ma uno generico. Puoi vedere il codice qui:

Pagina di classe pubblica : Pagina in cui TViewModel: class, new () public void FillWith (TViewModel viewModel, IDictionary> propertyTypeHandling = null) // rimosso per brevità

Il Pagina classe sottoclasse il vecchio Pagina classe e fornisce tutte le sue funzionalità; ma ha anche un metodo in più chiamato Riempire con che riempie la pagina con l'istanza del modello di vista fornita! Quindi ora il mio RegisterPage la classe sembra:

public class RegisterPage: Pagina public HomePage CreateValidUser (modello RegisterModel) FillWith (modello); torna a Navigate(By.CssSelector ( "input [type = 'submit']")); 

Ho duplicato tutti gli oggetti della pagina per mostrare entrambe le varianti e anche per rendere il codice base più facile da seguire per te; ma in realtà avrai bisogno di una classe per ogni oggetto di pagina.

Dopo aver convertito gli oggetti della mia pagina in quelli generici ora il test ha il seguente aspetto:

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

Questo è tutto - l'intero test! Molto più leggibile, ASCIUTTO e manutenibile, no??

Il ObjectMother la classe che sto usando nel test è una Object Mother che fornisce dati di test (il codice può essere trovato qui), niente di strano:

public class ObjectMother public static Ordine CreateShippingInfo () var shippingInfo = new Ordine FirstName = "Homer", LastName = "Simpson", Indirizzo = "742 Evergreen Terrace", Città = "Springfield", Stato = "Kentucky", PostalCode = "123456", Paese = "Stati Uniti", Telefono = "2341231241", Email = "[email protected]"; ritorno shippingInfo;  public static RegisterModel CreateRegisterModel () var model = new RegisterModel UserName = "HJSimpson", Email = "[email protected]", Password = "! 2345Qwert", ConfirmPassword = "! 2345Qwert"; modello di ritorno; 

Non fermarsi all'oggetto Page

Alcune pagine web sono molto grandi e complesse. Prima ho detto che il codice di prova è il codice e dovremmo trattarlo come tale. Normalmente rompiamo pagine web grandi e complesse in componenti più piccoli e, in alcuni casi, riutilizzabili (parziali). Questo ci permette di comporre una pagina web da componenti più piccoli e più maneggevoli. Dovremmo fare lo stesso per i nostri test. Per fare ciò possiamo usare i componenti della pagina.

Un componente Page è molto simile a un oggetto Page: è una classe che incapsula l'interazione con alcuni elementi di una pagina. La differenza è che interagisce con una piccola parte di una pagina Web: modella un controllo utente o una vista parziale, se lo desideri. Un buon esempio per un componente della pagina è una barra dei menu. Una barra dei menu viene generalmente visualizzata su tutte le pagine di un'applicazione Web. Non si vuole davvero continuare a ripetere il codice necessario per interagire con il menu in ogni singolo oggetto di pagina. Invece puoi creare un componente di una pagina di menu e usarlo dai tuoi oggetti di pagina. Puoi anche utilizzare i componenti della pagina per gestire le griglie di dati sulle tue pagine e, per fare un ulteriore passo avanti, il componente della griglia potrebbe essere composto da componenti della griglia della riga delle pagine. Nel caso di Mvc Music Store potremmo avere un TopMenuComponent e a SideMenuComponent e usali dal nostro HomePage.

Come nella tua applicazione web, potresti anche creare un, per esempio, LayoutPage oggetto della pagina che modella il layout / la pagina principale e la usa come superclasse per tutti gli altri oggetti della pagina. La pagina di layout sarà quindi composta da componenti della pagina di menu in modo che tutte le pagine possano accedere ai menu. Immagino che una buona regola sarebbe quella di avere un componente di pagina per vista parziale, un oggetto di pagina di layout per layout e un oggetto di pagina per pagina web. In questo modo sai che il tuo codice di prova è come granualar e ben composto come il tuo codice.

Alcuni framework per i test dell'interfaccia utente

Quello che ho mostrato sopra era un esempio molto semplice e forzato con alcune classi di supporto come infrastruttura per i test. In realtà i requisiti per il test dell'interfaccia utente sono molto più complessi di questo: ci sono controlli e interazioni complessi, devi scrivere e leggere dalle tue pagine, devi gestire le latenze di rete e avere il controllo su AJAX e altre interazioni Javascript, bisogno di accendere diversi browser e così su cui non ho spiegato in questo articolo. Sebbene sia possibile codificare tutti questi elementi, l'utilizzo di alcuni framework potrebbe farti risparmiare molto tempo. Ecco i framework che consiglio vivamente:

Framework per .Net:

  • Seleno è un progetto open source di TestStack che ti aiuta a scrivere test dell'interfaccia utente automatizzati con Selenium. Si concentra sull'uso di oggetti Page e componenti della pagina e leggendo e scrivendo su pagine Web utilizzando modelli di visualizzazione fortemente tipizzati. Se ti è piaciuto quello che ho fatto in questo articolo, ti piacerà anche Seleno dato che la maggior parte del codice mostrato qui è stato preso in prestito dalla base di codice di Seleno.
  • White è un framework open source di TestStack per l'automazione di applicazioni rich client basate su piattaforme Win32, WinForms, WPF, Silverlight e SWT (Java).

Divulgazione: sono un co-fondatore e un membro del team di sviluppo dell'organizzazione TestStack.

Quadri per Ruby:

  • Capybara è un framework di test di accettazione per applicazioni Web che ti aiuta a testare le applicazioni web simulando il modo in cui un utente reale interagirebbe con la tua app.
  • Poltergeist è un driver per Capybara. Ti permette di eseguire i tuoi test Capybara su un browser WebKit senza testa, fornito da PhantomJS.
  • page-object (Non ho usato personalmente questa gemma) è una gemma semplice che aiuta a creare oggetti di pagina flessibili per testare applicazioni basate su browser. L'obiettivo è facilitare la creazione di livelli di astrazione nei test per disaccoppiare i test dall'elemento che stanno testando e fornire un'interfaccia semplice agli elementi di una pagina. Funziona con watir-webdriver e selenio-webdriver.

Conclusione

Abbiamo iniziato con una tipica esperienza di automazione dell'interfaccia utente, spiegando perché i test dell'interfaccia utente falliscono, abbiamo fornito un esempio di test fragile e ne abbiamo discusso i problemi e li abbiamo risolti utilizzando alcune idee e modelli.

Se vuoi prendere un punto da questo articolo dovrebbe essere: Test Code Is Code. Se ci pensate, tutto ciò che ho fatto in questo articolo è stato applicare le buone pratiche di codifica e orientate agli oggetti che già conoscete per un test dell'interfaccia utente.

C'è ancora molto da imparare sui test dell'interfaccia utente e cercherò di discutere alcuni dei suggerimenti più avanzati in un prossimo articolo.

Buona prova!