Monolith für Hunderte von Client-Versionen: Wie wir Tests schreiben und unterstützen



Hallo allerseits!

Ich bin ein Backend-Entwickler im Badoo-Serverteam. Auf der HighLoad-Konferenz im letzten Jahr habe ich eine Präsentation gehalten , deren Textversion ich mit Ihnen teilen möchte. Dieser Beitrag ist besonders nützlich für diejenigen, die selbst Tests für das Backend schreiben und Probleme beim Testen von Legacy-Code haben, sowie für diejenigen, die komplexe Geschäftslogik testen möchten.

Worüber werden wir reden? Zunächst werde ich kurz auf unseren Entwicklungsprozess eingehen und wie er sich auf unseren Testbedarf und den Wunsch auswirkt, diese Tests zu schreiben. Dann werden wir die Pyramide der Testautomatisierung auf und ab gehen, die Arten von Tests diskutieren, die wir verwenden, über die Werkzeuge in jedem von ihnen sprechen und welche Probleme wir mit ihrer Hilfe lösen. Überlegen Sie sich am Ende, wie Sie all diese Dinge warten und ausführen können.

Unser Entwicklungsprozess


Wir haben unseren Entwicklungsprozess veranschaulicht:


Ein Golfer ist ein Backend-Entwickler. Irgendwann kommt eine Entwicklungsaufgabe auf ihn zu, normalerweise in Form von zwei Dokumenten: Anforderungen von der Geschäftsseite und ein technisches Dokument, das die Änderungen in unserem Interaktionsprotokoll zwischen dem Backend und den Kunden (mobile Anwendungen und die Site) beschreibt.

Der Entwickler schreibt den Code und setzt ihn früher als alle Clientanwendungen in Betrieb. Alle Funktionen werden durch einige Funktionsflags oder A / B-Tests geschützt. Dies ist in einem technischen Dokument vorgeschrieben. Danach werden gemäß den aktuellen Prioritäten und der Produkt-Roadmap Client-Anwendungen freigegeben. Für uns Backend-Entwickler ist es völlig unvorhersehbar, wann eine bestimmte Funktion auf Clients implementiert wird. Der Release-Zyklus für Client-Anwendungen ist etwas komplizierter und länger als bei uns, sodass unsere Produktmanager buchstäblich Prioritäten setzen.

Die vom Unternehmen übernommene Entwicklungskultur ist von großer Bedeutung: Der Backend-Entwickler ist vom Zeitpunkt der Implementierung im Backend bis zur letzten Integration auf der letzten Plattform, auf der das Feature ursprünglich geplant war, für das Feature verantwortlich.

Diese Situation ist durchaus möglich: Vor sechs Monaten haben Sie einige Funktionen eingeführt, Kundenteams haben sie lange Zeit nicht implementiert, weil sich die Prioritäten des Unternehmens geändert haben, Sie bereits mit anderen Aufgaben beschäftigt sind, Sie neue Fristen und Prioritäten haben - und hier kommen Ihre Kollegen und Sie sagen: „Erinnerst du dich an das Ding, das du vor sechs Monaten heruntergespült hast? Sie arbeitet nicht". Und anstatt sich neuen Aufgaben zu widmen, löschen Sie die Feuer.



Daher haben unsere Entwickler eine für PHP-Programmierer ungewöhnliche Motivation - sicherzustellen, dass während der Integrationsphase so wenig Probleme wie möglich auftreten.

Was möchten Sie zuerst tun, um sicherzustellen, dass die Funktion funktioniert?

Das erste, was mir in den Sinn kommt, ist natürlich die Durchführung manueller Tests. Sie holen die Anwendung ab, aber sie weiß nicht wie - da die Funktion neu ist, werden sich die Kunden in sechs Monaten darum kümmern. Nun, manuelle Tests geben keine Garantie dafür, dass für die Zeit, die von der Veröffentlichung des Backends bis zum Beginn der Integration vergeht, niemand auf den Clients etwas kaputt macht.

Und hier helfen uns automatisierte Tests.

Unit-Tests


Die einfachsten Tests, die wir schreiben, sind Unit-Tests. Wir verwenden PHP als Hauptsprache für das Backend und PHPUnit als Framework für Unit-Tests. Mit Blick auf die Zukunft werde ich sagen, dass alle unsere Backend-Tests auf der Grundlage dieses Frameworks geschrieben wurden.

Unit-Tests Wir behandeln meistens einige kleine isolierte Codeteile, überprüfen die Leistung von Methoden oder Funktionen, dh wir sprechen von winzigen Einheiten der Geschäftslogik. Unsere Unit-Tests sollten mit nichts interagieren, auf Datenbanken oder Dienste zugreifen.

Softmocks


Die Hauptschwierigkeit, mit der Entwickler beim Schreiben von Komponententests konfrontiert sind, ist nicht testbarer Code, und dies ist normalerweise Legacy-Code.

Ein einfaches Beispiel. Badoo ist 12 Jahre alt, es war einmal ein sehr kleines Startup, das von mehreren Leuten entwickelt wurde. Der Start war ohne Tests recht erfolgreich. Dann wurden wir groß genug und stellten fest, dass man ohne Tests nicht leben kann. Aber zu diesem Zeitpunkt war viel Code geschrieben worden, der funktionierte. Schreiben Sie es nicht nur zum Testen um! Das wäre aus geschäftlicher Sicht nicht sehr vernünftig.

Aus diesem Grund haben wir eine kleine Open-Source-Bibliothek SoftMocks entwickelt , die das Schreiben von Tests billiger und schneller macht. Es fängt alle Include / Require-PHP-Dateien ab und ersetzt die Quelldatei im laufenden Betrieb durch geänderten Inhalt, dh umgeschriebenen Code. Auf diese Weise können wir Stubs für jeden Code erstellen. Es beschreibt, wie die Bibliothek funktioniert.

So sieht es für einen Entwickler aus:

//mock  \Badoo\SoftMocks::redefineConstant($constantName, $newValue); //mock  : , ,  \Badoo\SoftMocks::redefineMethod( $class, $method, $method_args, $fake_code ); //mock  \Badoo\SoftMocks::redefineFunction( $function, $function_args, $fake_code ); 

Mit Hilfe solch einfacher Konstruktionen können wir alles, was wir wollen, global neu definieren. Sie ermöglichen es uns insbesondere, die Einschränkungen des Standard-PHPUnit-Herstellers zu umgehen. Das heißt, wir können statische und private Methoden verspotten, Konstanten neu definieren und viel mehr tun, was mit gewöhnlicher PHPUnit unmöglich ist.

Wir sind jedoch auf ein Problem gestoßen: Entwicklern scheint es bei SoftMocks nicht erforderlich zu sein, den getesteten Code zu schreiben. Sie können den Code jederzeit mit unseren globalen Mocks „kämmen“, und alles wird gut funktionieren. Dieser Ansatz führt jedoch zu komplexerem Code und zur Anhäufung von "Krücken". Aus diesem Grund haben wir verschiedene Regeln verabschiedet, die es uns ermöglichen, die Situation unter Kontrolle zu halten:

  1. Jeder neue Code sollte leicht mit Standard-PHPUnit-Mocks getestet werden können. Wenn diese Bedingung erfüllt ist, ist der Code testbar und Sie können einfach ein kleines Stück auswählen und nur es testen.
  2. SoftMocks können mit altem Code verwendet werden, der so geschrieben ist, dass er nicht für Unit-Tests geeignet ist, sowie in Fällen, in denen es zu teuer / lang / schwierig ist, etwas anderes zu tun (betonen Sie das Notwendige).

Die Einhaltung dieser Regeln wird in der Phase der Codeüberprüfung sorgfältig überwacht.

Mutationstests


Separat möchte ich auf die Qualität von Unit-Tests eingehen. Ich denke, viele von Ihnen verwenden Metriken wie die Codeabdeckung. Leider beantwortet sie keine Frage: "Habe ich einen guten Unit-Test geschrieben?" Es ist möglich, dass Sie einen solchen Test geschrieben haben, der eigentlich nichts überprüft, keine einzige Zusicherung enthält, aber eine hervorragende Codeabdeckung erzeugt. Natürlich ist das Beispiel übertrieben, aber die Situation ist nicht so weit von der Realität entfernt.

Vor kurzem haben wir begonnen, Mutationstests einzuführen. Dies ist ein ziemlich altes, aber nicht sehr bekanntes Konzept. Der Algorithmus für solche Tests ist recht einfach:

  • Nehmen Sie den Code und die Codeabdeckung.
  • parsim und beginne den Code zu ändern: wahr zu falsch,> zu> =, + zu - (im Allgemeinen Schaden in jeder Hinsicht);
  • Führen Sie für jede solche Mutationsänderung Testsuiten aus, die die geänderte Zeichenfolge abdecken.
  • Wenn die Tests fallen, sind sie gut und erlauben uns wirklich nicht, den Code zu brechen.
  • Wenn die Tests bestanden wurden, sind sie höchstwahrscheinlich trotz der Abdeckung nicht effektiv genug, und es kann sich lohnen, sie genauer zu betrachten, um eine Aussage zu treffen (oder es gibt einen Bereich, der nicht vom Test abgedeckt wird).

Es gibt mehrere vorgefertigte Frameworks für PHP, wie z. B. Humbug und Infection. Leider passten sie nicht zu uns, da sie mit SoftMocks nicht kompatibel sind. Aus diesem Grund haben wir unser eigenes kleines Konsolendienstprogramm geschrieben, das dasselbe tut, jedoch unser internes Codeabdeckungsformat verwendet und mit SoftMocks befreundet ist. Jetzt startet der Entwickler es manuell und analysiert die von ihm geschriebenen Tests, aber wir arbeiten daran, das Tool in unseren Entwicklungsprozess einzuführen.

Integrationstests


Mit Hilfe von Integrationstests überprüfen wir die Interaktion mit verschiedenen Diensten und Datenbanken.

Um die Geschichte besser zu verstehen, entwickeln wir eine fiktive Promo und decken sie mit Tests ab. Stellen Sie sich vor, unsere Produktmanager haben beschlossen, Konferenztickets an unsere engagiertesten Benutzer zu verteilen:


Promo sollte angezeigt werden, wenn:

  • Der Benutzer im Feld "Arbeit" gibt "Programmierer" an.
  • Der Benutzer nimmt am A / B-Test HL18_promo teil.
  • Der Benutzer ist vor mehr als zwei Jahren registriert.

Durch Klicken auf die Schaltfläche "Ticket erhalten" müssen wir die Daten dieses Benutzers in einer Liste speichern, um sie an unsere Manager zu übertragen, die Tickets verteilen.

Selbst in diesem recht einfachen Beispiel gibt es eine Sache, die mit Unit-Tests nicht überprüft werden kann - die Interaktion mit der Datenbank. Dazu müssen wir Integrationstests verwenden.

Betrachten Sie die Standardmethode zum Testen der von PHPUnit angebotenen Datenbankinteraktion:

  1. Erhöhen Sie die Testdatenbank.
  2. Wir bereiten DataTables und DataSets vor.
  3. Führen Sie den Test aus.
  4. Wir löschen die Testdatenbank.

Welche Schwierigkeiten warten bei einem solchen Ansatz auf Sie?

  • Sie müssen die Strukturen von DataTables und DataSets unterstützen. Wenn wir das Tabellenlayout geändert haben, müssen diese Änderungen im Test berücksichtigt werden. Dies ist nicht immer praktisch und erfordert zusätzliche Zeit.
  • Die Vorbereitung der Datenbank dauert einige Zeit. Jedes Mal, wenn wir den Test einrichten, müssen wir dort etwas hochladen, einige Tabellen erstellen, und dies ist lang und mühsam, wenn es viele Tests gibt.
  • Und der wichtigste Nachteil: Wenn diese Tests parallel ausgeführt werden, sind sie instabil. Wir haben Test A gestartet, er hat angefangen, an den Testtisch zu schreiben, den er erstellt hat. Gleichzeitig haben wir Test B gestartet, der mit derselben Testtabelle arbeiten möchte. Infolgedessen entstehen gegenseitige Blockaden und andere unvorhergesehene Situationen.

Um diese Probleme zu vermeiden, haben wir unsere eigene kleine Bibliothek DBMocks entwickelt.

DBMocks


Das Funktionsprinzip lautet wie folgt:

  1. Mit Hilfe von SoftMocks fangen wir alle Wrapper ab, über die wir mit Datenbanken arbeiten.
  2. Wann
    Die Abfrage durchläuft einen Mock, analysiert die SQL-Abfrage, zieht DB + TableName daraus und ruft den Host von der Verbindung ab.
  3. Auf demselben Host in tmpfs erstellen wir eine temporäre Tabelle mit derselben Struktur wie die ursprüngliche (wir kopieren die Struktur mit SHOW CREATE TABLE).
  4. Danach leiten wir alle Anfragen, die über diese Tabelle verspottet werden, an eine frisch erstellte temporäre Anfrage weiter.

Was gibt uns das:

  • keine Notwendigkeit, sich ständig um die Strukturen zu kümmern;
  • Tests können Daten in Quelltabellen nicht mehr beschädigen, da wir sie im laufenden Betrieb in temporäre Tabellen umleiten.
  • Wir testen immer noch die Kompatibilität mit der Version von MySQL, mit der wir arbeiten. Wenn die Anforderung plötzlich nicht mehr mit der neuen Version kompatibel ist, wird unser Test sie sehen und zum Absturz bringen.
  • und am wichtigsten ist, dass die Tests jetzt isoliert sind. Selbst wenn Sie sie parallel ausführen, werden die Threads in verschiedene temporäre Tabellen aufgeteilt, da wir jedem Test in den Namen der Testtabellen einen eindeutigen Schlüssel hinzufügen.

API-Tests


Der Unterschied zwischen Unit- und API-Tests wird durch dieses GIF gut veranschaulicht:


Das Schloss funktioniert einwandfrei, ist aber an der falschen Tür angebracht.

Unsere Tests simulieren eine Client-Sitzung, können Anforderungen gemäß unserem Protokoll an das Backend senden und das Backend antwortet darauf als echter Client.

Benutzerpool testen


Was brauchen wir, um solche Tests erfolgreich zu schreiben? Kehren wir zu den Bedingungen der Show unserer Promo zurück:

  • Der Benutzer im Feld "Arbeit" gibt "Programmierer" an.
  • Der Benutzer nimmt am A / B-Test HL18_promo teil.
  • Der Benutzer ist vor mehr als zwei Jahren registriert.

Anscheinend dreht sich hier alles um den Benutzer. In der Realität erfordern 99% der API-Tests einen autorisierten registrierten Benutzer, der in allen Diensten und Datenbanken vorhanden ist.

Wo kann man es bekommen? Sie können versuchen, es zum Zeitpunkt des Tests zu registrieren, aber:

  • es ist lang und ressourcenintensiv;
  • Nach Abschluss des Tests muss dieser Benutzer irgendwie entfernt werden. Dies ist eine nicht triviale Aufgabe, wenn es sich um große Projekte handelt.
  • Schließlich führen wir, wie in vielen anderen hoch ausgelasteten Projekten, viele Vorgänge im Hintergrund aus (Hinzufügen eines Benutzers zu verschiedenen Diensten, Replikation in andere Rechenzentren usw.). Tests wissen nichts über solche Prozesse, aber wenn sie implizit auf den Ergebnissen ihrer Ausführung beruhen, besteht die Gefahr einer Instabilität.


Wir haben ein Tool namens Test Users Pool entwickelt. Es basiert auf zwei Ideen:

  1. Wir registrieren Benutzer nicht jedes Mal, sondern verwenden sie oft.
  2. Nach dem Test setzen wir die Benutzerdaten auf ihren ursprünglichen Zustand zurück (zum Zeitpunkt der Registrierung). Andernfalls werden die Tests mit der Zeit instabil, da Benutzer mit Informationen aus anderen Tests „verschmutzt“ werden.


Es funktioniert ungefähr so:



Irgendwann wollten wir unsere API-Tests in einer Produktionsumgebung ausführen. Warum wollen wir das überhaupt? Weil die Entwicklungsinfrastruktur nicht mit der Produktion identisch ist.

Obwohl wir versuchen, die Produktionsinfrastruktur ständig in reduzierter Größe zu wiederholen, wird die Entwicklung niemals eine vollständige Kopie davon sein. Um absolut sicher zu sein, dass der neue Build den Erwartungen entspricht und keine Probleme auftreten, laden wir den neuen Code in den Vorproduktionscluster hoch, der mit Produktionsdaten und -diensten arbeitet, und führen dort unsere API-Tests aus.

In diesem Fall ist es sehr wichtig, darüber nachzudenken, wie Testbenutzer von echten Benutzern isoliert werden können.

Was passiert, wenn Testbenutzer in unserer Anwendung real erscheinen?


Wie zu isolieren? Jeder unserer Benutzer hat ein is_test_user Flag. In der Registrierungsphase wird es yes oder no und ändert sich nicht mehr. Durch dieses Flag isolieren wir Benutzer in allen Diensten. Es ist auch wichtig, dass wir Testbenutzer von der Geschäftsanalyse und den Ergebnissen von A / B-Tests ausschließen, um Statistiken nicht zu verfälschen.

Sie können es einfacher machen: Wir haben damit begonnen, dass alle Testbenutzer in die Antarktis „umgesiedelt“ wurden. Wenn Sie einen Geoservice haben, funktioniert dies vollständig.

QA-API


Wir brauchen nicht nur einen Benutzer - wir brauchen ihn mit bestimmten Parametern: um als Programmierer zu arbeiten, an einem bestimmten A / B-Test teilzunehmen und wurde vor mehr als zwei Jahren registriert. Für Testbenutzer können wir mithilfe unserer Backend-API problemlos einen Beruf zuweisen, aber der Einstieg in A / B-Tests ist wahrscheinlich. Und die Registrierungsbedingung vor mehr als zwei Jahren ist im Allgemeinen schwer zu erfüllen, da wir nicht wissen, wann der Benutzer im Pool erschien.

Um diese Probleme zu lösen, haben wir eine QA-API. Dies ist in der Tat eine Hintertür zum Testen. Hierbei handelt es sich um gut dokumentierte API-Methoden, mit denen Sie Benutzerdaten schnell und einfach verwalten und ihren Status unter Umgehung des Hauptprotokolls unserer Kommunikation mit Kunden ändern können. Die Methoden wurden von Backend-Entwicklern für QS-Ingenieure und zur Verwendung in UI- und API-Tests geschrieben.

Die QA-API kann nur bei Testbenutzern angewendet werden: Wenn kein entsprechendes Flag vorhanden ist, wird der Test sofort gelöscht. Hier ist eine unserer QA-API-Methoden, mit der Sie das Registrierungsdatum des Benutzers in ein beliebiges ändern können:



Es sieht also nach drei Anrufen aus, mit denen Sie die Daten des Testbenutzers schnell ändern können, damit diese die Bedingungen für die Anzeige der Promo erfüllen:

  • Im Feld "Arbeit" wird der "Programmierer" angezeigt:
    addUserWorkEducation?user_id=ID&works[]=Badoo,

  • Der Benutzer nimmt am A / B-Test HL18_promo teil:
    forceSplitTest?user_id=ID&test=HL18_promo
  • Vor mehr als zwei Jahren registriert:
    userCreatedChange?user_id=ID&created=2016-09-01


Da dies eine Hintertür ist, ist es wichtig, über Sicherheit nachzudenken. Wir haben unseren Service auf verschiedene Weise geschützt:

  • Auf Netzwerkebene isoliert: Auf Dienste kann nur über das Büronetzwerk zugegriffen werden.
  • Bei jeder Anfrage geben wir ein Geheimnis weiter, ohne das es unmöglich ist, auch über das Büronetzwerk auf die QA-API zuzugreifen.
  • Methoden funktionieren nur mit Testbenutzern.


Remotemocks


Um mit dem Remote-Backend von API-Tests arbeiten zu können, benötigen wir möglicherweise Mocks. Wofür? Wenn der API-Test in der Produktionsumgebung beispielsweise auf die Datenbank zugreift, müssen wir sicherstellen, dass die darin enthaltenen Daten von den Testdaten gelöscht werden. Darüber hinaus tragen Mocks dazu bei, dass die Testantwort besser zum Testen geeignet ist.

Wir haben drei Texte:



Badoo ist eine mehrsprachige Anwendung. Wir verfügen über eine komplexe Lokalisierungskomponente, mit der Sie schnell Übersetzungen für den aktuellen Standort des Benutzers übersetzen und empfangen können. Unsere Lokalisierer arbeiten ständig daran, die Übersetzungen zu verbessern, A / B-Tests mit Token durchzuführen und nach erfolgreicheren Formulierungen zu suchen. Während der Durchführung des Tests können wir nicht wissen, welcher Text vom Server zurückgegeben wird - er kann sich jederzeit ändern. Mit RemoteMocks können wir jedoch überprüfen, ob auf die Lokalisierungskomponente korrekt zugegriffen wird.

Wie funktionieren RemoteMocks? Der Test fordert das Backend auf, sie für seine Sitzung zu initialisieren, und nach Eingang aller nachfolgenden Anforderungen prüft das Backend, ob für die aktuelle Sitzung Mocks vorhanden sind. Wenn dies der Fall ist, werden sie einfach mit SoftMocks initialisiert.

Wenn wir ein Remote-Modell erstellen möchten, geben wir an, welche Klasse oder Methode durch welche ersetzt werden muss. Alle nachfolgenden Backend-Anforderungen werden unter Berücksichtigung dieses Modells ausgeführt:

 $this->remoteInterceptMethod( \Promo\HighLoadConference::class, 'saveUserEmailToDb', true ); 

Nun sammeln wir unseren API-Test:

 //       $app_startup = [ 'supported_promo_blocks' => [\Mobile\Proto\Enum\PromoBlockType::GENERIC_PROMO] ]; $Client = $this->getLoginedConnection(BmaFunctionalConfig::USER_TYPE_NEW, $app_startup); //  $Client->getQaApiClient()->addUserWorkEducation(['Badoo, ']); $Client->getQaApiClient()->forceSplitTest('HL18_promo'); $Client->getQaApiClient()->userCreatedChange('2016-09-01'); //     $this->remoteInterceptMethod(\Promo\HighLoadConference::class, 'saveUserEmail', true); //,   ,   $Resp = $Client->ServerGetPromoBlocks([]); $this->assertTrue($Resp->hasMessageType('CLIENT_NEXT_PROMO_BLOCKS')); $PromoBlock = $Resp->CLIENT_NEXT_PROMO_BLOCKS; … //   CTA, ,   ,   $Resp = $Client->ServerPromoAccepted($PromoBlock->getPromoId()); $this->assertTrue($Resp->hasMessageType('CLIENT_ACKNOWLEDGE_COMMAND')); 


Auf so einfache Weise können wir alle Funktionen testen, die im Backend zur Entwicklung kommen und Änderungen im mobilen Protokoll erfordern.

API-Testverwendungsregeln


Alles scheint in Ordnung zu sein, aber wir sind erneut auf ein Problem gestoßen: Die API-Tests erwiesen sich als zu praktisch für die Entwicklung, und es bestand die Versuchung, sie überall zu verwenden. Als wir feststellten, dass wir anfingen, Probleme mithilfe von API-Tests zu lösen, für die sie nicht vorgesehen waren.

Warum ist das so schlimm? Weil API-Tests sehr langsam sind. Sie gehen in das Netzwerk, wenden sich an das Backend, das die Sitzung aufnimmt, gehen zur Datenbank und zu einer Reihe von Diensten. Aus diesem Grund haben wir eine Reihe von Regeln für die Verwendung von API-Tests entwickelt:
  • Der Zweck der API-Tests besteht darin, das Interaktionsprotokoll zwischen dem Client und dem Server sowie die korrekte Integration des neuen Codes zu überprüfen.

  • es ist zulässig, komplexe Prozesse mit ihnen abzudecken, beispielsweise Handlungsketten;
  • Sie können nicht zum Testen der geringen Variabilität der Serverantwort verwendet werden. Dies ist die Aufgabe von Komponententests.
  • Während der Codeüberprüfung prüfen wir, ob Tests enthalten sind.

UI-Tests


Da wir über eine Pyramide der Automatisierung nachdenken, werde ich Ihnen ein wenig über UI-Tests erzählen.

Backend-Entwickler bei Badoo schreiben keine UI-Tests - dafür haben wir ein engagiertes Team in der QS-Abteilung. Wir decken das Feature mit UI-Tests ab, wenn es bereits in Erinnerung gerufen und stabilisiert wurde, da wir der Ansicht sind, dass es nicht zumutbar ist, Ressourcen für eine recht teure Automatisierung des Features aufzuwenden, die möglicherweise nicht über den A / B-Test hinausgeht.

Wir verwenden Calabash für mobile Autotests und Selen für das Web. Es geht um unsere Plattform für Automatisierung und Tests.

Testlauf


Wir haben jetzt 100.000 Komponententests, 6.000 Integrationstests und 14.000 API-Tests. Wenn Sie versuchen, sie in einem Thread auszuführen, dauert selbst auf unserem leistungsstärksten Computer eine vollständige Ausführung: modular - 40 Minuten, Integration - 90 Minuten, API-Tests - zehn Stunden. Es ist zu lang

Parallelisierung


In diesem Artikel haben wir über unsere Erfahrungen mit der Parallelisierung von Komponententests gesprochen.

Die erste Lösung, die offensichtlich erscheint, besteht darin, Tests in mehreren Threads auszuführen. Wir sind jedoch noch weiter gegangen und haben eine Cloud für den parallelen Start erstellt, um Hardwareressourcen skalieren zu können. Vereinfacht sieht seine Arbeit so aus:



Die interessanteste Aufgabe hierbei ist die Verteilung von Tests zwischen Threads, dh deren Aufteilung in Blöcke.

Sie können sie gleichmäßig aufteilen, aber alle Tests sind unterschiedlich, sodass die Ausführungszeit eines Threads möglicherweise stark verzerrt ist: Alle Threads haben bereits erreicht, und einer hängt eine halbe Stunde lang, da er bei sehr langsamen Tests „Glück“ hatte.

Sie können mehrere Threads starten und sie nacheinander testen. In diesem Fall ist der Nachteil weniger offensichtlich: Es fallen Gemeinkosten für die Initialisierung der Umgebung an, die bei einer großen Anzahl von Tests und diesem Ansatz eine wichtige Rolle zu spielen beginnen.

Was haben wir getan Wir haben angefangen, Statistiken über die Zeit zu sammeln, die für die Ausführung jedes Tests benötigt wurde, und haben dann begonnen, Blöcke so zusammenzustellen, dass laut Statistik ein Thread nicht länger als 30 Sekunden ausgeführt wird. Gleichzeitig packen wir die Tests ziemlich dicht in Stücke, um sie kleiner zu machen.

Unser Ansatz hat jedoch auch einen Nachteil. Dies ist mit API-Tests verbunden: Sie sind sehr langsam und verbrauchen viel Ressourcen, sodass keine schnellen Tests ausgeführt werden können.

Aus diesem Grund haben wir die Cloud in zwei Teile unterteilt: Im ersten Teil werden nur schnelle Tests gestartet, und im zweiten Teil können sowohl schnelle als auch langsame Tests gestartet werden. Mit diesem Ansatz haben wir immer einen Teil der Cloud, der schnelle Tests durchführen kann.



Infolgedessen wurden Unit-Tests in einer Minute ausgeführt, Integrationstests in fünf Minuten und API-Tests in 15 Minuten. Das heißt, ein vollständiger Lauf anstelle von 12 Stunden dauert nicht länger als 22 Minuten.

Testlauf zur Codeabdeckung


Wir haben einen großen komplexen Monolithen, und auf gute Weise müssen wir ständig alle Tests durchführen, da eine Änderung an einem Ort etwas an einem anderen zerstören kann. Dies ist einer der Hauptnachteile der monolithischen Architektur.

Irgendwann kamen wir zu dem Schluss, dass Sie nicht jedes Mal alle Tests ausführen müssen - Sie können Läufe basierend auf der Codeabdeckung durchführen:

  1. Nehmen Sie unseren Zweig diff.
  2. Wir erstellen eine Liste der geänderten Dateien.
  3. Für jede Datei erhalten wir eine Liste von Tests,
    das deckt es ab.
  4. Aus diesen Tests erstellen wir einen Satz und führen ihn in einer Testwolke aus.

Wo bekomme ich Berichterstattung? Wir sammeln einmal täglich Daten, wenn die Infrastruktur der Entwicklungsumgebung inaktiv ist. Die Anzahl der durchgeführten Tests hat deutlich abgenommen, die Geschwindigkeit, mit der Rückmeldungen von ihnen empfangen werden, hat sich im Gegenteil erheblich erhöht. Gewinn!

Ein zusätzlicher Bonus war die Möglichkeit, Tests für Patches durchzuführen. Trotz der Tatsache, dass Badoo schon lange kein Startup mehr ist, können wir Änderungen in der Produktion schnell implementieren, Hotfixes schnell einfügen, Funktionen einführen und die Konfiguration ändern. In der Regel ist uns die Geschwindigkeit der Einführung von Patches sehr wichtig. Der neue Ansatz führte zu einer starken Erhöhung der Rückkopplungsgeschwindigkeit aus den Tests, da wir jetzt nicht mehr lange auf einen vollständigen Lauf warten müssen.

. , , , . . , code coverage . , , — , - , . .

API-, code coverage. , , . - , API- .

Fazit


  • , . - , , - .
  • ≠ . code review , .
  • , , . .
  • . .
  • , ! , .


, Badoo PHP Meetup 16 . PHP-. , . ! 12:00, — YouTube- .

Source: https://habr.com/ru/post/de443768/


All Articles