Es gibt nie zu viele Tests - das weiß jeder. Memes über Unit- und Integrationstests machen keinen Spaß mehr. Wir wissen jedoch immer noch nicht, ob es möglich ist, sich auf die Ergebnisse des Bestehens der Tests zu verlassen, und wie viel Prozent der Abdeckung es uns ermöglicht, Fehler nicht in die Produktion zu lassen. Wenn schwerwiegende Änderungen in den Codes überspringen, ohne das Ergebnis zu beeinflussen, bietet sich die Lösung an - Sie müssen die Tests testen!

Der Ansatz zur Automatisierung dieser Aufgabe war der Bericht von Mark Langovoy von
Frontend Conf . Das Video und der Artikel sind kurz und die Ideen funktionieren sehr gut - das müssen Sie beachten.
Über den Sprecher: Mark
Langovoi (
marklangovoi ) arbeitet in Yandex im
Yandex.Tolok- Projekt. Dies ist eine Crowdsourcing-Plattform zum schnellen Markieren großer Datenmengen. Kunden laden Daten herunter, die beispielsweise für die Verwendung in Algorithmen für maschinelles Lernen vorbereitet werden müssen, und legen einen Preis fest. Auf der anderen Seite können Ausführende Aufgaben erledigen und Geld verdienen.
In seiner Freizeit entwickelt Mark die Krasnodar Devodar Developer Days, eine von 19
IT-Communities, deren Aktivisten wir zu Frontend Conf nach Moskau eingeladen haben.
Testen
Es gibt verschiedene Arten von automatisierten Tests.

Während gängiger
Unit-Tests schreiben wir Tests für kleine Teile (Module) einer Anwendung. Sie sind einfach zu schreiben, aber manchmal verhalten sie sich während der Integration mit anderen Modulen möglicherweise nicht genau so, wie wir es erwartet haben.
Um dies zu vermeiden, können wir
Integrationstests schreiben, die den Betrieb unserer Module gemeinsam testen.

Sie sind etwas komplizierter, daher konzentrieren wir uns heute auf Unit-Tests.
Unit Testing
Jedes Projekt, das zumindest eine minimale Stabilität wünscht, schreibt Unit-Tests.
Betrachten Sie ein Beispiel.
class Signal { on(callback) { ... } off(callback) { const callbackIndex = this.listeners.indexOf(callback); if (callbackIndex === -1) { return; } this.listeners = [ ...this.listeners.slice(0, callbackIndex - 1), ...this.listeners.slice(callbackIndex) ]; } trigger() { ... } }
Es gibt eine Signalklasse - dies ist ein Ereignisemitter mit einer Ein-Methode zum Abonnieren und einer Aus-Methode zum Löschen eines Abonnements. Wir prüfen, ob der Rückruf im Abonnenten-Array enthalten ist, und löschen ihn dann. Und natürlich gibt es eine Auslösemethode, die signierte Rückrufe aufruft.
Für dieses Beispiel haben wir einen einfachen Test, der die Ein- und Ausschaltmethoden und dann den Trigger aufruft, um zu überprüfen, ob der Rückruf nach dem Abbestellen nicht aufgerufen wurde.
test('off method should remove listener', () => { const signal = new Signal(); let wasCalled = false; const callback = () => { wasCalled = true; }; signal.on(callback); signal.off(callback); signal.trigger(); expect(wasCalled).toBeFalsy(); });
Qualitätsbewertungskriterien
Was sind die Kriterien für die Beurteilung der Qualität eines solchen Tests?
Die Codeabdeckung ist das beliebteste und bekannteste Kriterium, das angibt, wie viele Prozent der Codezeilen ausgeführt wurden, als der Test ausgeführt wurde.

Möglicherweise haben Sie eine Codeabdeckung von 70%, 80% oder 90%. Bedeutet dies jedoch, dass beim Sammeln des nächsten Builds für die Produktion alles in Ordnung ist oder etwas schief geht?
Kehren wir zu unserem Beispiel zurück.
Freitagabend, Sie sind müde und beenden das nächste Feature. Und dann stoßen Sie auf diesen Code, den Ihr Kollege geschrieben hat. Etwas an dir schien dir kompliziert und beängstigend.
...this.listeners.slice(0, callbackIndex - 1), ...this.listeners.slice(callbackIndex)
Sie haben entschieden, dass Sie das Array wahrscheinlich einfach löschen können:
class Signal { ... off(callback) { const callbackIndex = this.listeners.indexOf(callback); if (callbackIndex === -1) { return; } this.listeners = []; } ... }
Ich habe mich verpflichtet, das Projekt zusammengestellt und an die Produktion geschickt. Tests bestanden - warum nicht? Und er ging in einer Bar zur Ruhe.

Aber plötzlich, spät in der Nacht, klingelte ein Anruf beim Empfänger, dass alles fiel, die Leute das Produkt nicht benutzen konnten und im Allgemeinen verschwendete das Geschäft Geld! Sie brennen, Sie werden mit Entlassung bedroht.

Wie gehe ich damit um? Was tun mit Tests? Wie kann man solche primitiven dummen Fehler auffangen? Wer wird die Tests testen?
Natürlich können Sie eine Armee von QS-Ingenieuren einstellen - lassen Sie sie sitzen und klatschen Sie einfach in unsere Bewerbung.

Oder mieten Sie QS-Automatisierung. Sie können für das Schreiben von Tests verantwortlich gemacht werden - warum selbst schreiben, wenn es spezielle Leute dafür gibt?
Tatsächlich ist es jedoch teuer, daher werden wir heute über Mutationsanalysen oder Mutationstests sprechen.
Mutationstests
Dies ist eine Möglichkeit, den Testprozess unserer Tests zu automatisieren. Ihr Zweck ist es, ineffektive und unvollständige Tests zu identifizieren, dh tatsächlich
Tests zu testen .
Die Idee ist, Codeteile zu ändern, Tests mit ihnen durchzuführen, und wenn die Tests nicht fallen, sind sie unvollständig.
Änderungen werden mit bestimmten Operationen vorgenommen -
Mutatoren . Sie ersetzen beispielsweise Plus durch Minus, Multiplikation mit Division und andere ähnliche Operationen. Mutatoren können Codeteile ändern, Bedingungen in while-Arrays auf Null setzen, anstatt dem Array ein Element hinzuzufügen.

Durch das Anwenden von Mutationen auf den Quellcode mutiert dieser und wird zu einer
Mutante .
Mutanten werden in zwei Kategorien unterteilt:
- Getötet - diejenigen, bei denen wir Abweichungen feststellen konnten, dh bei denen mindestens ein Test fiel.
- Die Überlebenden sind diejenigen, die vor uns weggelaufen sind und den Fehler in die Produktion gebracht haben.
Zur Beurteilung der Qualität gibt es eine
MSI- Metrik
(Mutation Score Indicator) - den Prozentsatz der getöteten und überlebenden Mutanten. Je größer der Unterschied zwischen Codeabdeckungstests und MSI ist, desto schlechter spiegelt der Prozentsatz der Codeabdeckung die Relevanz unserer Tests wider.
Dies war ein bisschen Theorie, und jetzt überlegen Sie, wie es in JavaScript verwendet werden kann.
Javascript-Lösung
Es gibt nur ein aktiv entwickeltes Mutationstest-Tool in JavaScript -
Stryker . Dieser Name wurde dem Instrument zu Ehren des Charakters von X-Man William Stryker gegeben - dem Schöpfer von "Weapon X" und einem Kämpfer mit allen Mutanten.

Stryker ist kein Testläufer wie Karma oder Jest; Es ist auch kein Test-Framework wie Mocha oder Jasmine. Dies ist ein Mutationstest-Framework, das Ihre aktuelle Infrastruktur ergänzt.
Plugin-System
Stryker ist sehr flexibel und basiert vollständig auf einem Plug-In-System, von dem die meisten von Stryker-Entwicklern geschrieben wurden.

Es gibt Plugins zum Ausführen von Tests für Jest, Karma und Mocha. Es gibt eine Integration mit den Frameworks Mocha (Stryker-Mocha-Framework) Jasmine (Stryker-Jasmin) und vorgefertigten Mutator-Sets für JavaScript, TypeScript und sogar für Vue:
- Stryker-Javascript-Mutator;
- Stryker-Typoskript;
- Stryker-Vue-Mutator.
Mutatoren für die Reaktion sind im Stryker-Javascript-Mutator enthalten. Darüber hinaus können Sie jederzeit Ihre eigenen Mutatoren schreiben.
Wenn Sie den Code vor der Ausführung konvertieren müssen, können Sie Plugins für Webpack, Babel oder TypeScript verwenden.

Dies ist alles relativ einfach eingerichtet.
Konfiguration
Die Konfiguration ist nicht schwierig: Sie müssen lediglich in der JSON-Konfiguration angeben, welchen Testrunner (und / oder Testframework und / oder Transpiler) Sie verwenden, und die entsprechenden Plugins von npm installieren.
Das einfache Konsolendienstprogramm
stryker-cli kann dies alles in einem Frage- und Antwortmodus für Sie erledigen. Sie wird Sie fragen, was Sie verwenden, und sich selbst konfigurieren.
Wie funktioniert es?
Der Lebenszyklus ist einfach und besteht aus folgenden Schritten:
- Lesen und Analysieren der Konfiguration. Stryker lädt die Konfiguration herunter und analysiert sie auf verschiedene Plugins, Einstellungen, Ausschluss von Dateien usw.
- Laden Sie die Plugins gemäß der Konfiguration herunter.
- Ausführen von Tests für den Quellcode, um zu überprüfen, ob die Tests gerade relevant sind (plötzlich sind sie bereits fehlerhaft).
- Wenn alles in Ordnung ist, wird aus den Dateien, die wir mutieren durften, eine Reihe von Mutanten generiert.
- Ausführen von Tests an Mutanten.

Oben sehen Sie ein Beispiel für das Starten von Stryker:
- Stryker startet;
- liest eine Konfiguration;
- lädt die notwendigen Abhängigkeiten;
- findet Dateien, die mutieren;
- führt Tests für den Quellcode durch;
- erzeugt 152 Mutanten;
- führt Tests in 8 Threads aus (in diesem Fall basierend auf der Anzahl der CPU-Kerne).
Dies ist kein schneller Vorgang, daher ist es besser, ihn auf einigen CI / CD-Servern durchzuführen.
Nach Bestehen aller Tests gibt Stryker einen kurzen Bericht über die Dateien mit der Anzahl der erstellten, getöteten und überlebenden Mutanten sowie dem Prozentsatz des Verhältnisses von getöteten Mutanten zu Überlebenden (MSI) und den verwendeten Mutatoren.
Dies sind potenzielle Probleme, die in unseren Tests nicht vorhergesehen wurden.
Zusammenfassend
Mutationstests sind nützlich und interessant . Es kann Probleme in den frühen Phasen des Testens und ohne die Teilnahme von Menschen finden. Dies verkürzt beispielsweise die Zeit für die Validierung von Pull-Anforderungen, da qualifizierte Entwickler keine Zeit für die Validierung von Pull-Anforderungen aufwenden müssen, die bereits potenzielle Probleme aufweist. Oder speichern Sie die Produktion, wenn Sie am Freitagabend eine neue Version vorbereiten möchten.
Stryker ist ein flexibles Multithread-Mutationstest-Tool. Es entwickelt sich aktiv, ist aber bisher feucht und hat die Hauptversion noch nicht erreicht. Während der Erstellung dieses Berichts haben die Entwickler beispielsweise im Plugin für Babel endlich die Möglichkeit gegeben, die Konfigurationsdatei anzugeben und die Jest-Integration zu korrigieren. Dies ist ein OpenSource-
Projekt , dessen Entwicklung unterstützt werden kann.
FAQ- Wie teste ich Mutationstests? Sicher gibt es auch einen Fehler. Im ersten Unit-Test-Beispiel betrug die Abdeckung 90%. Es scheint, dass alles in Ordnung ist, aber dennoch rutschten die Fälle durch, als alles fiel und in Flammen stand. Warum sollte es dementsprechend das Gefühl geben, dass nach diesen Mutationstests alles in Ordnung ist?- Ich sage nicht, dass Mutationstests eine Silberkugel sind und alles heilen werden. Natürlich kann es einige grenzwertig verrückte Fälle geben oder das Fehlen einer Art Mutator. Erstens sind typische Fehler leicht zu erkennen. Zum Beispiel haben Sie das Alter überprüft, auf <18 gesetzt (es war notwendig <=) und im Test vergessen, eine Grenzfallprüfung durchzuführen. Sie haben einen weiteren Vergleich mit dem Mutator durchgeführt, und als Ergebnis ist der Test gefallen (oder nicht gefallen), und Sie verstehen, dass alles gut oder alles schlecht ist. Solche Dinge werden schnell gefangen. Auf diese Weise können Sie Tests einfach korrekt anhängen und fehlende Punkte finden.
- Oft haben Sie eine Situation von "fassungslos und verlassen"? Ich denke das ist nicht wahr.- Nein, aber ich denke, dass es in vielen Projekten solche Dinge gibt. Das stimmt natürlich nicht. Viele Leute denken, dass die Codeabdeckung hilft, alles zu überprüfen, Sie können sicher gehen und sich keine Sorgen machen - aber das ist nicht so.
- Ich werde sofort sagen, wo das Problem liegt. Wir haben viele Arten von Reduzierern und andere Dinge, die wir auf Mutationsbasis testen, und es gibt viele davon. Dies alles wächst und es stellt sich heraus, dass für jede Pull-Anforderung Mutationstests gestartet werden, was viel Zeit in Anspruch nimmt. Ist es möglich, nur auf dem zu laufen, was sich geändert hat?"Ich denke, Sie können es selbst konfigurieren." Auf der Entwicklerseite können Sie beispielsweise beim Pushing und Commit ein
fusselfreies Plugin erstellen, das nur Dateien ausführt, die sich geändert haben. Auf CI / CD ist dies ebenfalls möglich. In unserem Fall ist das Projekt sehr groß und alt, und wir üben Stichproben. Wir überprüfen nicht alles, da es eine Woche dauern wird, wird es Hunderttausende von Mutationen geben. Ich würde empfehlen, Stichproben durchzuführen oder selbst einen selektiven Startvorgang zu organisieren. Ich habe kein fertiges Werkzeug für eine solche Integration gesehen.
- Ist die Vollständigkeit aller möglichen Mutationen für einen bestimmten Code gewährleistet? Wenn nicht, wie genau werden Mutationen ausgewählt?- Ich persönlich habe nicht nachgesehen, aber ich habe auch keine Probleme damit festgestellt. Stryker muss alle möglichen Mutationen auf demselben Code generieren.
- Ich möchte nach Schnappschüssen fragen. Mein Unit-Test testet sowohl die Logik als auch das Layout der Snapshot-Reaktionskomponente. Wenn ich ein logisches Design ändere, ändert sich natürlich genau dort mein Layout. Das ist erwartetes Verhalten, nicht wahr?- Ja, das bedeutet, dass Sie Snapshots manuell aktualisieren.
- Also ignorieren Sie irgendwie Schnappschüsse in diesem Bericht?- Höchstwahrscheinlich müssen Schnappschüsse im Voraus aktualisiert und anschließend Mutationstests durchgeführt werden, da sonst viel Müll von Stryker kommt.
- Frage zu CI-Servern. Für einfache Komponententests gibt es unter GitLab Reporter für alles, was Sie möchten, die den Prozentsatz der erfolgreichen Tests anzeigen, die Sie konfigurieren können, ob sie fehlschlagen oder nicht. Was ist mit Stryker? Das Tablet wird nur in der Konsole angezeigt. Was kann ich als Nächstes tun?- Sie haben HTML-Reporter, Sie können Ihre eigenen Reporter erstellen - alles ist flexibel anpassbar. Vielleicht gibt es einige spezifische Tools, aber da wir immer noch Punktmutationstests durchführen, habe ich keine spezifischen Integrationen mit TeamCity und ähnlichen CI / CD-Tools gefunden.
- Inwieweit erhöhen Mutationstests die Unterstützung für die Tests, die Sie im Allgemeinen haben? Das heißt, Tests sind ein Schmerz, und Tests müssen neu geschrieben werden, wenn der Code neu geschrieben wird usw. Manchmal ist es einfacher, den Code neu zu schreiben als Tests. Und hier habe ich auch Mutationstests. Wie teuer ist es für ein Unternehmen?- Erstens werde ich wahrscheinlich korrigieren, dass das Umschreiben von Code zum Zwecke von Tests falsch ist. Der Code sollte leicht zu testen sein. Für das, was abgeschlossen werden muss, ist es für das Unternehmen erneut wichtig, dass die Tests so vollständig und effektiv wie möglich sind. Wenn sie nicht vollständig sind, bedeutet dies, dass ein Fehler auftreten kann, der Verluste verursacht. Natürlich können Sie nur die wichtigsten Teile für das Geschäft testen.
"Trotzdem, wie viel teurer wird es, wenn Mutationstests erscheinen, als wenn sie nicht da wären."- Es sind jetzt genauso schlechte Tests. Wenn die Tests jetzt schlecht geschrieben sind, müssen Sie viel hinzufügen. Bei Mutationstests werden Fälle gefunden, die nicht durch Tests abgedeckt sind.
- Auf der Folie mit den Ergebnissen des Stryker-Checks gibt es viele Vorings, sie sind kritisch oder nicht kritisch. Wie gehe ich mit Fehlalarmen um?- Die subtile Frage ist, was als falsch angesehen wird. Ich habe die Jungs in unserem Team gefragt, welche interessanten Dinge mit ihnen passiert sind. Es gab ein Beispiel für den Fehlertext. Stryker berichtete, dass die Tests nicht auf die Änderung des Fehlertextes reagierten. Es scheint ein Pfosten zu sein, aber klein.
- Sie sehen solche Fehler und überspringen unkritische Fehler im manuellen Modus?"Wir haben eine Stichprobe, also ja."
- Ich habe eine praktische Frage. Wie viel Prozent der Tests haben Sie bei der Implementierung nicht bestanden?- Wir haben es nicht für das gesamte Projekt implementiert, aber es gab kleinere Probleme für das neue Projekt. Daher kann ich die genauen Zahlen nicht sagen, aber im Allgemeinen hat der Ansatz die Situation definitiv verbessert.
Weitere, ebenso nützliche Front-End-Performances finden Sie auf unserem Youtube-Kanal . Alle thematischen Berichte aller unserer Konferenzen werden nach und nach veröffentlicht. Oder abonnieren Sie den Newsletter und wir halten Sie über alle neuen Materialien und Neuigkeiten zukünftiger Konferenzen auf dem Laufenden.