Heim >Backend-Entwicklung >PHP-Tutorial >Testen Sie Ihre gesamte Geschäftslogik in weniger als Sekunden

Testen Sie Ihre gesamte Geschäftslogik in weniger als Sekunden

DDD
DDDOriginal
2024-12-05 13:22:10544Durchsuche

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.


Der Arbeitsablauf und die Testfrustrationen

 

Wo beginne ich mein Feature?

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.

 

Keine Angst, es ist nur eine 2.000-Zeilen-Datei ohne Tests?

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.

 

Tests sind quälend langsam

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.

 

Tests brechen nach jedem Refactoring ab

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.


Lass uns träumen! Was ist der perfekte Test?

 

Sofortiges Feedback

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.

 

Konzentrieren Sie sich auf die Verhaltensweisen

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.

 

Der Copilot

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. ?


Einige Anforderungen vor dem Start

 

Das Feature und die Akzeptanztests

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:

Test All your Business Logic in less than econd

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.

 

Domänenzentrierte Architektur

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.

 

Verhaltensorientierte Tests

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!

 

Fake die E/A

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.


Der GPS-Workflow

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.

Test All your Business Logic in less than econd

 

Geben Sie Ihr Ziel ein

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.

 

Arrangieren

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]);

 

Akt

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.

 

Behaupten

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]);

 

Das Ziel

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]);
    }
}

 

Haben Sie keine Angst vor Fehlern

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.

 

Führen Sie die Testkompilierung durch

 

❌ ? : "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! ?

 

Kommen wir zur Geschäftslogik

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]);
    }
}

 

✅ ? : Test All your Business Logic in less than econd

 

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 ??
 

Refactoring-Zeit

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

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 All your Business Logic in less than econd

 

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 = [],
    )
    {
    }
}

 

Der Code

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. ?

 

Andere Beispiele

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:

Test All your Business Logic in less than econd

 

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]);

 

Geschwindigkeit ⚡

 

Arbeitsablauf

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.

 

Testen

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:

 

Test All your Business Logic in less than econd

 

4 Sekunden... ⚡

 

Konzepte

Dieser Workflow liegt an der Schnittstelle mehrerer Konzepte. Lassen Sie mich einige davon vorstellen, damit Sie sie bei Bedarf weiter erkunden können.

 

Beispielkartierung

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.

Gehen Sie weiter:

  • Matt Wynne – Einführung in die Beispielkartierung
  • Aslak Hellesøy – Einführung in die Beispielkartierung
  • Kenny Baas-Schwegler – „Geschichten aus dem wirklichen Leben“ mit DDD & Event Storming verarbeiten
  • Martin Fowler – Gegeben, wann, dann

 

ERSTE

Das „Was wollen wir?“ Ein Teil könnte mit dem Akronym F.I.R.S.T zusammengefasst werden:

  • Schnell
  • Isoliert
  • Wiederholbar
  • Selbstvalidierend
  • Gründlich

Jetzt haben Sie eine Checkliste, um zu wissen, ob Ihre Tests gut gemacht sind.

Gehen Sie weiter

  • Robert C. Martin – Clean Code, Kapitel 9: Unit Tests, F.I.R.S.T

 

Anschlüsse und Adapter, Sechskant-, Zwiebel- und Clean-Architektur

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.

Gehen Sie weiter

  • Alistair Cockburn – Sechseckige Architektur
  • Onkel Bob – Saubere Architektur
  • Herberto Graca – DDD, Hexagonal, Onion, Clean, CQRS, … Wie ich alles zusammenfüge

 

Outside-in-Diamant-Teststrategie

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.

Test All your Business Logic in less than econd

Gehen Sie weiter

  • Thomas Pierrain – Outside-in Diamond ? TDD #1 – ein Stil, der von (und für) gewöhnliche Menschen gemacht wurde
  • Thomas Pierrain – Outside-in Diamond? TDD #2 (Anatomie eines Stils)
  • Thomas Pierrain – Schreiben Sie Antifragilitäts- und domänengesteuerte Tests mit „Outside-in-Diamant“ ◆ TDD

 

 

Wunschdenken-Programmierung

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.

 

Arrangieren, Handeln, Durchsetzen

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.

Gehen Sie weiter

  • Bill Wake – 3A – Arrangieren, Handeln, Durchsetzen

 


Letzte Gedanken

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn