Wenn Sie eine kleine Liste typischer Fehler beherrschen, die beim Schreiben von Komponententests auftreten, können Sie sie sogar gerne schreiben. Heute wird der Leiter der Yandex.Browser-Entwicklungsgruppe für Android Konstantin
kzaikin Zaikin seine Erfahrungen mit Habr-Lesern teilen.
- Ich habe einen praktischen Bericht. Ich hoffe, dass es Ihnen allen zugute kommt - denen, die bereits Unit-Tests schreiben, und denen, die nur an das Schreiben denken, und denen, die es versuchen und denen es nicht gelungen ist.
Wir haben ein ziemlich großes Projekt. Eines der größten mobilen Projekte in Russland. Wir haben viel Code, viele Tests. Tests werden bei jeder Poolanforderung verfolgt, sie fallen nicht gleichzeitig ab.
Wer weiß, welche Testabdeckung er im Projekt hat? Null, okay. Wer hat Unit-Tests im Projekt? Und wer glaubt, dass Unit-Tests nicht benötigt werden? Daran sehe ich nichts auszusetzen, es gibt Menschen, die aufrichtig davon überzeugt sind, und meine Geschichte sollte ihnen helfen, davon überzeugt zu sein.
Glücklicherweise Tausende von grünen Tests - wir kamen nicht sofort. Es gibt keine Silberkugel und die Hauptidee meines Berichts auf dem Bildschirm:

Das chinesische Sprichwort steht in Hieroglyphen, dass eine Reise von ungefähr tausend mit einem Schritt beginnt. Es scheint, dass es ein solches Analogon zu diesem Sprichwort gibt.
Wir haben vor langer Zeit entschieden, dass wir unser Produkt und unseren Code verbessern müssen, und wir gehen gezielt darauf zu. Auf diesem Weg trafen wir viele Unebenheiten, einen Unterwasserschwader, und sammelten zusammen mit diesen einige Überzeugungen.

Warum brauchen wir Tests?
Damit alte Funktionen nicht fallen, wenn wir neue einführen. Ein Abzeichen auf GitHub haben. Um vorhandene Funktionen umzugestalten - ein tiefer Gedanke, muss er denjenigen offenbart werden, die keine Tests schreiben. Damit vorhandene Funktionen beim Refactoring nicht ausfallen, schützen wir uns mit Tests. Damit der Chef eine Poolanfrage sendet, ja.
Meine Meinung - bitte verbinden Sie sie nicht mit der Meinung meines Teams - dass die Tests uns helfen. Mit ihnen können Sie Ihren Code ausführen, ohne ihn in Produktion zu bringen, ohne ihn auf Geräten zu installieren. Sie starten ihn und führen ihn sehr schnell aus. Sie können alle Eckfälle weglaufen lassen, die Sie im Leben auf dem Gerät und in der Produktion nicht bekommen, und Ihr Tester wird sie nicht finden. Aber Sie als Entwickler werden sie erfinden, überprüfen und Fehler frühzeitig beheben.
Sehr wichtig: Tests zeigen, wie der Code laut Entwickler funktionieren sollte und was Ihre Methoden laut Entwickler tun sollten. Dies sind keine Kommentare, die wegfahren und nach einer Weile von nützlichen schädlich werden. Es kommt vor, dass in den Kommentaren eine Sache geschrieben ist und im Code völlig anders. Unit Tests in diesem Sinne können nicht lügen. Wenn der Test grün ist, dokumentiert er, was dort passiert. Der Test ist fehlgeschlagen - Sie haben die primäre Absicht des Entwicklers verletzt.
Verträge festlegen. Hierbei handelt es sich nicht um unterzeichnete und abgestempelte Verträge, sondern um Softwareverträge für das Klassenverhalten. Wenn Sie umgestalten, werden in diesem Fall die Verträge verletzt und die Tests fallen, wenn Sie sie brechen. Wenn die Verträge gespeichert werden, bleiben die Tests grün, und Sie haben mehr Vertrauen, dass Ihr Refactoring korrekt ist.

Dies ist die allgemeine Idee meines gesamten Berichts. Sie können die erste Zeile anzeigen und verlassen.
Viele Leute denken, dass der Testcode so lala Code ist, er ist nicht für die Produktion, also können Sie ihn so lala schreiben. Ich bin damit nicht einverstanden und denke, dass die Tests zunächst verantwortungsbewusst angegangen werden sollten, ebenso wie der Produktionscode. Wenn Sie sich ihnen auf die gleiche Weise nähern, profitieren Sie von den Tests. Andernfalls wird es ein Schmutz sein.
Insbesondere beziehen sich die beiden folgenden Zeilen anscheinend auf jeden Code.
KISS - halte es einfach, dumm. Keine Notwendigkeit zu komplizieren. Tests sollten einfach sein. Und der Produktionscode sollte einfach sein, aber die Tests sind besonders. Wenn Sie Tests haben, die leicht zu lesen sind, dann sind dies Tests, die höchstwahrscheinlich gut geschrieben sind, gut ausgedrückt sind und leicht zu testen sind. Selbst während der Poolanfrage wird eine Person, die sich Ihre brandneuen Tests ansieht, verstehen, was Sie sagen wollten. Und wenn etwas kaputt geht, können Sie leicht verstehen, was passiert ist.
DRY - wiederhole dich nicht. In Tests neigt der Entwickler häufig dazu, die verbotene Technik zu verwenden, die niemand in der Produktion zu verwenden scheint - Kopieren und Einfügen. Bei der Produktion eines Entwicklers, der aktiv kopiert und einfügt, werden sie es einfach nicht verstehen. In Tests ist dies leider üblich. Keine Notwendigkeit, dies zu tun, weil - die erste Zeile. Wenn Sie die Tests ehrlich schreiben, wie wirklich guten Code, sind die Tests für Sie nützlich.
Während wir Hunderttausende von Codezeilen entwickelten, Tausende von Tests schrieben und Rechen sammelten, hatte ich typische Kommentare zu den Tests gesammelt. Ich bin ziemlich faul und als ich zu den Pool-Anfragen ging und die gleichen Fehler beobachtete, basierend auf dem DRY-Prinzip, habe ich beschlossen, diese typischen Probleme aufzuschreiben. Ich habe es zuerst im internen Wiki gemacht und dann praktische Testgerüche auf GitHub gepostet, denen Sie folgen können wenn Sie Tests schreiben.

Ich werde nach Punkten auflisten. Erhöhen Sie einen Zähler in Ihrem Kopf, wenn Sie sich an einen solchen Testgeruch erinnern. Wenn Sie bis fünf zählen, können Sie Ihre Hand heben und "Bingo!" Schreien. Und am Ende frage ich mich, wer wie viel gezählt hat. Mein Zähler entspricht der Anzahl der Punkte, ich habe sie alle selbst gesammelt.

Das Schwierigste an der Programmierung, das Sie kennen. Und in Tests ist das wirklich wichtig. Wenn Sie den Test nicht gut benennen, können Sie höchstwahrscheinlich nicht formulieren, was der Test überprüft.
Menschen sind ziemlich einfache Wesen, sie sind leicht in Namen gefangen. Deshalb bitte ich Sie, Tests gut anzurufen. Formulieren Sie einen Test, um einfache Regeln zu überprüfen und zu befolgen.
no_action_or_assertion
Wenn der Name des Tests keine Beschreibung dessen enthält, was der Test überprüft, haben Sie beispielsweise die Controller-Klasse und schreiben den testController-Test. Was überprüfen Sie? Was soll dieser Test tun? Höchstwahrscheinlich entweder nichts oder zu viele Dinge, um sie zu überprüfen. Weder der eine noch der andere passt zu uns. Daher ist es notwendig, im Namen des Tests zu schreiben, was wir prüfen.
langer_name
Sie können nicht zum anderen Extrem gehen. Der Name des Tests sollte kurz genug sein, damit eine Person ihn leicht analysieren kann. In diesem Sinne ist Kotlin großartig, da Sie damit Testnamen in Anführungszeichen mit Leerzeichen in normalem Englisch schreiben können. Sie sind leichter zu lesen. Trotzdem riechen lange Namen.
Wenn Ihr Testname zu lang ist, haben Sie höchstwahrscheinlich zu viele Testmethoden in eine Testklasse eingefügt, und Sie müssen klarstellen, was Sie überprüfen. In diesem Fall müssen Sie Ihre Testklasse in mehrere aufteilen. Keine Angst davor. Sie haben einen Testklassennamen, der den Namen Ihres Produktionscodes überprüft, und es gibt kurze Testnamen.
old_prefix
Das ist Atavismus. Zuvor testeten alle in Java mit JUnit, wo bis zur vierten Version vereinbart wurde, dass Testmethoden mit dem Wort test beginnen sollten. Es ist so passiert, jeder nennt es immer noch so. Aber es gibt ein Problem, im Englischen ist das Wort test das Verb „check“. Menschen sind leicht in dieser Falle gefangen und schreiben keine anderen Verben mehr. Schreiben Sie testController. Es ist einfach, sich selbst zu überprüfen: Wenn Sie kein Verb geschrieben haben, was Ihre Testklasse tun sollte, haben Sie höchstwahrscheinlich etwas nicht überprüft, Sie haben es nicht gut genug geschrieben. Deshalb bitte ich Sie immer, das Wort test aus den Namen der Testmethoden zu entfernen.
Ich erzähle sehr einfache Dinge, aber seltsamerweise helfen sie. Wenn die Tests gut aufgerufen werden, sehen sie höchstwahrscheinlich unter der Haube gut aus. Es ist sehr einfach.

Ich habe tatsächlich Testgeruchs-IDs wie auf GitHub gelesen. Der Link ist unten, Sie können gehen und verwenden.
multiple_asserts
In der Testmethode gibt es viele Aussagen. Also vielleicht oder nicht? Kann sein. Ist es gut oder schlecht? Ich finde das sehr schlecht. Wenn Sie mehrere Aussagen in eine Testmethode geschrieben haben, überprüfen Sie mehrere Aussagen. Wenn Sie Ihren Test testen und die erste Behauptung fällt, erreicht der Test die zweite Behauptung? Wird nicht erreichen. Sie erhalten bereits nach dem Sturz Ihrer Baugruppe irgendwo auf dem CI, dass der Test gefallen ist, gehen Sie, reparieren Sie etwas, füllen Sie es erneut, es wird bei der nächsten Behauptung fallen. Es könnte sehr gut sein.
In diesem Fall wäre es viel cooler, wenn Sie diese Testmethode in mehrere zerlegen würden und alle Methoden mit mehreren Asserts gleichzeitig fallen würden, da sie unabhängig voneinander gestartet würden.
Einige weitere Zusicherungen können die verschiedenen Aktionen maskieren, die mit der Testklasse ausgeführt werden. Ich empfehle, einen Test zu schreiben - einen Assert. Vermögenswerte können sehr kompliziert sein. Mein Kollege hat im allerersten Bericht einen Code demonstriert, in dem er die hervorragende assertThat-Konstruktion und den Matcher verwendet hat. Ich liebe Matchups bei JUnit sehr, also kannst du das auch nutzen. Für den Testleser ist dies nur eine kurze Aussage. GitHub hat Beispiele für all diese Gerüche und wie man sie behebt. Es gibt ein Beispiel für schlechten Code und guten Code. Dies alles geschieht in Form eines Projekts, das Sie herunterladen, öffnen, kompilieren und alle Tests ausführen können.
many_tests_in_one
Der nächste Geruch ist eng mit dem vorherigen verwandt. Sie machen etwas mit dem System - Sie machen eine Behauptung. Etwas anderes mit dem System machen, einige lange Operationen - eine Behauptung machen - etwas anderes machen. Tatsächlich haben Sie einfach mehrere Methoden untersucht und erhalten solide, gute Testmethoden.
repeating_setup
Dies bezieht sich auf Ausführlichkeit. Wenn Sie eine Testklasse haben und jede Testmethode zu Beginn dieselben Methoden ausführt.
Eine Testklasse, in der zu Beginn dieselben Methoden ausgeführt werden. Dies scheint ein bisschen, aber in jeder Testmethode ist dieser Müll vorhanden. Und wenn es allen Testmethoden gemeinsam ist, ziehen Sie es in JUnit 5 in den Konstruktor oder
Before- Block oder
Before Each-Block. Wenn Sie dies tun, verbessert sich die Lesbarkeit jeder Methode und Sie werden DRY sin los. Solche Tests sind leichter zu warten und leichter zu lesen.

Die Zuverlässigkeit der Tests ist sehr wichtig. Es gibt Anzeichen, anhand derer festgestellt werden kann, dass der Test weint, grün oder rot ist. Wenn der Entwickler es schreibt, ist er sicher, dass es grün ist, und dann werden die Tests aus irgendeinem Grund grün oder rot, was uns im Allgemeinen Schmerzen und Unsicherheit darüber gibt, dass die Tests nützlich sind. Wir sind uns bei den Tests nicht sicher, was bedeutet, dass wir nicht sicher sind, ob sie nützlich sind.
zufällig
Ich selbst habe einmal Tests geschrieben, in denen Math.random () enthalten war, Zufallszahlen gemacht und etwas damit gemacht. Keine Notwendigkeit, dies zu tun. Wir erwarten, dass das Testsystem in derselben Konfiguration in das Testsystem eintritt und dass die Ausgabe von diesem auch dieselbe sein muss. Daher müssen Sie beispielsweise bei Komponententests niemals Vorgänge mit dem Netzwerk ausführen. Da der Server möglicherweise nicht antwortet, kann es zu unterschiedlichen Zeiten kommen, etwas anderes.
Wenn Sie einen Test benötigen, der mit dem Netzwerk funktioniert, führen Sie einen lokalen Proxy durch, aber gehen Sie auf keinen Fall zu einem echten Netzwerk. Dies ist der gleiche Zufall. Und natürlich können Sie keine zufälligen Daten verwenden. Wenn Sie etwas tun müssen, machen Sie einige Beispiele mit Randbedingungen, mit schlechten Bedingungen, aber sie sollten fest codiert sein.
tread_sleep
Ein klassisches Problem, mit dem Entwickler konfrontiert sind, wenn sie versuchen, asynchronen Code zu testen. Es ist so, dass ich im Test etwas gemacht habe und dann warten muss, bis es abgeschlossen ist. Wie macht man? Thread.sleep () natürlich.
Es gibt ein Problem. Wenn Sie Ihren Test entwickelt haben, haben Sie ihn beispielsweise auf einer Ihrer Schreibmaschinen durchgeführt. Er funktioniert mit einer gewissen Geschwindigkeit. Sie führen die Tests auf einem anderen Computer aus. Und was passiert, wenn Ihr System während der Thread.sleep () -Zeit nicht funktioniert? Der Test wird rot. Das ist unerwartet. Daher wird hier empfohlen, wenn Sie asynchrone Vorgänge ausführen, diese überhaupt nicht zu testen. Fast jede asynchrone Operation kann bereitgestellt werden, sodass Sie über einen bedingten Mechanismus verfügen, der eine asynchrone Operation und einen synchron ausgeführten Codeblock bereitstellt. Beispielsweise verfügt AsyncTask im Inneren über einen synchron ausgeführten Codeblock. Sie können es problemlos synchron und ohne Asynchronität testen. Es ist nicht erforderlich, AsyncTask selbst zu testen. Es handelt sich um eine Framework-Klasse. Warum sollten Sie es testen? Halte es fest und dein Leben wird einfacher.
Thread.sleep () ist sehr schmerzhaft. Zusätzlich zu der Tatsache, dass es die Zuverlässigkeit von Tests verschlechtert, da es ihnen ermöglicht, aufgrund unterschiedlicher Timings auf Geräten zu weinen, verlangsamt es auch die Ausführung Ihrer Tests. Wer möchte, dass seine Unit-Tests, die in Millisekunden ausgeführt werden sollen, fünf Sekunden lang ausgeführt werden, weil ich den Profilschlaf eingestellt habe?
modify_global
Es ist typisch, dass wir zu Beginn des Tests eine globale statische Variable geändert haben, um zu überprüfen, ob unser System ordnungsgemäß funktioniert, aber am Ende nicht zurückgekehrt sind. Dann kommt eine coole Situation: Auf dem Computer führte der Entwickler die Tests in einer Sequenz aus, überprüfte zuerst die globale Variable mit dem Standardwert, dann änderte er sie im Test und tat dann etwas anderes. Beide Tests sind grün. Und auf CI begannen die Tests in umgekehrter Reihenfolge. Und einer oder beide Tests sind rot, obwohl sie alle grün waren.
Sie müssen nach sich selbst aufräumen. Scout-Regeln in diesem Sinne: Die globale Variable wurde geändert - in den ursprünglichen Zustand zurückkehren. Besser noch, stellen Sie sicher, dass globale Staaten nicht verwendet werden. Aber das ist ein tieferer Gedanke. Es geht um die Tatsache, dass Tests manchmal Fehler in der Architektur aufzeigen. Wenn wir globale Zustände ändern und sie in ihren ursprünglichen Zustand zurückversetzen müssen, um Tests zu schreiben, sind wir alle in unserer Architektur gut? Brauchen wir zum Beispiel wirklich globale Variablen? In der Regel können Sie auf sie verzichten, indem Sie einige Kontextklassen oder ähnliches einfügen, sodass Sie sie jedes Mal im Test neu initialisieren, einfügen und neu initialisieren können.
@VisibleForTesting
Testgeruch für Fortgeschrittene. Die Notwendigkeit, so etwas zu benutzen, entsteht in der Regel nicht am ersten Tag. Sie haben bereits etwas getestet und mussten dann die Klasse in einen bestimmten Zustand übersetzen. Und du machst dir eine Hintertür. Sie haben eine Produktionsklasse und erstellen eine bestimmte Methode, die in der Produktion niemals aufgerufen wird. Dadurch injizieren Sie etwas in die Klasse oder ändern ihren Status. Somit wird die Kapselung böswillig unterbrochen. In der Produktion funktioniert Ihre Klasse irgendwie, aber in Tests ist es tatsächlich eine andere Klasse, Sie kommunizieren mit ihr über andere Ein- und Ausgänge. Und hier können Sie eine Situation bekommen, in der Sie die Produktion ändern, aber die Tests bemerken es nicht. Tests gehen weiterhin durch die Hintertür und haben nicht bemerkt, dass beispielsweise Ausnahmen im Konstruktor ausgelöst wurden, da sie einen anderen Konstruktor durchlaufen.
Im Allgemeinen sollten Sie Ihre Klassen mit denselben Ein- und Ausgängen wie in der Produktion testen. Es sollte keinen Zugriff auf Methoden nur für Tests geben.

Wie viele unserer 15.000 Tests werden durchgeführt? Bei jeder Poolanforderung in Team City müssen Entwickler etwa 20 Minuten warten. Nur weil 15 Tausend viele Tests sind. Und in diesem Abschnitt habe ich Gerüche zusammengestellt, die Tests verlangsamen. Obwohl thread_sleep schon da war.
unnötiger_android_test
Android hat Instrumentierungstests, sie sind wunderschön, sie laufen auf einem Gerät oder Emulator. Dies wird Ihr Projekt wirklich vollständig anheben, aber sie sind sehr langsam. Und für sie müssen Sie sogar einen ganzen Emulator erhöhen. Selbst wenn Sie sich vorstellen, dass Sie einen erhöhten Emulator auf CI haben - es stimmt nur so überein, dass Sie einen haben -, dauert das Ausführen des Tests auf dem Emulator viel länger als auf dem Host-Computer, beispielsweise mit Robolectric. Obwohl es andere Methoden gibt. Dies ist ein solches Framework, mit dem Sie mit Klassen aus dem Android-Framework auf dem Host-Computer in reinem Java arbeiten können. Wir nutzen es ziemlich aktiv. Früher war Google etwas cool, aber jetzt sprechen Googler selbst in verschiedenen Berichten darüber. Es wird für die Verwendung empfohlen.
unnötig_robolektrisch
Das Android-Framework von Robolectric wird emuliert. Es ist dort nicht vollständig, obwohl die Implementierung umso weiter, je vollständiger sie ist. Es ist fast echtes Android und läuft nur auf Ihrem Desktop, Laptop oder CI. Es muss aber auch nicht überall eingesetzt werden. Robolectric ist nicht kostenlos. Wenn Sie einen Test haben, den Sie heldenhaft von der Android-Instrumentierung auf Robolectric übertragen haben, sollten Sie sich überlegen: Gehen Sie vielleicht noch weiter, entfernen Sie Robolectric und machen Sie ihn zum einfachsten JUnit-Test. Robolektrische Tests benötigen Zeit zum Initialisieren, zum Laden von Ressourcen, zum Initialisieren Ihrer Aktivität, Anwendung und alles andere. Es dauert einige Zeit. Dies ist keine Sekunde, es sind Millisekunden, manchmal Zehn und Hunderte. Aber wenn es viele Tests gibt, ist auch das wichtig.
Es gibt Techniken, die Robolectric loswerden. Sie können Ihren Code über Schnittstellen isolieren, indem Sie den gesamten Plattformteil mit Schnittstellen umschließen. Dann gibt es nur noch einen JUnit-Host-Test. JUnit auf dem Host-Computer ist sehr schnell, es gibt einen minimalen Overhead, solche Tests können zu Tausenden und Zehntausenden ausgeführt werden, sie werden eine Minute, einige Minuten ausgeführt. , , , Android instrumentation-, . .
. smells? .
, .
