Wartungsfähige automatisierte UI-Tests

Vor einigen Jahren war ich bei automatisierten UI-Tests sehr skeptisch, und diese Skepsis ist auf wenige Fehlversuche zurückzuführen. Ich schrieb einige automatisierte UI-Tests für Desktop- oder Webanwendungen, und ein paar Wochen später würde ich sie aus der Codebasis reißen, weil die Kosten für die Wartung zu hoch waren. Ich dachte also, dass das Testen der Benutzeroberfläche hart war und dass es zwar einen großen Nutzen brachte, es jedoch am besten war, es auf ein Minimum zu beschränken und nur die komplexesten Workflows in einem System durch das Testen der Benutzeroberfläche zu testen und den Rest den Komponententests zu überlassen. Ich erinnere mich, dass ich meinem Team von Mike Cohns Testpyramide erzählte und dass in einem typischen System über 70% der Tests Unit-Tests, ca. 5% UI-Tests und die restlichen Integrationstests sein sollten.

Ich dachte also, dass das Testen der Benutzeroberfläche hart war und dass es zwar einen großen Nutzen brachte, aber es am besten war, es auf ein Minimum zu reduzieren…

Ich hab mich geirrt! Sicher, UI-Tests können schwierig sein. Es dauert ziemlich lange, um UI-Tests ordnungsgemäß zu schreiben. Sie sind viel langsamer und spröder als Komponententests, weil sie Klassen- und Prozessgrenzen überschreiten, den Browser treffen, UI-Elemente (z. B. HTML, JavaScript) enthalten, die sich ständig ändern, sie treffen auf die Datenbank, das Dateisystem und möglicherweise auf Netzwerkdienste. Wenn einer dieser beweglichen Teile nicht gut läuft, haben Sie einen fehlerhaften Test. Das ist aber auch das Schöne an UI-Tests: Sie testen Ihr System durchgehend. Kein anderer Test liefert so viel oder so umfassende Abdeckung. Automatisierte UI-Tests könnten, wenn sie richtig durchgeführt werden, die besten Elemente Ihrer Regressionssuite sein.

In den letzten Projekten haben meine UI-Tests über 80% meiner Tests gebildet! Ich sollte auch erwähnen, dass es sich bei diesen Projekten hauptsächlich um CRUD-Anwendungen mit wenig Geschäftslogik handelt, und seien wir ehrlich: Die große Mehrheit der Softwareprojekte fällt in diese Kategorie. Die Geschäftslogik sollte noch Unit-getestet sein. Der Rest der Anwendung kann jedoch durch die UI-Automatisierung gründlich getestet werden.


UI-Tests sind schiefgegangen

Ich möchte auf das eingehen, was ich falsch gemacht habe, was auch unter Entwicklern und Testern, die mit der UI-Automatisierung beginnen, sehr typisch ist.

Was geht also schief und warum? Viele Teams starten die UI-Automatisierung mit Bildschirmschreibern. Wenn Sie Web-Automatisierung mit Selenium durchführen, haben Sie höchstwahrscheinlich Selenium IDE verwendet. Von der Selenium IDE-Startseite:

Die Selenium-IDE (Integrated Development Environment) ist das Werkzeug, mit dem Sie Ihre Selenium-Testfälle entwickeln.

Dies ist tatsächlich einer der Gründe, warum das Testen von Benutzeroberflächen zu einem schrecklichen Erlebnis wird: Sie laden einen Bildschirmrekorder herunter und starten ihn, navigieren zu Ihrer Website und klicken auf, geben, klicken, geben, klicken, geben, geben, klicken, tippen, tippen und klicken behaupten. Dann spielen Sie die Aufnahme ab und es funktioniert. Süss!! Sie exportieren also die Aktionen als Testskript, fügen es in Ihren Code ein, wickeln es in einen Test ein, führen den Test aus und sehen, wie der Browser vor Ihren Augen lebendig wird und Ihre Tests sehr reibungslos ablaufen. Sie werden sehr aufgeregt, teilen Ihre Ergebnisse mit Ihren Kollegen und zeigen es Ihrem Chef, und sie werden sehr aufgeregt und gehen: "Automatisieren Sie ALLE DINGE"

Eine Woche später haben Sie 10 automatisierte UI-Tests und alles scheint großartig zu sein. Dann fordert das Unternehmen Sie auf, den Benutzernamen durch die E-Mail-Adresse zu ersetzen, da dies zu Verwirrung bei den Benutzern geführt hat. Wie bei jedem anderen großartigen Programmierer, den Sie mit Ihrer UI-Testsuite ausführen, sind nur 90% Ihrer Tests fehlerhaft, da sich der Benutzer bei jedem Test mit Benutzername und Benutzername einloggt und sich der Feldname geändert hat. Es dauert zwei Stunden, um alle zu ersetzen die Verweise auf Nutzername in deinen Tests mit Email und um die Tests wieder grün zu machen. Dasselbe passiert immer wieder und irgendwann verbringt man Stunden am Tag damit, gebrochene Tests zu reparieren: Tests, die nicht fehlgeschlagen sind, weil mit Ihrem Code etwas schiefgegangen ist; Sie haben jedoch einen Feldnamen in Ihrer Datenbank / Ihrem Modell geändert oder Ihre Seite geringfügig umstrukturiert. Ein paar Wochen später beenden Sie die Ausführung Ihrer Tests aufgrund dieser hohen Wartungskosten, und Sie schließen daraus, dass das Testen der Benutzeroberfläche scheiße ist.

Sie sollten NICHT Selenium IDE oder einen anderen Bildschirm-Recorder verwenden, um Ihre Testfälle zu entwickeln. Das heißt, nicht der Bildschirmrecorder selbst führt zu einer spröden Testreihe. Der von ihnen generierte Code hat inhärente Wartbarkeitsprobleme. Viele Entwickler haben immer noch eine spröde UI-Testsuite, auch wenn Sie keine Bildschirmschreiber verwenden, nur weil ihre Tests dieselben Eigenschaften aufweisen.

Alle Tests in diesem Artikel sind auf der Website des Mvc Music Store geschrieben. Die Website hat einige Probleme, die das Testen der Benutzeroberfläche ziemlich schwierig machen. Deshalb habe ich den Code portiert und die Probleme behoben. Den eigentlichen Code, gegen den ich diese Tests schreibe, finden Sie auf dem GitHub-Repo für diesen Artikel

Wie sieht also ein spröder Test aus? Es sieht ungefähr so ​​aus:

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")). Klicken Sie auf (); driver.FindElement (By.LinkText ("Register")). Klicken Sie auf (); 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" "]")). Klicken Sie auf (); driver.FindElement (By.LinkText ("Disco")). Klicken Sie auf (); driver.FindElement (By.CssSelector ("img [alt = \" Le Freak "]")). Klicken Sie auf (); driver.FindElement (By.LinkText ("Add to cart")). Klicken Sie auf (); driver.FindElement (By.LinkText ("Checkout >>")). Klicken Sie auf (); driver.FindElement (By.Id ("FirstName")). Clear (); driver.FindElement (By.Id ("FirstName")). SendKeys ("Homer"); driver.FindElement (By.Id ("LastName")). 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 ("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 ("Country")). SendKeys ("United States"); driver.FindElement (By.Id ("Phone")). Clear (); driver.FindElement (By.Id ("Phone")). SendKeys ("2341231241"); driver.FindElement (By.Id ("Email")). Clear (); driver.FindElement (By.Id ("Email")). SendKeys ("[email protected]"); driver.FindElement (By.Id ("PromoCode")). Clear (); driver.FindElement (By.Id ("PromoCode")). SendKeys ("FREE"); driver.FindElement (By.CssSelector ("input [type =" "submit" "]")). Klicken Sie auf (); Assert.IsTrue (driver.PageSource.Contains ("Checkout abgeschlossen")); 

Sie können das finden BrittleTest Klasse hier.

Host ist eine statische Klasse mit einer einzigen statischen Eigenschaft: Beispiel, Bei der Instantiierung wird IIS Express auf der getesteten Website gestartet und Firefox WebDriver an die Browser-Instanz gebunden. Wenn der Test abgeschlossen ist, werden der Browser und IIS Express automatisch geschlossen.

Bei diesem Test wird ein Webbrowser gestartet, die Startseite der Mvc Music Store-Website aufgerufen, ein neuer Benutzer registriert, ein Album durchsucht, das Album in den Warenkorb gelegt und ausgecheckt.

Man könnte argumentieren, dass dieser Test zu viel ist und deshalb brüchig ist. Aber die Größe dieses Tests ist nicht der Grund dafür, dass er brüchig ist. Die Art und Weise, wie er geschrieben wird, macht es zu einem Alptraum, der gepflegt werden muss.

Es gibt verschiedene Denkrichtungen zum Testen von UI und wie viel jeder Test abdecken sollte. Einige glauben, dass dieser Test zu viel tut, und einige glauben, ein Test sollte ein reales Szenario abdecken, und das Ganze ist ein perfekter Test (Wartbarkeit beiseite)..

Was ist also los mit diesem Test??

  • Dies ist ein Verfahrenscode. Eines der Hauptprobleme dieses Codierstils ist die Lesbarkeit oder deren Mangel. Wenn Sie den Test ändern möchten oder der Test abgebrochen wird, weil sich eine der beteiligten Seiten geändert hat, können Sie nur schwer herausfinden, was geändert werden soll, und eine Linie zwischen den Funktionsabschnitten ziehen. denn es ist alles ein großer Haufen Code, wo wir den "Treiber" dazu bringen, ein Element auf der Seite zu finden und etwas damit zu tun. Keine Modularität.
  • Dieser eine Test an sich hat möglicherweise nicht viel Duplikation, aber ein paar weitere Tests wie dieser, und Sie haben eine Menge duplizierter Selektoren und Logik, um mit Webseiten aus verschiedenen Tests zu interagieren. Zum Beispiel Von.Id ("Benutzername") Der Selektor wird in allen Tests, für die eine Registrierung erforderlich ist, dupliziert driver.FindElement (By.Id ("UserName")). Clear () und driver.FindElement (By.Id ("UserName")). SendKeys ("") werden überall kopiert, wo Sie mit dem Textfeld UserName interagieren möchten. Dann gibt es das gesamte Anmeldeformular und das Checkout-Formular usw., das in allen Tests wiederholt wird, die mit ihnen interagieren müssen! Doppelter Code führt zu Albträumen der Wartbarkeit.
  • Es gibt viele magische Zeichenketten überall, was wiederum ein Problem mit der Wartbarkeit ist.

Testcode ist Code!

Es gibt auch Muster, mit denen Sie wartungsfähigere UI-Tests schreiben können.

Ähnlich wie bei Ihrem eigentlichen Code müssen Sie Ihre Tests beibehalten. Also gib ihnen die gleiche Behandlung.

Was macht uns bei Tests der Eindruck, dass wir bei ihnen auf Qualität verzichten können? Wenn überhaupt, ist eine schlechte Testsuite meiner Meinung nach viel schwieriger zu warten als falscher Code. Ich habe jahrelang schlechte Codes in der Produktion gehabt, die nie gebrochen haben, und ich musste sie nie anfassen. Sicher, es war hässlich und schwer zu lesen und zu warten, aber es funktionierte und musste nicht geändert werden, so dass die tatsächlichen Wartungskosten gleich Null waren. Bei schlechten Tests ist die Situation jedoch nicht ganz die gleiche: Weil schlechte Tests brechen werden und sie zu beheben, wird schwierig. Ich kann nicht zählen, wie oft Entwickler das Testen vermeiden, weil sie der Meinung sind, dass das Schreiben von Tests eine enorme Zeitverschwendung ist, da die Wartung zu viel Zeit in Anspruch nimmt.

Testcode ist Code: Wenden Sie SRP auf Ihren Code an? Dann sollten Sie es auch in Ihren Tests anwenden. Ist Ihr Code TROCKEN? Dann TROCKNEN Sie Ihre Tests auch auf. Wenn Sie keine guten Tests schreiben (Benutzeroberfläche oder andere), werden Sie viel Zeit damit verbringen, sie zu warten.

Es gibt auch Muster, mit denen Sie wartungsfähigere UI-Tests schreiben können. Diese Muster sind plattformunabhängig: Ich habe dieselben Ideen und Muster verwendet, um UI-Tests für WPF-Anwendungen und Webanwendungen zu schreiben, die in ASP.Net und Ruby on Rails geschrieben sind. Unabhängig von Ihrem Technologie-Stack sollten Sie daher in der Lage sein, Ihre UI-Tests durch einige einfache Schritte wartungsfreundlicher zu gestalten.

Einführung in das Seitenobjektmuster

Viele der oben genannten Probleme wurzeln in der prozeduralen Natur des Testskripts und die Lösung ist einfach: Objektorientierung.

Das Seitenobjekt ist ein Muster, mit dem die Objektorientierung auf UI-Tests angewendet wird. Aus dem Selenium-Wiki:

In der Benutzeroberfläche Ihrer Web-App gibt es Bereiche, mit denen Ihre Tests interagieren. Ein Seitenobjekt modelliert diese einfach als Objekte innerhalb des Testcodes. Dadurch wird die Menge an dupliziertem Code reduziert. Wenn sich die Benutzeroberfläche ändert, muss der Fix nur an einer Stelle angewendet werden.

Die Idee ist, dass Sie für jede Seite in Ihrer Anwendung / Website ein Seitenobjekt erstellen möchten. Seitenobjekte sind im Wesentlichen das UI-Automatisierungsäquivalent Ihrer Webseiten.

Ich habe die Logik und die Interaktionen des BrittleTest in ein paar Seitenobjekte überarbeitet und einen neuen Test erstellt, der sie verwendet, anstatt den Webtreiber direkt zu treffen. Den neuen Test finden Sie hier. Der Code wird hier als Referenz kopiert:

öffentliche 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 Immergrüne Terrasse"; shippingPage.City = "Springfield"; shippingPage.State = "Kentucky"; shippingPage.PostalCode = "123456"; shippingPage.Country = "Vereinigte Staaten"; shippingPage.Phone = "2341231241"; shippingPage.Email = "[email protected]"; shippingPage.PromoCode = "FREE"; var orderPage = shippingPage.SubmitOrder (); Assert.AreEqual (orderPage.Title, "Checkout Complete"); 

Zugegebenermaßen hat der Testkörper nicht viel an Größe verloren und tatsächlich musste ich sieben neue Klassen erstellen, um diesen Test zu unterstützen. Trotz der zusätzlichen Codezeilen wurden nur einige Probleme behoben, die der ursprüngliche Sprödtest hatte (mehr dazu weiter unten). Lassen Sie uns zunächst ein wenig tiefer in das Muster der Seitenobjekte und das, was wir hier getan haben, eintauchen.

Mit dem Page Object-Muster erstellen Sie normalerweise eine Seitenobjektklasse pro getesteter Webseite, wobei die Klasse Interaktionen mit der Seite modelliert und kapselt. Ein Textfeld auf Ihrer Webseite wird also zu einer Zeichenfolgeeigenschaft im Seitenobjekt. Um dieses Textfeld auszufüllen, setzen Sie diese Texteigenschaft einfach auf den gewünschten Wert anstelle von:

driver.FindElement (By.Id ("Email")). Clear (); driver.FindElement (By.Id ("Email")). SendKeys ("[email protected]");

wir können schreiben:

registerPage.Email = "[email protected]";

woher registerPage ist eine Instanz der RegisterPage-Klasse. Ein Kontrollkästchen auf der Seite wird zu einer bool-Eigenschaft im Seitenobjekt. Wenn Sie das Kontrollkästchen aktivieren und deaktivieren, müssen Sie nur die boolesche Eigenschaft auf true oder false setzen. Ebenso wird ein Link auf der Webseite zu einer Methode im Seitenobjekt, und durch Klicken auf den Link wird die Methode im Seitenobjekt aufgerufen. Also statt:

driver.FindElement (By.LinkText ("Admin")). Klicken Sie auf ();

wir können schreiben:

homepage.GoToAdminForAnonymousUser ();

Tatsächlich wird jede Aktion auf unserer Webseite zu einer Methode in unserem Seitenobjekt, und als Reaktion auf diese Aktion (dh beim Aufruf der Methode für das Seitenobjekt) erhalten Sie eine Instanz eines anderen Seitenobjekts zurück, das auf die gerade von Ihnen gewünschte Webseite zeigt zu navigieren, indem Sie die Aktion ausführen (z. B. Senden eines Formulars oder Klicken auf einen Link). Auf diese Weise können Sie Ihre Ansichtsinteraktionen in Ihrem Testskript einfach verketten:

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

Hier werde ich nach der Registrierung des Benutzers zur Startseite geleitet (eine Instanz seines Seitenobjekts wird von zurückgegeben SubmitRegistration Methode). Auf der HomePage-Instanz rufe ich also an SelectGenreByName Klicken Sie auf der Seite auf einen Link 'Disco', der eine Instanz von AlbumBrowsePage zurückgibt, und dann auf diese Seite, die ich anrufe SelectAlbumByName Klicken Sie auf das Album "Le Freak" und geben Sie eine Instanz von AlbumDetailsPage usw. zurück.

Ich gebe es zu: Es gibt viele Klassen für etwas, was vorher gar keine Klasse war. Wir haben jedoch viele Vorteile aus dieser Praxis gewonnen. Erstens ist der Code nicht mehr prozedural. Wir verfügen über ein gut erhaltenes Testmodell, bei dem jedes Objekt eine gute Einkapselung der Interaktion mit einer Seite ermöglicht. Wenn sich zum Beispiel etwas in Ihrer Registrierungslogik ändert, müssen Sie lediglich Ihre RegisterPage-Klasse ändern, anstatt Ihre gesamte Testsuite durchzugehen und jede einzelne Interaktion mit der Registrierungsansicht zu ändern. Diese Modularität bietet auch eine gute Wiederverwendbarkeit: Sie können Ihre ShoppingCartPage Überall, wo Sie mit dem Einkaufswagen interagieren müssen. In einer einfachen Praxis, vom prozeduralen zum objektorientierten Testcode überzugehen, haben wir fast drei der vier Probleme mit dem anfänglichen spröden Test beseitigt, der prozeduraler Code und Logik- und Selektor-Duplizierung war. Wir haben immer noch ein paar Duplikate, die wir in Kürze beheben werden.

Wie haben wir diese Seitenobjekte tatsächlich implementiert? Ein Seitenobjekt in seinem Stamm ist nichts anderes als ein Wrapper für die Interaktionen, die Sie mit der Seite haben. Hier habe ich nur die Interaktionen der Benutzeroberfläche aus den spröden Tests extrahiert und in ihre eigenen Seitenobjekte eingefügt. Zum Beispiel wurde die Registrierungslogik in ihre eigene Klasse extrahiert RegisterPage das sah so aus:

public class RegisterPage: Page public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ("input [type = 'submit']")));  public string Benutzername set Ausführen (By.Name ("UserName"), e => e.Clear (); e.SendKeys (Wert););  public string Email set Ausführen (By.Name ("Email"), e => e.Clear (); e.SendKeys (Wert););  public string ConfirmPassword set Execute (By.Name ("ConfirmPassword"), e => e.Clear (); e.SendKeys (value););  public string Password set Ausführen (By.Name ("Password"), e => e.Clear (); e.SendKeys (value);); 

Ich habe eine erstellt Seite Superklasse, die sich um ein paar Dinge kümmert, wie NavigateTo was hilft, zu einer neuen Seite zu navigieren, indem Sie eine Aktion ausführen und Ausführen das führt einige Aktionen für ein Element aus. Das Seite Klasse sah aus wie:

öffentliche Klasse Page protected RemoteWebDriver WebDriver get return Host.Instance.WebDriver;  public string Title get return WebDriver.Title;  public TPage NavigateTo(Von) wo TPage: Page, new () WebDriver.FindElement (by) .Click (); return Activator.CreateInstance();  public void Execute (By by, Action.) action) var element = WebDriver.FindElement (by); Aktion (Element); 

In dem BrittleTest, mit einem Element zu interagieren, das wir gemacht haben FindElement einmal pro Aktion. Das Ausführen Abgesehen von der Abstraktion der Interaktion des Web-Treibers bietet diese Methode einen zusätzlichen Vorteil, durch den ein Element ausgewählt werden kann, das eine kostspielige Aktion sein kann, und einmal mehrere Aktionen ausführt:

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

wurde ersetzt durch:

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

Einen zweiten Blick auf die RegisterPage Seitenobjekt oben haben wir noch etwas Vervielfältigung. Testcode ist Code und wir möchten nicht, dass unser Code dupliziert wird. also lasst uns das umgestalten. Wir können den Code, der zum Ausfüllen eines Textfelds erforderlich ist, in eine Methode auf der Datenbank extrahieren Seite Klasse und rufen Sie das einfach von Seitenobjekten auf. Die Methode könnte folgendermaßen implementiert werden:

public void SetText (string elementName, string newText) Ausführen (By.Name (ElementName), e => e.Clear (); e.SendKeys (newText);); 

Und jetzt die Eigenschaften an RegisterPage kann geschrumpft werden auf:

public string Benutzername set SetText ("UserName", Wert); 

Sie könnten auch eine fließende API dafür erstellen, um den Setter besser lesen zu können (z. Füllen ("UserName"). Mit (Wert)) aber das überlasse ich dir.

Wir machen hier nichts Außergewöhnliches. Einfacher Refactoring in unserem Testcode, wie wir ihn immer für unseren "anderen" Code gemacht haben!!

Sie können den vollständigen Code für sehen Seite und RegisterPage Unterricht hier und hier.

Stark typisiertes Seitenobjekt

Wir haben Verfahrensprobleme mit dem Sprödtest gelöst, wodurch der Test lesbarer, modularer, DRYer und effektiv wartbar war. Es gibt eine letzte Ausgabe, die wir nicht behoben haben: Es gibt immer noch viele magische Zeichenketten. Kein Albtraum, aber immer noch ein Problem, das wir beheben konnten. Geben Sie stark typisierte Seitenobjekte ein!

Dieser Ansatz ist praktisch, wenn Sie für Ihre Benutzeroberfläche ein MV * -Framework verwenden. In unserem Fall verwenden wir ASP.Net MVC.

Schauen wir uns das noch einmal an RegisterPage:

public class RegisterPage: Page public HomePage SubmitRegistration () return NavigateTo(By.CssSelector ("input [type = 'submit']")));  public string Benutzername set SetText ("UserName", Wert);  öffentliche Zeichenfolge Email set SetText ("Email", Wert);  public string ConfirmPassword set SetText ("ConfirmPassword", Wert);  public string Password set SetText ("Passwort", Wert); 

Diese Seite modelliert die Registeransicht in unserer Web-App (nur das oberste Bit hier zu Ihrer Bequemlichkeit kopieren):

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

Hmmm, was ist das? RegisterModel Dort? Es ist das Ansichtsmodell für die Seite: die M in dem MVC. Hier ist der Code (ich habe die Attribute entfernt, um das Rauschen zu reduzieren):

öffentliche Klasse RegisterModel öffentliche Zeichenfolge Benutzername get; einstellen;  öffentliche Zeichenfolge Email get; einstellen;  öffentlicher String Passwort get; einstellen;  public string ConfirmPassword get; einstellen; 

Das sieht sehr bekannt aus, oder? Es hat die gleichen Eigenschaften wie das RegisterPage Klasse, die nicht überraschend ist RegisterPage wurde basierend auf diesem Ansichts- und Ansichtsmodell erstellt. Mal sehen, ob wir Ansichtsmodelle nutzen können, um unsere Seitenobjekte zu vereinfachen.

Ich habe ein neues erstellt Seite Superklasse; aber eine generische. Sie können den Code hier sehen:

öffentliche Klasse : Seite, auf der TViewModel: class, new () public void FillWith (TViewModel viewModel, IDictionary> propertyTypeHandling = null) // aus Gründen der Kürze entfernt

Das Seite Klasse Unterklassen der alten Seite Klasse und stellt alle seine Funktionalität bereit; aber es hat auch eine extra Methode aufgerufen Füllen mit was füllt die Seite mit der bereitgestellten View-Model-Instanz aus! So jetzt meine RegisterPage Klasse sieht so aus:

öffentliche Klasse RegisterPage: Seite public HomePage CreateValidUser (RegisterModel-Modell) FillWith (Modell); return NavigateTo(By.CssSelector ("input [type = 'submit']"))); 

Ich habe alle Seitenobjekte dupliziert, um beide Variationen zu zeigen und um die Codebase für Sie leichter nachvollziehen zu können. In Wirklichkeit benötigen Sie jedoch für jedes Seitenobjekt eine Klasse.

Nach dem Konvertieren meiner Seitenobjekte in generische Objekte sieht der Test jetzt so aus:

public class StrongeadrachetAbkAbbatWorkComponent [Test] public void Can_buy_an_Album_when_registered () var orderedPage = HomePage.Initiate () .GoToAdminForAnonymousUser () .GoToRegisterPage () .CreateValidUser (Objekt) Freak ") .AddAlbumToCart () .Checkout () .SubmitShippingInfo (ObjectMother.CreateShippingInfo ()," Free "); Assert.AreEqual ("Checkout Complete", orderPage.Title); 

Das ist es - der ganze Test! Viel lesbarer, trockener und wartbarer, nicht wahr??

Das ObjectMother Klasse, die ich im Test verwende, ist eine Objektmutter, die Testdaten bereitstellt (Code finden Sie hier), nichts Besonderes:

public class ObjectMother public static Order CreateShippingInfo () var shippingInfo = neuer Auftrag Vorname = "Homer", Nachname = "Simpson", Adresse = "742 Immergrüne Terrasse", Stadt = "Springfield", Bundesstaat = "Kentucky", PostalCode = "123456", Land = "Vereinigte Staaten", Telefon = "2341231241", E-Mail = "[email protected]"; Rückgabe shippingInfo;  public static RegisterModel CreateRegisterModel () var model = new RegisterModel Benutzername = "HJSimpson", Email = "[email protected]", Passwort = "! 2345Qwert", ConfirmPassword = "! 2345Qwert"; Rückkehrmodell; 

Stoppen Sie nicht am Seitenobjekt

Einige Webseiten sind sehr umfangreich und komplex. Früher habe ich gesagt, dass Testcode Code ist und wir sollten ihn als solchen behandeln. Normalerweise zerlegen wir große und komplexe Webseiten in kleinere und teilweise wiederverwendbare (Teil-) Komponenten. Dies ermöglicht uns, eine Webseite aus kleineren, überschaubareren Komponenten zusammenzustellen. Wir sollten das gleiche für unsere Tests tun. Dazu können wir Seitenkomponenten verwenden.

Eine Seitenkomponente ähnelt einem Seitenobjekt: Sie ist eine Klasse, die die Interaktion mit einigen Elementen einer Seite kapselt. Der Unterschied besteht darin, dass es mit einem kleinen Teil einer Webseite interagiert: Es modelliert ein Benutzersteuerelement oder eine Teilansicht, wenn Sie möchten. Ein gutes Beispiel für eine Seitenkomponente ist eine Menüleiste. Eine Menüleiste wird normalerweise auf allen Seiten einer Webanwendung angezeigt. Sie möchten nicht wirklich immer den Code wiederholen, der für die Interaktion mit dem Menü in jedem einzelnen Seitenobjekt erforderlich ist. Stattdessen können Sie eine Menüseitenkomponente erstellen und von Ihren Seitenobjekten aus verwenden. Sie können die Seitenkomponenten auch verwenden, um mit Datennetzen auf Ihren Seiten umzugehen, und um noch einen Schritt weiter zu gehen, könnte die Gitterseitenkomponente selbst aus Gitternetzseitenkomponenten bestehen. Im Falle von Mvc Music Store könnten wir eine TopMenuComponent und ein SideMenuComponent und nutze sie von unseren Startseite.

Wie in Ihrer Webanwendung können Sie auch ein Beispiel erstellen, LayoutPage Seitenobjekt, das Ihre Layout- / Masterseite modelliert und als Superklasse für alle anderen Seitenobjekte verwendet. Die Layoutseite würde dann aus Menüseitenkomponenten bestehen, so dass alle Seiten auf die Menüs zugreifen können. Ich denke, eine gute Faustregel wäre, eine Seitenkomponente pro Teilansicht, ein Layoutseitenobjekt pro Layout und ein Seitenobjekt pro Webseite zu haben. Auf diese Weise wissen Sie, dass Ihr Testcode genau so detailliert ist wie Ihr Code.

Einige Frameworks für UI-Tests

Was ich oben gezeigt habe, war eine sehr einfache und konstruierte Probe mit einigen unterstützenden Klassen als Testinfrastruktur. In der Realität sind die Anforderungen für das Testen von Benutzeroberflächen sehr viel komplexer: Es gibt komplexe Steuerelemente und Interaktionen, Sie müssen auf Ihre Seiten schreiben und lesen, Sie müssen sich mit Netzwerklatenzen beschäftigen und AJAX und andere Javascript-Interaktionen steuern. müssen verschiedene Browser auslösen und so weiter, was ich in diesem Artikel nicht erklärt habe. Obwohl es möglich ist, um all dies herum zu codieren, kann die Verwendung einiger Frameworks viel Zeit sparen. Hier sind die Frameworks, die ich sehr empfehlen kann:

Frameworks für .Net:

  • Seleno ist ein Open Source-Projekt von TestStack, mit dem Sie automatisierte UI-Tests mit Selenium schreiben können. Es konzentriert sich auf die Verwendung von Seitenobjekten und Seitenkomponenten sowie das Lesen und Schreiben von Webseiten mit stark typisierten Ansichtsmodellen. Wenn Ihnen gefallen hat, was ich in diesem Artikel gemacht habe, werden Sie auch Seleno mögen, da der größte Teil des hier gezeigten Codes aus der Seleno-Codebase entliehen wurde.
  • White ist ein Open Source-Framework von TestStack für die Automatisierung von Rich-Client-Anwendungen auf Basis von Win32-, WinForms-, WPF-, Silverlight- und SWT (Java) -Plattformen.

Offenlegung: Ich bin Mitgründer und Mitglied des Entwicklungsteams der TestStack-Organisation.

Frameworks für Ruby:

  • Capybara ist ein Akzeptanztest-Framework für Webanwendungen, mit dem Sie Webanwendungen testen können, indem Sie simulieren, wie ein echter Benutzer mit Ihrer App interagieren würde.
  • Poltergeist ist ein Fahrer für Capybara. Damit können Sie Ihre Capybara-Tests in einem WebKit-Browser mit Headless ausführen, der von PhantomJS bereitgestellt wird.
  • page-object (Ich habe diesen Edelstein nicht persönlich verwendet) ist ein einfacher Edelstein, der beim Erstellen flexibler Seitenobjekte zum Testen von browserbasierten Anwendungen hilft. Das Ziel besteht darin, das Erstellen von Abstraktionsebenen in Ihren Tests zu vereinfachen, um die Tests von dem zu testenden Objekt zu entkoppeln und eine einfache Schnittstelle zu den Elementen auf einer Seite bereitzustellen. Es funktioniert sowohl mit Watir-Webdriver als auch mit Selenium-Webdriver.

Fazit

Wir haben mit einer typischen UI-Automatisierungserfahrung begonnen, erläutert, warum UI-Tests fehlschlagen, ein Beispiel für einen spröden Test gegeben, die Probleme besprochen und anhand einiger Ideen und Muster gelöst.

Wenn Sie einen Punkt aus diesem Artikel entnehmen möchten, sollte dies lauten: Test Code Is Code. Wenn Sie darüber nachdenken, habe ich in diesem Artikel lediglich die guten Kodierungen und objektorientierten Praktiken, die Sie bereits kennen, auf einen UI-Test anwenden können.

Es gibt noch viel über UI-Tests zu lernen, und ich werde versuchen, einige der fortgeschritteneren Tipps in einem zukünftigen Artikel zu besprechen.

Viel Spaß beim Testen!