Der Wunsch, sich von manuellen Regressionstests zu lösen, ist ein guter Grund, Autotests einzuführen. Die Frage ist welche? Die Schnittstellenentwickler Natalya Stus und Alexei Androsov erinnerten sich daran, wie ihr Team mehrere Iterationen durchlaufen und Frontend-Tests in Auto.ru basierend auf Jest und Puppeteer erstellt hat: Komponententests, Tests für einzelne React-Komponenten, Integrationstests. Das interessanteste dieser Erfahrung ist das isolierte Testen von React-Komponenten in einem Browser ohne Selenium Grid, Java und andere Dinge.

Alexey:
- Zuerst müssen Sie ein wenig erzählen, was Automotive News ist. Dies ist eine Seite, die Autos verkauft. Es gibt eine Suche, ein persönliches Konto, Autoservices, Ersatzteile, Bewertungen, Händler und vieles mehr. Auto.ru ist ein sehr großes Projekt, viel Code. Wir schreiben den gesamten Code in einem großen Monorepe, weil alles durcheinander ist. Dieselben Personen erledigen ähnliche Aufgaben, z. B. für Mobilgeräte und Desktops. Es stellt sich eine Menge Code heraus, und Monorepa ist für uns von entscheidender Bedeutung. Die Frage ist, wie man es testet?

Wir haben React und Node.js, die serverseitiges Rendern durchführen und Daten vom Backend anfordern. Verbleibende und kleine Stücke auf dem BEM.

Natalya:
- Wir haben angefangen, an Automatisierung zu denken. Der Release-Zyklus unserer einzelnen Anwendungen umfasste mehrere Schritte. Zunächst wird die Funktion vom Programmierer in einem separaten Zweig entwickelt. Danach wird die Funktion in demselben separaten Zweig von manuellen Testern getestet. Wenn alles in Ordnung ist, fällt die Aufgabe in den Release-Kandidaten. Wenn nicht, kehren Sie erneut zur Entwicklungsiteration zurück und testen Sie erneut. Bis der Tester sagt, dass in dieser Funktion alles in Ordnung ist, fällt sie nicht in den Release-Kandidaten.
Nach dem Zusammenstellen des Release-Kandidaten erfolgt eine manuelle Regression - nicht nur Auto.ru, sondern nur das Paket, das wir rollen werden. Wenn wir beispielsweise das Desktop-Web rollen, gibt es eine manuelle Regression des Desktop-Web. Dies sind viele manuelle Testfälle. Eine solche Regression dauerte ungefähr einen Arbeitstag eines manuellen Testers.
Wenn die Regression abgeschlossen ist, erfolgt eine Freigabe. Danach verschmilzt der Release-Zweig mit dem Master. Zu diesem Zeitpunkt können wir nur den Mastercode einfügen, den wir nur für das Desktop-Web getestet haben, und dieser Code kann beispielsweise das mobile Web beschädigen. Dies wird nicht sofort überprüft, sondern erst bei der nächsten manuellen Regression - dem mobilen Web.

Der schmerzhafteste Punkt in diesem Prozess war natürlich die manuelle Regression, die sehr lange dauerte. Natürlich haben alle manuellen Tester es satt, jeden Tag das Gleiche zu tun. Deshalb haben wir uns entschlossen, alles zu automatisieren. Die erste Lösung, die ausgeführt wurde, waren die Selenium- und Java-Selbsttests, die von einem separaten Team geschrieben wurden. Dies waren End-to-End-Tests, e2e, bei denen die gesamte Anwendung getestet wurde. Sie haben ungefähr fünftausend solcher Tests geschrieben. Womit sind wir gelandet?
Natürlich haben wir die Regression beschleunigt. Autotests bestehen viel schneller als ein manueller Tester, etwa zehnmal schneller. Dementsprechend wurden die Routinemaßnahmen, die sie jeden Tag ausführten, aus den manuellen Testern entfernt. Gefundene Fehler aus Autotests sind leichter zu reproduzieren. Starten Sie diesen Test einfach neu oder sehen Sie sich die Schritte an, die er ausführt - im Gegensatz zum manuellen Tester, der sagt: "Ich habe auf etwas geklickt und alles ist kaputt gegangen."
Vorausgesetzt, Stabilität der Beschichtung. Wir führen immer die gleichen Lauftests durch - im Gegensatz zu manuellen Tests, bei denen der Tester feststellen kann, dass wir diesen Ort nicht berührt haben, und ich werde es diesmal nicht überprüfen. Wir haben Tests hinzugefügt, um Screenshots zu vergleichen, und die Genauigkeit beim Testen der Benutzeroberfläche verbessert. Jetzt überprüfen wir die Diskrepanz in einigen Pixeln, die der Tester mit seinen Augen nicht sehen kann. Alles dank Screenshot-Tests.
Aber es gab Nachteile. Die größte - für e2e-Tests benötigen wir eine Testumgebung, die vollständig mit dem Produkt übereinstimmt. Es muss immer auf dem neuesten Stand und betriebsbereit sein. Dies erfordert fast so viel Kraft wie der Verkauf von Stabilitätsunterstützung. Das können wir uns natürlich nicht immer leisten. Daher hatten wir oft Situationen, in denen die Testumgebung lügt oder irgendwo etwas kaputt ist und Tests fehlschlagen, obwohl das vorderste Paket keine Probleme aufwies.
Diese Tests werden auch von einem separaten Team entwickelt, das seine eigenen Aufgaben hat, seine eigene Aufgabe im Task-Tracker hat und neue Funktionen werden mit einiger Verzögerung behandelt. Sie können nicht sofort nach der Veröffentlichung einer neuen Funktion kommen und sofort Autotests darauf schreiben. Da die Tests teuer und schwer zu schreiben und zu warten sind, decken wir nicht alle Szenarien ab, sondern nur die kritischsten. Gleichzeitig wird ein separates Team benötigt, das über separate Tools und eine separate Infrastruktur verfügt. Die Analyse der gefallenen Tests ist auch für manuelle Tester oder Entwickler eine nicht triviale Aufgabe. Ich werde einige Beispiele zeigen.

Wir haben Tests durchgeführt. 500 Tests bestanden, von denen einige fielen. Wir können so etwas im Bericht sehen. Hier hat der Test einfach nicht begonnen und es ist nicht klar, ob dort alles gut ist oder nicht.

Ein weiteres Beispiel: Der Test wurde gestartet, stürzte jedoch mit einem solchen Fehler ab. Er konnte kein Element auf der Seite finden, aber warum - wir wissen es nicht. Entweder wurde dieses Element einfach nicht angezeigt, oder es stellte sich heraus, dass es sich auf der falschen Seite befand, oder der Locator wurde geändert. Alles was Sie brauchen, um Hände zu debazhen.

Screenshot-Tests geben uns auch nicht immer eine gute Genauigkeit. Hier laden wir eine Art Karte, sie hat sich leicht bewegt, unser Test ist gefallen.

Wir haben versucht, eine Reihe dieser Probleme zu lösen. Wir haben begonnen, einen Teil der Tests auf dem Produkt durchzuführen - diejenigen, die keine Auswirkungen auf Benutzerdaten haben, ändern nichts in der Datenbank. Das heißt, wir haben bei prod eine separate Maschine hergestellt, die in die Prod-Umgebung schaut. Wir installieren einfach ein neues Frontend-Paket und führen dort Tests durch. Das Produkt ist zumindest stabil.
Wir haben einige der Tests auf Mokeys übertragen, aber wir haben viele verschiedene Backends, verschiedene APIs, und das Sperren ist eine sehr schwierige Aufgabe, insbesondere für 5.000 Tests. Dafür wurde ein spezieller Dienst namens Mockritsa geschrieben, der es einfach macht, die notwendigen Mokas für das Frontend zu erstellen, und der ziemlich einfach zu vertreten ist.
Wir mussten auch ein paar Eisen kaufen, damit unser Selenium-Gitter von Geräten, von denen diese Tests gestartet werden, größer war, damit sie nicht herunterfielen, weil sie den Browser nicht anheben konnten und dementsprechend schneller gingen. Selbst nachdem wir versucht hatten, diese Probleme zu lösen, kamen wir zu dem Schluss, dass solche Tests nicht für CI geeignet sind, sondern sehr lange dauern. Wir können sie nicht bei jeder Poolanforderung ausführen. Wir werden diese Berichte, die für jede Poolanforderung generiert werden, später in unserem Leben einfach nie analysieren.

Dementsprechend benötigen wir für CI schnelle und stabile Tests, die aus zufälligen Gründen nicht fehlschlagen. Wir möchten Tests für Poolanfragen ohne Teststände, Backends, Datenbanken und ohne komplizierte Benutzerfälle ausführen.
Wir möchten, dass diese Tests gleichzeitig mit dem Code geschrieben werden und dass die Testergebnisse sofort deutlich machen, in welcher Datei etwas schief gelaufen ist.
Alexey:
- Ja, und wir haben beschlossen, alles zu versuchen, was wir wollen, um alles von Anfang bis Ende in derselben Jest-Infrastruktur zu verbessern. Warum haben wir uns für Jest entschieden? Wir haben bereits Unit-Tests zu Jest geschrieben, es hat uns gefallen. Dies ist ein beliebtes, unterstütztes Tool, das bereits eine Reihe vorgefertigter Integrationen enthält: React Test Render, Enzyme. Alles funktioniert sofort, nichts muss gebaut werden, alles ist einfach.

Und Jest hat persönlich für mich gewonnen, da es im Gegensatz zu jedem Moka schwierig ist, die Nebenwirkung eines Tests eines Drittanbieters in Ihr Bein zu schießen, wenn ich vergessen habe, ihn oder etwas anderes zu reinigen. In Moka wird dies ein- oder zweimal gemacht, aber in Jest ist es schwierig: Es wird ständig in separaten Threads gestartet. Es ist möglich, aber schwierig. Und für e2e Puppeteer haben wir uns auch entschlossen, es zu versuchen. Das haben wir bekommen.

Natalya:
"Ich werde auch mit einem Beispiel für Unit-Tests beginnen." Wenn wir Tests nur für eine Funktion schreiben, gibt es keine besonderen Probleme. Wir rufen diese Funktion auf, übergeben einige Argumente, vergleichen, was passiert ist, mit dem, was hätte passieren sollen.
Wenn wir über React-Komponenten sprechen, wird alles etwas komplizierter. Wir müssen sie irgendwie rendern. Es gibt einen React-Test-Renderer, der jedoch für Unit-Tests nicht sehr praktisch ist, da wir Komponenten nicht isoliert testen können. Es wird die Komponente vollständig bis zum Ende rendern, bis zum Layout.
Und ich möchte zeigen, wie es mit Enzyme möglich ist, Unit-Tests für React-Komponenten anhand eines Beispiels für eine solche Komponente zu schreiben, für die wir eine bestimmte MyComponent haben. Er bekommt eine Art Requisite, er hat eine Art Logik. Dann gibt er die Foo-Komponente zurück, die wiederum die Balkenkomponente zurückgibt, die bereits in der Balkenkomponente tatsächlich das Layout an uns zurückgibt.

Wir können ein Enzym-Tool wie das flache Rendern verwenden. Dies ist genau das, was wir brauchen, um die MyComponent-Komponente isoliert zu testen. Und diese Tests hängen nicht davon ab, was die Komponenten foo und bar in sich enthalten. Wir werden nur die Logik der Komponente MyComponent testen.
Jest hat so etwas wie Snapshot, und sie können uns auch hier helfen. "Erwarten Sie etwas zu MatchSnapshot" erstellt eine solche Struktur für uns, nur eine Textdatei, in der tatsächlich gespeichert wird, was wir erwartet haben, was passiert und wenn dieser Test zum ersten Mal ausgeführt wird, wird diese Datei geschrieben. Bei weiteren Testläufen wird das Erhaltene mit dem in der Datei MyComponent.test.js.snap enthaltenen Standard verglichen.
Hier sehen wir nur, dass das gesamte Rendering genau das zurückgibt, was die Rendermethode von MyComponent zurückgibt, und was foo ist, ist im Allgemeinen egal. Wir können solche zwei Tests für unsere zwei Fälle schreiben, für unsere zwei Fälle für die MyComponent-Komponente.

Im Prinzip können wir dasselbe ohne einen Snapshot testen, indem wir nur die benötigten Skripte überprüfen, zum Beispiel, welche Requisite an die foo-Komponente übergeben wird. Dieser Ansatz hat jedoch ein Minus. Wenn wir MyComponent, unserem neuen Test, ein anderes Element hinzufügen, wird dies in keiner Weise angezeigt.

Schnappschuss-Tests sind daher diejenigen, die uns fast alle Änderungen innerhalb der Komponente zeigen. Wenn wir jedoch beide Tests in Snapshot schreiben und dann dieselben Änderungen an der Komponente vornehmen, werden wir feststellen, dass beide Tests fallen. Im Prinzip sagen uns die Ergebnisse dieser gefallenen Tests ungefähr dasselbe, dass wir dort eine Art „Hallo“ hinzugefügt haben.

Und das ist auch überflüssig, daher glaube ich, dass es besser ist, einen Snapshot-Test für dieselbe Struktur zu verwenden. Überprüfen Sie den Rest der Logik irgendwie anders, ohne Snapshot, da Snapshot nicht sehr bezeichnend ist. Wenn Sie Snapshot sehen, sehen Sie nur, dass etwas gerendert wurde, aber es ist nicht klar, welche Logik Sie hier getestet haben. Dies ist für TDD völlig ungeeignet, wenn Sie es verwenden möchten. Und es funktioniert nicht wie eine Dokumentation. Das heißt, wenn Sie sich diese Komponente ansehen, werden Sie feststellen, dass Snapshot zwar etwas entspricht, aber welche Logik es gab, ist nicht sehr klar.


Auf die gleiche Weise werden wir Unit-Tests für die foo-Komponente schreiben, für die Balkenkomponente, zum Beispiel Snapshot.

Wir erhalten eine 100% ige Abdeckung für diese drei Komponenten. Wir glauben, dass wir alles überprüft haben, wir sind gut gemacht.
Nehmen wir an, wir haben etwas an der Stangenkomponente geändert, eine neue Requisite hinzugefügt und natürlich einen Test für die Stangenkomponente durchgeführt. Wir haben den Test korrigiert und alle drei Tests bestehen mit uns.

Wenn wir jedoch diese ganze Geschichte sammeln, funktioniert nichts, da MyComponent mit einem solchen Fehler nicht zurechtkommt. Wir geben die erwartete Stütze nicht an die Stangenkomponente weiter. Daher sprechen wir über die Tatsache, dass wir in diesem Fall auch Integrationstests benötigen, die prüfen, ob wir die untergeordnete Komponente von unserer Komponente korrekt aufrufen.

Wenn Sie solche Komponenten haben und eine davon ändern, sehen Sie sofort, welche Auswirkungen diese Komponente hat.
Welche Möglichkeiten haben wir in Enzyme, Integrationstests durchzuführen? Flaches Rendern selbst gibt eine solche Struktur zurück. Es enthält eine Tauchmethode. Wenn es für eine React-Komponente aufgerufen wird, schlägt es darin fehl. Wenn wir es auf der foo-Komponente aufrufen, erhalten wir dem, was die foo-Komponente rendert. Dies ist bar. Wenn wir den Tauchgang erneut durchführen, erhalten wir tatsächlich das Layout, das die bar-Komponente an uns zurückgibt. Dies ist nur ein Integrationstest.

Oder Sie können alles auf einmal mit der Mount-Methode rendern, die das vollständige DOM-Rendering implementiert. Ich rate jedoch nicht dazu, da es ein sehr schwieriger Schnappschuss sein wird. In der Regel müssen Sie nicht die gesamte Struktur vollständig überprüfen. Sie müssen jeweils nur die Integration zwischen der übergeordneten und der untergeordneten Komponente überprüfen.

Und für MyComponent fügen wir einen Integrationstest hinzu, also füge ich im ersten Test nur Tauchen hinzu, und es stellt sich heraus, dass wir nicht nur die Logik der Komponente selbst getestet haben, sondern auch deren Integration mit der foo-Komponente. Ebenso fügen wir den Integrationstest für die foo-Komponente hinzu, die die Balkenkomponente korrekt aufruft, und überprüfen dann die gesamte Kette. Wir sind sicher, dass keine Änderungen uns beim Rendern von MyComponent stören werden

Ein weiteres Beispiel, bereits aus einem realen Projekt. Nur kurz darüber, was Jest und Enzyme sonst noch können. Scherz kann Moki machen. Wenn Sie eine externe Funktion in Ihrer Komponente verwenden, können Sie diese sperren. In diesem Beispiel rufen wir beispielsweise eine Art API auf. Wir möchten natürlich nicht in eine API im Komponententest gehen, also löschen wir einfach die Funktion getResource mit einem jest.fn-Objekt. In der Tat die Scheinfunktion. Dann können wir überprüfen, ob es aufgerufen wurde oder nicht, wie oft es aufgerufen wurde, mit welchen Argumenten. All dies ermöglicht es Ihnen, Scherz zu machen.

Beim flachen Rendern können Sie den Speicher an eine Komponente übergeben. Wenn Sie ein Geschäft benötigen, können Sie es einfach dorthin übertragen, und es wird funktionieren.

Sie können auch Status und Requisite in einer bereits gerenderten Komponente ändern.

Sie können die Simulationsmethode für eine Komponente aufrufen. Es ruft nur den Handler an. Wenn Sie beispielsweise einen Klick simulieren, wird hier onClick für die Schaltflächenkomponente aufgerufen. All dies kann natürlich in der Dokumentation zu Enzyme, vielen nützlichen Stücken, nachgelesen werden. Dies sind nur einige Beispiele aus einem realen Projekt.

Alexey:
- Wir kommen zu der interessantesten Frage. Wir können Jest testen, wir können Unit-Tests schreiben, Komponenten überprüfen, überprüfen, welche Elemente auf einen Klick falsch reagieren. Wir sind in der Lage, ihre HTML zu überprüfen. Jetzt müssen wir das Layout der Komponente CSS überprüfen.

Und es ist ratsam, dies zu tun, damit sich das Prinzip des Testens in keiner Weise von dem unterscheidet, das ich zuvor beschrieben habe. Wenn ich HTML überprüfe, dann habe ich flaches Rendern aufgerufen, es hat HTML genommen und für mich gerendert. Ich möchte CSS überprüfen, einfach eine Art Render aufrufen und einfach überprüfen - ohne etwas zu erhöhen, ohne Werkzeuge einzurichten.

Ich fing an, danach zu suchen, und fast überall wurde die gleiche Antwort auf diese ganze Sache gegeben, die Puppenspieler oder Selen-Gitter genannt wurde. Sie öffnen eine Registerkarte, gehen zu einer HTML-Seite, machen einen Screenshot und vergleichen ihn mit der vorherigen Option. Wenn es sich nicht geändert hat, ist alles in Ordnung.
Die Frage ist, was ist Seite HTML, wenn ich nur eine Komponente isoliert überprüfen möchte? Es ist wünschenswert - unter verschiedenen Bedingungen.

Ich möchte nicht für jede Komponente, für jeden Status eine Reihe dieser HTML-Seiten schreiben. Avito hat einen guten Lauf. Roma Dvornov veröffentlichte einen Artikel über Habré, und er hatte übrigens eine Rede. Was haben sie gemacht? Sie nehmen Komponenten auf, setzen HTML durch ein Standard-Rendering zusammen. Dann sammeln sie mit Hilfe von Plugins und allerlei Tricks alle Vermögenswerte, die sie haben - Bilder, CSS. Fügen Sie alles in HTML ein und sie erhalten einfach das richtige HTML.

Und dann haben sie einen speziellen Server ausgelöst, dort HTML gesendet, es rendert es und gibt ein Ergebnis zurück. Ein sehr interessanter Artikel, lesen Sie jedoch, Sie können viele interessante Ideen von dort zeichnen.

Was ich dort nicht mag. Das Zusammenbauen einer Komponente unterscheidet sich von der Produktion. Zum Beispiel haben wir ein Webpack, und dort wird es von einigen Babel-Assets gesammelt und auf andere Weise herausgezogen. Ich kann nicht garantieren, dass ich getestet habe, was ich jetzt herunterladen werde.
Und wieder ein separater Dienst für Screenshots. Ich möchte es irgendwie einfacher machen. Und es gab tatsächlich die Idee, dass wir es genau so sammeln sollten, wie wir es sammeln werden. Und versuchen Sie etwas wie Docker zu verwenden, weil es so etwas ist, es kann auf einen Computer gestellt werden, lokal, es wird einfach, isoliert, berührt nichts, alles ist in Ordnung.

Aber dieses Problem ist mit Seite HTML, es bleibt das gleiche, was es wirklich ist. Und eine Idee wurde geboren. Sie haben eine so vereinfachte webpack.conf und daraus gibt es einige EntryPoint für Client-JS. Die Module werden beschrieben, wie man sie zusammenbaut, die Ausgabedatei, alle von Ihnen beschriebenen Plugins, alles ist konfiguriert, alles ist in Ordnung.

Was ist, wenn mir das gefällt? Er wird in meine Komponente gehen und sie isoliert sammeln. Und es wird genau eine Komponente geben. Wenn ich dort ein HTML-Webpack hinzufüge, wird mir auch HTML angezeigt, und diese Assets werden dort gesammelt, und dieses Ding kann jedoch bereits automatisch getestet werden.
Und ich wollte das alles schreiben, aber dann fand ich das.

Jest-Puppenspieler-React, ein junges Plugin. Und ich begann aktiv dazu beizutragen. Wenn du es plötzlich probieren willst, kannst du zum Beispiel zu mir kommen, ich kann irgendwie helfen. Das Projekt ist in der Tat nicht meins.
Sie schreiben eine reguläre Datei als test.js, und diese Dateien müssen ein wenig separat geschrieben werden, um sie zu finden, damit nicht das gesamte Projekt für Sie kompiliert wird, sondern nur die erforderlichen Komponenten kompiliert werden. In der Tat nehmen Sie die Webpack-Konfiguration. Und die Eingabepunkte ändern sich in diese browser.js-Dateien, das heißt, genau das, was wir testen möchten, wird in HTML verpackt, und mit Hilfe von Puppeteer werden Screenshots erstellt.

Was kann er tun? , jest-image-snapshot. . , , js, media-query, , .
headless-, , , , , headless-, Chrome . web-, , , , .
Docker. . . , Docker, . . Docker , , , Linux, - , - . Docker , .

? , . , . before-after, , . , . , Chrome, Firefox. .
. pixelmatch. , looksame, «», . , .

— . , , . , : - , — Enzyme. Redux store . . viewport, , . , , .

. , . ? , .

: 5-10 . Selenium . , , , . .

Puppeteer, e2e-. , e2e- — , Selenium.
:
— , Selenium Java , . - JS Puppeteer, , .
, . , , .

— Selenium Java, — JS Puppeteer. . 18 . , , Java. , , Java Selenium.

:
— ? . , html-, css . e2e. . , .

, , . . , , — , . , . - , , : , .
, , . git hook, -, . green master — , , , . Vielen Dank.