Heim >Backend-Entwicklung >PHP-Tutorial >Testen Sie Ihre gesamte Geschäftslogik in weniger als Sekunden
In diesem Artikel geht es nicht um Tests. Es geht darum, einen Workflow einzuführen, der Ihnen bei der Entwicklung Ihrer Funktion die Kontrolle behält. Tests sind nur der Motor und die glückliche Konsequenz dieses Prozesses.
Dieser Workflow hat die Art und Weise, wie ich programmiere, völlig verändert und zaubert mir immer wieder ein Lächeln ins Gesicht. Ich hoffe, dass es bei Ihnen dasselbe bewirkt. Am Ende verfügen Sie über eine vollständig entwickelte Funktion, die alle Geschäftsregeln erfüllt, und über eine Testsuite, die sie in weniger als einer Sekunde validiert.
Ich habe PHP für die Demonstration verwendet, aber dieser Workflow ist vollständig an jede Sprache anpassbar.
Wenn es an der Zeit ist, eine neue Funktion zu entwickeln, ist es oft schwierig zu wissen, wo man anfangen soll. Sollten Sie mit der Geschäftslogik, dem Controller oder dem Frontend beginnen?
Es ist ebenso schwierig zu wissen, wann man aufhören muss. Ohne einen klaren Prozess können Sie Ihren Fortschritt nur durch manuelle Tests messen. Ein langwieriger und fehleranfälliger Ansatz.
Du kennst das. Diese Datei, in der unerklärliche Dinge passieren. Sie müssen eine einzelne Zeile ändern, aber jeder Versuch scheint das gesamte Projekt zu zerstören. Ohne ein Sicherheitsnetz aus Tests fühlt sich Refactoring wie eine Gratwanderung über eine Schlucht an.
Um dieses Chaos zu vermeiden, entscheiden Sie sich, jede Funktion mit End-to-End-Tests zu testen. Tolle Idee! Bis Ihnen klar wird, dass Sie genug Zeit haben, fünf Tassen Kaffee zu trinken, während Sie auf den Abschluss der Testsuite warten. Produktivität? Aus dem Fenster.
Um sofortiges Feedback zu erhalten, beschließen Sie, detaillierte Tests für alle Ihre Kurse zu schreiben. Doch jetzt führt jede Änderung, die Sie vornehmen, zu einer Kaskade fehlerhafter Tests, und Sie verbringen am Ende mehr Zeit damit, sie zu reparieren, als an der eigentlichen Änderung zu arbeiten. Es ist frustrierend, ineffizient und lässt Sie vor jedem Refactor fürchten.
Feedback ist in jeder Phase der Softwareentwicklung von entscheidender Bedeutung. Während ich den Kern des Projekts entwickle, benötige ich sofortiges Feedback, um sofort zu wissen, ob gegen meine Geschäftsregeln verstoßen wird. Während des Refactorings ist es ein unschätzbarer Vorteil, einen Assistenten zu haben, der mich darüber informiert, ob der Code fehlerhaft ist oder ob ich sicher fortfahren kann.
Was das Projekt leisten kann, das Projektverhalten, ist der wichtigste Aspekt. Diese Verhaltensweisen sollten benutzerzentriert sein. Selbst wenn sich die Implementierung (der Code, der erstellt wurde, damit eine Funktion funktioniert) ändert, bleibt die Absicht des Benutzers dieselbe.
Wenn ein Benutzer beispielsweise eine Produktbestellung aufgibt, möchte er benachrichtigt werden. Es gibt viele Möglichkeiten, einen Benutzer zu benachrichtigen: E-Mail, SMS, Post usw. Während sich die Absicht des Benutzers nicht ändert, ändert sich die Implementierung oft.
Wenn ein Test die Absicht des Benutzers darstellt und der Code diese Absicht nicht mehr erfüllt, sollte der Test fehlschlagen. Wenn der Code jedoch während des Refactorings immer noch der Absicht des Benutzers entspricht, sollte der Test nicht abbrechen.
Stellen Sie sich vor, Sie würden Schritt für Schritt durch die Erstellung einer neuen Funktion geführt. Wenn Sie den Test zuerst schreiben, wird er zu Ihrem Leitfaden für die Codierung der richtigen Funktion. Es ist vielleicht noch nicht ganz klar, aber ich möchte, dass mein Test beim Programmieren als GPS fungiert. Bei dieser Vorgehensweise muss ich nicht über den nächsten Schritt nachdenken, sondern folge einfach den GPS-Anweisungen. Wenn Ihnen dieses Konzept immer noch unklar vorkommt, machen Sie sich keine Sorgen! Ich werde es im Abschnitt „Workflow“ ausführlich erläutern. ?
Wie ich in der Einleitung erwähnt habe, geht es in diesem Artikel nicht um Tests, sondern um die Erstellung einer Funktion. Und dazu brauche ich eine Funktion. Genauer gesagt benötige ich eine Funktion, die von mit Beispielen definierten Abnahmetests begleitet wird. Für jede Funktion sollte das Team Akzeptanzkriterien oder Regeln festlegen und für jede Regel ein oder mehrere Beispiele erstellen.
⚠️ Diese Beispiele müssen benutzer-/domänenzentriert sein und dürfen keine technischen Aspekte beschreiben. Eine einfache Möglichkeit, zu überprüfen, ob Ihre Beispiele klar definiert sind, besteht darin, sich zu fragen: „Würde mein Beispiel mit jeder Implementierung funktionieren (Web-Frontend, HTTP-API, Terminal-CLI, reales Leben usw.)?“
Wenn ich beispielsweise die Funktion „Produkt in den Warenkorb legen“ entwickeln möchte, könnten meine Beispiele so aussehen:
Diese Beispiele dienen als Grundlage für die Tests. Für komplexere Szenarien verwende ich das Muster „Gegeben/Wann/Dann“ für zusätzliche Details, für einfachere Szenarien ist es jedoch nicht erforderlich.
Sofortiges Feedback und ein Fokus auf Verhaltensweisen, ist das nicht eine ziemliche Herausforderung? Mit der Symfony MVC Services-Architektur kann dies schwierig zu erreichen sein. Aus diesem Grund verwende ich eine domänenorientierte Architektur, die ich in diesem Artikel ausführlich beschrieben habe: Eine andere Möglichkeit, Ihr Symfony-Projekt zu strukturieren.
Um mich auf Verhaltensweisen zu konzentrieren, muss ich die Tests richtig strukturieren. Zunächst beschreibe ich den Zustand des Systems vor der Benutzeraktion. Als nächstes führe ich die Benutzeraktion aus, die den Zustand des Systems ändert. Abschließend behaupte ich, dass der Zustand des Systems meinen Erwartungen entspricht.
Beim Testen auf diese Weise ist es dem Test egal, wie die Benutzeraktion vom System gehandhabt wird; Es prüft lediglich, ob die Aktion erfolgreich ist oder nicht. Das bedeutet, dass der Test nicht abbricht, wenn wir die Implementierung der Benutzeraktion ändern. Wenn die Implementierung den Systemstatus jedoch nicht wie erwartet aktualisiert, wird der Test abbrechen – und das ist genau das, was wir wollen!
Um sofortiges Feedback zu erhalten, müssen bestimmte Teile des Systems verspottet werden. Eine domänenzentrierte Architektur macht dies möglich, da die Domäne ausschließlich auf Schnittstellen zur Interaktion mit externen Bibliotheken basiert. Dadurch ist es unglaublich einfach, diese Abhängigkeiten zu fälschen, sodass die Funktionstests sehr schnell ausgeführt werden können. Natürlich werden auch die realen Implementierungen (allerdings nicht in diesem Artikel) mithilfe von Integrationstests getestet.
In diesem Workflow ist der Test mein GPS! Ich lege ein Ziel fest, lasse mich von ihm leiten und benachrichtige mich, wenn ich angekommen bin. Der Test wird auf der Grundlage des vom Team bereitgestellten Beispiels Gestalt annehmen.
Um die Geschäftslogik und die Benutzerabsicht zu testen, verwende ich einen Funktionstest:
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
Diese Datei enthält alle Tests für diese Funktion, aber beginnen wir mit dem ersten.
Im ersten Teil beschreibe ich den Stand der Bewerbung. Hier gibt es einen leeren Warenkorb für den Kunden mit der ID „1“.
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
Der Handler und der Befehl repräsentieren die Absicht des Benutzers, so ist es explizit und jeder versteht es.
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
? : Ich habe beschlossen, die Befehle und Abfragen in diesem Projekt zu trennen, aber wir könnten ein AddProductToBasketUseCase haben.
Zuletzt ist es an der Zeit zu beschreiben, wie das Endergebnis aussehen soll. Ich gehe davon aus, dass ich das richtige Produkt in meinem Warenkorb habe.
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
Hier ist der Test, der das erste Beispiel in Code übersetzt.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket(): void { // Arrange $customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]); // Act $commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId)); // Assert $expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]); } }
Zu diesem Zeitpunkt zeigt meine IDE überall Fehler an, da noch nichts existiert, was ich in diesem Test verwendet habe. Aber handelt es sich dabei wirklich um Fehler oder handelt es sich lediglich um die nächsten Schritte in Richtung unseres Ziels? Ich behandle diese Fehler als Anweisungen. Jedes Mal, wenn ich den Test ausführe, wird uns gesagt, was als nächstes zu tun ist. Auf diese Weise muss ich nicht zu viel nachdenken; Ich folge einfach dem GPS.
? Tipp: Um das Entwicklererlebnis zu verbessern, können Sie einen Datei-Watcher aktivieren, der die Testsuite automatisch ausführt, wenn Sie eine Änderung vornehmen. Da die Testsuite extrem schnell ist, erhalten Sie bei jedem Update sofort Feedback.
In diesem Beispiel gehe ich jeden Fehler einzeln an. Wenn Sie sich sicher fühlen, können Sie unterwegs natürlich auch Abkürzungen nehmen. ?
Also, lasst uns den Test durchführen! Für jeden Fehler werde ich das Nötigste tun, um mit dem nächsten Schritt fortzufahren.
❌ ? : "Fehler: Klasse „AppTestsFunctionalBasket“ nicht gefunden"
Da es sich um den ersten Test dieser Funktion handelt, existieren die meisten Klassen noch nicht. Als ersten Schritt erstelle ich ein Basket-Objekt:
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
❌ ? : "Fehler: Klasse „AppTestsFunctionalInMemoryBasketRepository" nicht gefunden"
Ich erstelle das InMemoryBasketRepository :
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
❌ ? : "Fehler: Klasse „AppTestsFunctionalAddProductToBasketCommandHandler“ nicht gefunden“
Ich erstelle den AddProductToBasketCommandHandler im Katalog-/Anwendungsordner. Dieser Handler wird der Einstiegspunkt für die Funktion „Produkt zum Warenkorb hinzufügen“ sein.
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
Um die domänenzentrierte Architektur zu respektieren, erstelle ich eine Schnittstelle für das Repository, so kehren wir die Abhängigkeit um.
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
❌ ? : "TypeError : AppCatalogApplicationCommandAddProductToBasketAddProductToBasketCommandHandler::__construct(): Argument #1 ($basketRepository) muss vom Typ AppCatalogDomainBasketRepository sein, AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository gegeben"
Jetzt muss die InMemory-Implementierung die Schnittstelle implementieren, damit sie in den Befehlshandler eingefügt werden kann.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket(): void { // Arrange $customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]); // Act $commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId)); // Assert $expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]); } }
❌ ? : _"Fehler: Klasse „AppTestsFunctionalAddProductToBasketCommand“ nicht gefunden“
_
Ich erstelle einen Befehl. Da es sich bei einem Befehl immer um ein DTO handelt, habe ich ihn als schreibgeschützt markiert und alle Eigenschaften öffentlich gemacht.
// src/Catalog/Domain/Basket.php namespace App\Catalog\Domain; class Basket { public function __construct( public readonly string $id, public readonly string $customerId, public array $products = [], ) { } }
❌ ? : „Behauptung, dass zwei Objekte gleich sind, fehlgeschlagen.“
Der Test wird kompiliert! Es ist immer noch rot, also sind wir noch nicht fertig, lasst uns weitermachen! ?
Es ist Zeit, die Geschäftslogik zu programmieren. Genau wie beim Schreiben des Tests schreibe ich, auch wenn noch nichts existiert, wie ich von meinem Handler erwarte, dass er den Befehl verarbeitet. Es lässt sich nicht kompilieren, aber auch hier führt mich der Test zum nächsten Schritt.
// src/Catalog/Infrastructure/Persistence/InMemory/InMemoryBasketRepository.php namespace App\Catalog\Infrastructure\Persistence\InMemory; class InMemoryBasketRepository { public function __construct( public array $baskets ) { } }
❌ ? : "Fehler: Aufruf der undefinierten Methode AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository::get()"
Ich erstelle die get-Methode in der Repository-Schnittstelle:
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
❌ ? : "Schwerwiegender PHP-Fehler: Die Klasse AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository enthält 1 abstrakte Methode und muss daher als abstrakt deklariert werden oder die restlichen Methoden implementieren (AppCatalogDomainBasketRepository::get)"
Und jetzt implementiere ich es im InMemory-Repository. Da es nur zum Testen dient, erstelle ich eine wirklich einfache Implementierung :
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
❌ ? : "Fehler: Aufruf der undefinierten Methode AppCatalogDomainBasket::add()"
Ich erstelle die Methode add und implementieren sie auf dem Korbobjekt
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
❌ ? : Fehler: Aufruf der undefinierten Methode AppCatalogInfrastructurePersistenceInMemoryInMemoryBasketRepository::save()
Dasselbe gilt hier, ich erstelle die Methode in der Schnittstelle und implementieren sie im In-Memory-Repository:
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket(): void { // Arrange $customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]); // Act $commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId)); // Assert $expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]); } }
✅ ? :
Ziel erreicht! ? ? ?
Mit diesem Workflow habe ich die Entwicklererfahrung spielerisch gestaltet. Nach der Herausforderung, den roten Test zu meistern, erhalten Sie einen Schuss Dopamin, wenn Sie sehen, wie er grün wird ??
Um den Test zu bestehen, bin ich absichtlich etwas schnell gegangen. Jetzt kann ich mir die Zeit nehmen, den Test zu verfeinern und den Code besser zu implementieren.
Der Test übersetzte das Beispiel, verlor jedoch während der Übersetzung die Absicht des Benutzers. Mit wenigen Funktionen kann ich es dem Originalbeispiel viel näher bringen.
Beispiel:
Test:
// src/Catalog/Domain/Basket.php namespace App\Catalog\Domain; class Basket { public function __construct( public readonly string $id, public readonly string $customerId, public array $products = [], ) { } }
Das ist nicht der Schwerpunkt dieses Artikels, daher werde ich nicht ins Detail gehen, aber ich könnte das Domänenmodell mithilfe eines umfangreichen Domänenmodells präziser ausdrücken.
Da der Test grün ist, kann ich jetzt so viel umgestalten, wie ich möchte, der Test steht mir zur Seite. ?
Wie Sie am Anfang des Artikels gesehen haben, gibt es für eine Funktion viele Beispiele, um sie zu beschreiben. Es ist also an der Zeit, sie alle umzusetzen :
// src/Catalog/Infrastructure/Persistence/InMemory/InMemoryBasketRepository.php namespace App\Catalog\Infrastructure\Persistence\InMemory; class InMemoryBasketRepository { public function __construct( public array $baskets ) { } }
Ich habe diesen Workflow mehrmals mit verschiedenen Tests verfolgt und verschiedene Teile meines Codes haben sich weiterentwickelt, um sie an die Geschäftsregeln anzupassen. Wie Sie beim ersten Test gesehen haben, waren meine Objekte sehr einfach und manchmal sogar einfach. Als jedoch neue Geschäftsregeln eingeführt wurden, drängten sie mich dazu, ein viel intelligenteres Domänenmodell zu entwickeln. Hier ist meine Ordnerstruktur:
Um das Testen zu vereinfachen und gleichzeitig die Kapselung von Domänenmodellen beizubehalten, habe ich die Builder- und Snapshot-Muster eingeführt.
// tests/Functional/AddProductToBasketTest.php namespace App\Tests\Functional; use PHPUnit\Framework\TestCase; class AddProductToBasketTest extends TestCase { public function testAddProductToBasket() { } }
$customerId = 1; $productId = 1; $basketId = 1; $basket = new Basket($basketId, customerId: $customerId); $inMemoryBasketRepository = new InMemoryBasketRepository(baskets: [$basket]);
$commandHandler = new AddProductToBasketCommandHandler( $inMemoryBasketRepository, ); $commandHandler(new AddProductToBasketCommand(customerId: $customerId, basketId: $basketId, productId: $productId));
Wie ich es verwende:
$expectedBasket = new Basket($basketId, $customerId, array($productId)); $this->assertEquals($expectedBasket, $inMemoryBasketRepository->baskets[0]);
Da ich mich voll und ganz auf die Geschäftslogik konzentriere, führt mich mein Test bei jedem Schritt und sagt mir jedes Mal, was ich tun soll, wenn ich neuen Code hinzufüge. Außerdem muss ich keine manuellen Tests mehr durchführen, um sicherzustellen, dass alles funktioniert, was meine Effizienz deutlich verbessert.
In diesem Artikel habe ich ein sehr einfaches Beispiel gezeigt, in dem ich hauptsächlich Klassen erstellt habe. Bei komplexerer Geschäftslogik erhöht jedoch jede neue Regel die Komplexität des Domänenmodells. Beim Refactoring besteht mein Ziel darin, das Domänenmodell zu vereinfachen und gleichzeitig die Tests grün zu halten. Es ist eine echte Herausforderung – aber eine unglaublich befriedigende, wenn es endlich klappt.
Was ich hier gezeigt habe, ist nur der erste Teil des gesamten Workflows. Wie bereits erwähnt, muss ich für eine voll funktionsfähige Funktion noch die echten Adapter implementieren. In diesem Fall würde dies die Erstellung der Repositorys (Warenkorb, Kunde und Produkt) unter Verwendung eines Test-First-Ansatzes mit einer echten Datenbank umfassen. Sobald das abgeschlossen ist, würde ich einfach die tatsächlichen Implementierungen in der Abhängigkeitsinjektionskonfiguration konfigurieren.
Ich habe reines PHP verwendet, ohne mich auf ein Framework zu verlassen, und mich ausschließlich auf die Kapselung der gesamten Geschäftslogik konzentriert. Mit dieser Strategie muss ich mir keine Sorgen mehr um die Testleistung machen. Unabhängig davon, ob es sich um Hunderte oder sogar Tausende von Tests handelt, werden sie immer noch in wenigen Sekunden ausgeführt und liefern beim Codieren sofortiges Feedback.
Um Ihnen einen Eindruck von der Leistung zu geben, ist hier die Testsuite, die 10.000 Mal wiederholt wurde:
Dieser Workflow liegt an der Schnittstelle mehrerer Konzepte. Lassen Sie mich einige davon vorstellen, damit Sie sie bei Bedarf weiter erkunden können.
Um eine Funktion zu beschreiben, besteht der beste Ansatz darin, so viele Beispiele wie nötig bereitzustellen. Eine Teambesprechung kann Ihnen dabei helfen, ein tiefes Verständnis für die Funktion zu erlangen, indem konkrete Beispiele dafür identifiziert werden, wie sie funktionieren sollte.
Das Given-When-Then-Framework ist ein großartiges Werkzeug zur Formalisierung dieser Beispiele.
Um diese Beispiele in Code zu übersetzen, kann die Sprache Gherkin (mit Behat in PHP) hilfreich sein. Ich persönlich bevorzuge es jedoch, direkt in PHPUnit-Tests zu arbeiten und meine Funktionen mit diesen Schlüsselwörtern zu benennen.
Das „Was wollen wir?“ Ein Teil könnte mit dem Akronym F.I.R.S.T zusammengefasst werden:
Jetzt haben Sie eine Checkliste, um zu wissen, ob Ihre Tests gut gemacht sind.
Alle diese Architekturen zielen darauf ab, die Geschäftslogik von den Implementierungsdetails zu isolieren. Unabhängig davon, ob Sie Ports und Adapter, Hexagonal oder Clean Architecture verwenden, besteht die Kernidee darin, die Geschäftslogik Framework-unabhängig und einfach zu testen zu machen.
Sobald Sie dies im Kopf haben, gibt es ein umfassendes Spektrum an Implementierungen, und die beste hängt von Ihrem Kontext und Ihren Vorlieben ab. Ein großer Vorteil dieser Architektur besteht darin, dass sie durch die Isolierung der Geschäftslogik wesentlich effizientere Tests ermöglicht.
Ich schätze, Sie sind bereits mit der Testpyramide vertraut, stattdessen verwende ich lieber die Rautendarstellung. Diese von Thomas Pierrain beschriebene Strategie ist anwendungsfallorientiert und konzentriert sich auf das Verhalten unserer Anwendung.
Diese Strategie fördert auch Black-Box-Tests der Anwendung und konzentriert sich dabei auf die Ergebnisse, die sie erzeugt, und nicht darauf, wie sie sie erzeugt. Dieser Ansatz erleichtert das Refactoring erheblich.
Wie Sie im gesamten Workflow, im Test und im Befehlshandler gesehen haben, habe ich immer geschrieben, was ich wollte, bevor es überhaupt existierte. Ich habe ein paar „Wünsche“ geäußert. Dieser Ansatz wird als Wunschdenkenprogrammierung bezeichnet. Es ermöglicht Ihnen, sich die Struktur Ihres Systems und die ideale Art der Interaktion mit ihm vorzustellen, bevor Sie sich für die Implementierung entscheiden. In Kombination mit Tests wird es zu einer leistungsstarken Methode zur Steuerung Ihrer Programmierung.
Dieses Muster hilft dabei, Tests effektiv zu strukturieren. Zunächst wird das System in den richtigen Ausgangszustand versetzt. Als nächstes wird ein Zustandsmodifikator, beispielsweise ein Befehl oder eine Benutzeraktion, ausgeführt. Abschließend wird eine Aussage getroffen, um sicherzustellen, dass sich das System nach der Aktion im erwarteten Zustand befindet.
Wir haben erläutert, wie eine domänenorientierte Architektur in Kombination mit abnahmetestgesteuerter Entwicklung Ihr Entwicklungserlebnis verändern kann. Sofortiges Feedback, robuste Tests und die Fokussierung auf die Absichten der Benutzer machen das Codieren nicht nur effizienter, sondern auch angenehmer.
Probieren Sie diesen Arbeitsablauf aus und Sie werden vielleicht jedes Mal lächeln, wenn ein Test grün wird ✅ ➡️?
Lassen Sie mich in den Kommentaren wissen, was Ihr perfekter Arbeitsablauf wäre oder was Sie in diesem verbessern würden!
Das obige ist der detaillierte Inhalt vonTesten Sie Ihre gesamte Geschäftslogik in weniger als Sekunden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!