Regeln für die Implementierung von TDD in einem alten Projekt

    Der Artikel „Verschieben der Verantwortung des Repository-Musters“ warf mehrere Fragen auf, die sehr schwer zu beantworten sind. Wird ein Repository benötigt, wenn eine Abstraktion von technischen Details völlig unmöglich ist? Wie kompliziert kann das Repository sein, um die Rechtschreibung beizubehalten? Die Antwort auf diese Fragen hängt von der Betonung des Systemdesigns ab. Wahrscheinlich die schwierigste Frage: Brauche ich überhaupt ein Repository? Das Problem der „fließenden Abstraktion“ und die zunehmende Komplexität der Codierung mit zunehmenden Abstraktionsebenen erlauben es nicht, eine Lösung zu finden, die beide Lager zufriedenstellt. In der Berichterstellung führt das Intentionsdesign beispielsweise zur Erstellung einer großen Anzahl von Methoden für jeden Filter und jede Sortierung, und die generische Lösung verursacht einen großen Codierungsaufwand. Sie können für immer weitermachen ...

    Für eine vollständigere Darstellung habe ich das Problem der Abstraktionen von der Seite der Anwendung in vorgefertigtem Code, in Legacy-Code, betrachtet. In diesem Fall interessiert uns das Repository nur als Werkzeug, um qualitativ hochwertigen und sicheren Code zu erhalten. Natürlich ist dieses Muster nicht das einzige, was für die Anwendung von TDD-Praktiken erforderlich ist. Nachdem ich in mehreren großen Projekten „geschmackloses Essen“ gegessen und beobachtet hatte, was funktioniert und was nicht, kam ich auf einige Regeln, die mir helfen, die TDD-Praktiken zu befolgen. Ich freue mich über konstruktive Kritik und andere Tricks der TDD-Implementierung.

    Vorwort


    Einige bemerken möglicherweise, dass TDD in einem alten Projekt nicht möglich ist. Es gibt eine Meinung, dass verschiedene Arten von Integrationstests (UI-Tests, Ende-zu-Ende) für sie besser geeignet sind, weil Das Verstehen des alten Codes ist zu schwierig. Sie können auch hören, dass das Schreiben von Tests vor der Codierung selbst nur zu einem Zeitverlust führt, weil Wir wissen möglicherweise nicht, wie der Code funktionieren wird. Ich musste in mehreren Projekten arbeiten, in denen ich mich nur auf Integrationstests beschränkte, da Unit-Tests keine Indikatoren sind. Zur gleichen Zeit wurden viele Tests geschrieben, sie starteten eine Reihe von Diensten usw. usw. Infolgedessen konnte nur eine Person dies verstehen, die sie tatsächlich schrieb.

    Während meiner Praxis habe ich es geschafft, in mehreren sehr großen Projekten zu arbeiten, in denen es viel Legacy-Code gab. In einigen gab es Tests, in anderen wollten sie es nur einführen. Ich selbst habe 2 große Projekte ins Leben gerufen. Und überall habe ich irgendwie versucht, den TDD-Ansatz anzuwenden. In den ersten Phasen des Verständnisses wurde TDD als Test First-Entwicklung wahrgenommen. Aber je weiter, desto deutlicher wurden die Unterschiede zwischen diesem vereinfachten Verständnis und der aktuellen Sichtweise, kurz BDD genannt, sichtbar. Unabhängig von der verwendeten Sprache bleiben die Hauptpunkte, die ich als Regeln bezeichnet habe, ähnlich. Jemand kann Parallelen zwischen Regeln und anderen Prinzipien zum Schreiben von gutem Code finden.

    Regel 1: Verwenden Sie Bottom-Up (Inside-Out)


    Diese Regel ist für die Analysemethode und das Software-Design relevanter, wenn neue Codeteile in ein bereits funktionierendes Projekt eingebettet werden.

    Wenn Sie ein neues Projekt entwerfen, ist es absolut selbstverständlich, das gesamte System zu präsentieren. Zu diesem Zeitpunkt steuern Sie sowohl den Komponentensatz als auch die zukünftige Flexibilität der Architektur. Daher können Sie Module schreiben, die auf bequeme und beste Weise miteinander integriert werden. Mit einem solchen Top-Down-Ansatz können Sie die zukünftige Architektur im Voraus gut entwerfen, die erforderlichen Richtlinien beschreiben und eine ganzheitliche Sicht auf das haben, was Sie letztendlich wollen. Nach einer Weile verwandelt sich das Projekt in einen sogenannten Legacy-Code. Und hier beginnt der Spaß.

    In der Phase, in der eine neue Funktionalität in ein vorhandenes Projekt mit einer Reihe von Modulen und Abhängigkeiten zwischen ihnen integriert werden muss, kann es sehr schwierig sein, sie alle in Ihren Kopf zu setzen, damit sich das Design als korrekt herausstellt. Die andere Seite dieses Problems ist der Arbeitsaufwand, der zur Erledigung einer solchen Aufgabe erforderlich ist. Daher ist in diesem Fall der Ansatz von unten effektiver. Mit anderen Worten, Sie erstellen zuerst ein vollständiges Modul, das die erforderliche Aufgabe löst, und binden es dann in das vorhandene System ein, wobei Sie nur die erforderlichen Änderungen vornehmen. In diesem Fall können Sie für die Qualität dieses Moduls bürgen Es stellt eine vollständige Funktionseinheit dar.

    Ich möchte darauf hinweisen, dass bei den Ansätzen nicht alles so einfach ist. Wenn Sie beispielsweise neue Funktionen im alten System entwerfen, verwenden Sie nicht will, sondern beide Ansätze. In der ersten Analyse müssen Sie das System noch bewerten, dann auf die Modulebene absenken, implementieren und dann auf die Ebene des gesamten Systems zurückkehren. Meiner Meinung nach ist die Hauptsache hier nicht zu vergessen, dass das neue Modul als spezifisches Werkzeug vollständig funktionsfähig und unabhängig sein sollte. Je klarer es ist, sich an diesen Ansatz zu halten, desto weniger Änderungen werden am alten Code vorgenommen.

    Regel 2: Nur geänderten Code testen


    Wenn Sie mit einem alten Projekt arbeiten, müssen Sie absolut keine Tests für alle möglichen Szenarien der Methode / Klasse schreiben. Darüber hinaus sind Ihnen möglicherweise einige Szenarien überhaupt nicht bekannt es kann viele von ihnen geben. Das Projekt ist bereits in Produktion, der Kunde ist zufrieden, so dass Sie sich entspannen können. Im allgemeinen Fall führen in einem solchen System nur Ihre Änderungen zu Problemen. Daher sollten nur sie getestet werden.

    Beispiel


    Es gibt ein Online-Shop-Modul, das einen Warenkorb mit ausgewählten Artikeln erstellt und in der Datenbank speichert. Die konkrete Umsetzung stört uns nicht. Wie gemacht wird, so wird gemacht - das ist der Legacy-Code. Jetzt müssen wir hier ein neues Verhalten einführen: Senden Sie eine Benachrichtigung an die Buchhaltungsabteilung, falls die Kosten des Warenkorbs 1000 USD überschreiten. Hier ist der Code, den wir sehen. Wie kann man Veränderungen umsetzen?

    public class EuropeShop : Shop
    {
        public override void CreateSale()
        {
            var items = LoadSelectedItemsFromDb();
            var taxes = new EuropeTaxes();
            var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
            var cart = new Cart();
            cart.Add(saleItems);
            taxes.ApplyTaxes(cart);
            SaveToDb(cart);
        }
    }
    

    Nach der ersten Regel sollten Änderungen minimal und atomar sein. Wir sind nicht daran interessiert, Daten herunterzuladen, wir sind nicht daran interessiert, Steuern zu berechnen und in der Datenbank zu speichern. Wir interessieren uns aber für den bereits berechneten Warenkorb. Wenn es ein Modul gäbe, das das Notwendige tut, würde es die notwendige Aufgabe ausführen. Deshalb machen wir das.

    public class EuropeShop : Shop
    {
        public override void CreateSale()
        {
            var items = LoadSelectedItemsFromDb();
            var taxes = new EuropeTaxes();
            var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
            var cart = new Cart();
            cart.Add(saleItems);
            taxes.ApplyTaxes(cart);
            // NEW FEATURE
            new EuropeShopNotifier().Send(cart);
            SaveToDb(cart);
        }
    }
    

    Ein solcher Notifier funktioniert an sich, kann getestet werden und die am alten Code vorgenommenen Änderungen sind minimal. Genau das sagt die zweite Regel.

    Regel 3: Nur Anforderungen testen


    Überlegen Sie sich, was das Modul tatsächlich benötigt, um nicht auf die Anzahl der Szenarien einzugehen, für die Unit-Tests erforderlich sind. Schreiben Sie zuerst für einen minimalen Satz von Bedingungen, die als Modulanforderungen dargestellt werden können. Die minimale Menge ist eins, wenn eine neue hinzugefügt wird, das Verhalten des Moduls bleibt nahezu unverändert, und wenn es entfernt wird, stellt sich heraus, dass das Modul nicht funktionsfähig ist. Der BDD-Ansatz hilft wirklich dabei, das Gehirn auf den richtigen Weg zu bringen.

    Stellen Sie sich auch vor, wie andere Klassen, die Clients Ihres Moduls sind, damit interagieren. Müssen sie 10 Codezeilen schreiben, um Ihr Modul zu konfigurieren? Je einfacher die Kommunikation zwischen den Teilen des Systems ist, desto besser. Daher ist es besser, die Module, die für etwas Bestimmtes verantwortlich sind, vom alten Code zu isolieren. Hier hilft Ihnen SOLID.

    Beispiel


    Nun wollen wir sehen, wie alles, was oben geschrieben wurde, uns beim Code hilft. Zunächst wählen wir alle Module aus, die nur indirekt mit der Erstellung des Warenkorbs zusammenhängen. So wird die Verantwortung auf die Module verteilt.

    public class EuropeShop : Shop
    {
        public override void CreateSale()
        {
            // 1) load from DB
            var items = LoadSelectedItemsFromDb();
            // 2) Tax-object creates SaleItem and
            // 4) goes through items and apply taxes
            var taxes = new EuropeTaxes();
            var saleItems = items.Select(item => taxes.ApplyTaxes(item)).ToList();
            // 3) creates a cart and 4) applies taxes
            var cart = new Cart();
            cart.Add(saleItems);
            taxes.ApplyTaxes(cart);
            new EuropeShopNotifier().Send(cart);
            // 4) store to DB
            SaveToDb(cart);
        }
    }
    

    Und so können sie unterschieden werden. Solche Änderungen zu einem bestimmten Zeitpunkt sind in einem großen System natürlich nicht möglich, können jedoch schrittweise vorgenommen werden. Wenn beispielsweise Änderungen das Steuermodul betreffen, können Abhängigkeiten von anderen Teilen des Systems vereinfacht werden. Dies kann helfen, starke Abhängigkeiten von ihm loszuwerden und in Zukunft als autarkes Werkzeug zu verwenden.

    public class EuropeShop : Shop
    {
        public override void CreateSale()
        {
            // 1) extracted to a repository
            var itemsRepository = new ItemsRepository();
            var items = itemsRepository.LoadSelectedItems();
            // 2) extracted to a mapper
            var saleItems = items.ConvertToSaleItems();
            // 3) still creates a cart
            var cart = new Cart();
            cart.Add(saleItems);
            // 4) all routines to apply taxes are extracted to the Tax-object
            new EuropeTaxes().ApplyTaxes(cart);
            new EuropeShopNotifier().Send(cart);
            // 5) extracted to a repository
            itemsRepository.Save(cart);
        }
    }
    

    Bei den Tests kann auf diese Szenarien verzichtet werden. Während ihrer Umsetzung sind wir nicht interessiert.

    public class EuropeTaxesTests
    {
        public void Should_not_fail_for_null() { }
        public void Should_apply_taxes_to_items() { }
        public void Should_apply_taxes_to_whole_cart() { }
        public void Should_apply_taxes_to_whole_cart_and_change_items() { }
    }
    public class EuropeShopNotifierTests
    {
        public void Should_not_send_when_less_or_equals_to_1000() { }
        public void Should_send_when_greater_than_1000() { }
        public void Should_raise_exception_when_cannot_send() { }
    }
    

    Regel 4: Nur getesteten Code hinzufügen


    Wie ich oben geschrieben habe, sollten Sie Änderungen am alten Code minimieren. Dazu können der alte und der neue / geänderte Code getrennt werden. Der neue Code kann in Methoden unterschieden werden, deren Funktion mit Unit-Tests überprüft werden kann. Dieser Ansatz wird dazu beitragen, die damit verbundenen Risiken zu verringern. Es gibt zwei Techniken, die unter Effektives Arbeiten mit Legacy-Code beschrieben wurden (Link zum folgenden Buch).

    Sprout-Methode / Klasse - Mit dieser Technik können Sie den neuen Code sehr sicher in den alten Code einbetten. Die Art und Weise, wie ich einen Notifier hinzugefügt habe, ist ein Beispiel für diesen Ansatz.

    Wickelmethode - etwas komplizierter, aber das Wesentliche ist dasselbe. Es ist nicht immer geeignet, aber nur in Fällen, in denen ein neuer Code vor / nach dem alten aufgerufen wird. Bei der Zuweisung von Verantwortlichkeiten wurden zwei Aufrufe der ApplyTaxes-Methode durch einen Aufruf ersetzt. Zu diesem Zweck musste die zweite Methode geändert werden, damit die Arbeitslogik nicht viel brach und überprüft werden konnte. So sah die Klasse vor den Änderungen aus.

    public class EuropeTaxes : Taxes
    {
        internal override SaleItem ApplyTaxes(Item item)
        {
            var saleItem = new SaleItem(item)
            {
                SalePrice = item.Price*1.2m
            };
            return saleItem;
        }
        internal override void ApplyTaxes(Cart cart)
        {
            if (cart.TotalSalePrice <= 300m) return;
            var exclusion = 30m/cart.SaleItems.Count;
            foreach (var item in cart.SaleItems)
                if (item.SalePrice - exclusion > 100m)
                    item.SalePrice -= exclusion;
        }
    }
    

    Und so danach. Die Logik der Arbeit mit den Elementen des Korbs hat sich ein wenig geändert, aber im Allgemeinen ist alles gleich geblieben. In diesem Fall ruft die alte Methode zuerst die neuen ApplyToItems und dann die vorherige Version auf. Dies ist die Essenz dieser Technik.

    public class EuropeTaxes : Taxes
    {
        internal override void ApplyTaxes(Cart cart)
        {
            ApplyToItems(cart);
            ApplyToCart(cart);
        }
        private void ApplyToItems(Cart cart)
        {
            foreach (var item in cart.SaleItems)
                item.SalePrice = item.Price*1.2m;
        }
        private void ApplyToCart(Cart cart)
        {
            if (cart.TotalSalePrice <= 300m) return;
            var exclusion = 30m / cart.SaleItems.Count;
            foreach (var item in cart.SaleItems)
                if (item.SalePrice - exclusion > 100m)
                    item.SalePrice -= exclusion;
        }
    }
    

    Regel 5: Versteckte Abhängigkeiten „brechen“


    In dieser Regel geht es um das größte Übel im alten Code: um die Verwendung des neuen Operators innerhalb der Methode eines BO, um andere BOs, Repositorys oder andere komplizierte Objekte zu erstellen. Warum ist das so schlimm? Die einfachste Erklärung: Dadurch werden Teile des Systems stark miteinander verbunden und ihre Konsistenz verringert. Noch kürzer: Es verstößt gegen das Prinzip „geringe Kopplung, hohe Kohäsion“. Wenn Sie von der anderen Seite schauen, ist es zu schwierig, solchen Code in ein separates, unabhängiges Tool zu unterteilen. Es ist sehr zeitaufwändig, solche versteckten Abhängigkeiten gleichzeitig zu beseitigen. Dies kann jedoch schrittweise erfolgen.

    Zunächst sollten Sie die Initialisierung aller Abhängigkeiten an den Konstruktor übertragen. Dies gilt insbesondere für neue Operatoren und die Erstellung von Klassen. Wenn Sie über einen ServiceLocator zum Empfangen von Klasseninstanzen verfügen, sollte dieser auch in den Konstruktor entfernt werden, in dem Sie alle erforderlichen Schnittstellen abrufen können.

    Zweitens sollten die Variablen, in denen die Instanz des externen BO / Repository gespeichert ist, einen abstrakten Typ haben, sondern eine Schnittstelle. Die Schnittstelle ist besser, weil Löse die Hände des Entwicklers mehr. Dies wird es letztendlich ermöglichen, aus einem Modul ein atomares Instrument zu machen.

    Drittens lassen Sie keine großen Blattmethoden. Dies ist ein klares Zeichen dafür, dass die Methode mehr leistet als im Namen angegeben. Und dann deutet dies auf eine mögliche Verletzung von SOLID, dem Gesetz von Demeter, hin. Sowie Logik und die Erdordnung.

    Beispiel


    Nun wollen wir sehen, wie sich der Code, der den Warenkorb nach dem obigen erstellt, geändert hat. Nur der Codeblock, der den Warenkorb erstellt, bleibt unverändert. Der Rest wird externen Klassen zugeordnet und kann durch jede Implementierung ersetzt werden. Jetzt nimmt die EuropeShop-Klasse die Form eines atomaren Werkzeugs an, das bestimmte Dinge benötigt, die im Konstruktor explizit dargestellt werden. Code wird leichter zu verstehen.

    public class EuropeShop : Shop
    {
        private readonly IItemsRepository _itemsRepository;
        private readonly Taxes.Taxes _europeTaxes;
        private readonly INotifier _europeShopNotifier;
        public EuropeShop()
        {
            _itemsRepository = new ItemsRepository();
            _europeTaxes = new EuropeTaxes();
            _europeShopNotifier = new EuropeShopNotifier();
        }
        public override void CreateSale()
        {
            var items = _itemsRepository.LoadSelectedItems();
            var saleItems = items.ConvertToSaleItems();
            var cart = new Cart();
            cart.Add(saleItems);
            _europeTaxes.ApplyTaxes(cart);
            _europeShopNotifier.Send(cart);
            _itemsRepository.Save(cart);
        }
    }
    

    Regel 6: Je weniger große Tests, desto besser


    Große Tests sind verschiedene Integrationstests, mit denen versucht wird, Benutzerskripte zu testen. Sicher, sie sind wichtig, aber die Überprüfung der Logik einiger IF auf der Rückseite des Codes ist sehr teuer. Infolgedessen kann nur ein Entwickler, nur in einem speziellen Anzug, der mit Amuletten überzogen ist, dort etwas ändern. Das Schreiben eines solchen Tests erfordert genauso viel Zeit, wenn nicht mehr, als das Schreiben der Funktion selbst. Ihre Unterstützung ist nur ein weiterer Legacy-Code, dessen Änderung beängstigend ist. Dies sind jedoch nur Tests!

    Um nicht auf die Trauer der Designer zu treten, die versuchen, ihre Löcher mit Integrationstests zu testen, und hoffen, dass sie vor einem möglichen Fakap gewarnt werden, ist es notwendig, die erforderlichen Tests zu trennen und diese Trennung klar einzuhalten. Wenn Sie eine Integrationsüberprüfung benötigen, schreiben Sie eine minimale Anzahl von Tests, einschließlich positiver und negativer Interaktionsszenarien. Wenn Sie den Algorithmus überprüfen müssen, schreiben Sie Komponententests und beschränken Sie sich auf einen minimalen Satz.

    Regel 7: Testen Sie keine privaten Methoden


    Wenn Sie plötzlich eine private Methode testen wollten, haben Sie sich anscheinend nach Krücken gesehnt. Einige sehen daran nichts auszusetzen. Aber schauen wir uns die Gründe für Ihre "Wunschliste" an. Die private Methode ist möglicherweise zu komplex oder enthält Code, der nicht von öffentlichen Methoden aufgerufen wird. Ich bin sicher, dass sich jeder andere Grund, den Sie sich vorstellen können, als Merkmal eines „schlechten“ Codes oder Designs herausstellen wird. Höchstwahrscheinlich sollte ein Teil des Codes aus der privaten Methode einer separaten Methode / Klasse zugeordnet werden. Überprüfen Sie, ob das erste SOLID-Prinzip verletzt wird. Dies ist der erste Grund, warum sich dies nicht lohnt. Das zweite ist, dass Sie auf diese Weise nicht das Verhalten des gesamten Moduls überprüfen, sondern wie es funktioniert. Die interne Implementierung kann unabhängig vom Modulverhalten variieren. Daher erhalten Sie in diesem Fall fragile Tests,

    Stellen Sie sich Ihre Klassen als eine Reihe atomarer Werkzeuge vor, von denen Sie nichts wissen, um zu vermeiden, dass private Methoden getestet werden müssen. Sie erwarten ein Verhalten, das Sie testen. Diese Ansicht gilt auch für Assemblyklassen. Die Klassen, die Clients (aus anderen Assemblys) zur Verfügung stehen, sind öffentlich, und diejenigen, die interne Arbeiten ausführen, sind privat. Es gibt jedoch einen Unterschied zu den Methoden. Interne Klassen können komplex sein, sodass sie auch intern erstellt und getestet werden können.

    Beispiel


    Um beispielsweise eine Bedingung in einer privaten Methode der EuropeTaxes-Klasse zu testen, schreibe ich keinen Test für diese Methode. Ich werde erwarten, dass Steuern auf eine bestimmte Weise angewendet werden, also wird der Test genau das widerspiegeln. Im Test selbst habe ich mit Stiften gezählt, was sich herausstellen sollte, es als Standard genommen und das gleiche Ergebnis von der Klasse erwartet.

    public class EuropeTaxes : Taxes
    {
        // code skipped
        private void ApplyToCart(Cart cart)
        {
            if (cart.TotalSalePrice <= 300m) return; // <<< I WANT TO TEST THIS CONDIFTION
            var exclusion = 30m / cart.SaleItems.Count;
            foreach (var item in cart.SaleItems)
                if (item.SalePrice - exclusion > 100m)
                    item.SalePrice -= exclusion;
        }
    }
    // test suite
    public class EuropeTaxesTests
    {
        // code skipped
        [Fact]
        public void Should_apply_taxes_to_cart_greater_300()
        {
            #region arrange
            // list of items which will create a cart greater 300
            var saleItems = new List(new[]{new Item {Price = 83.34m},
                new Item {Price = 83.34m},new Item {Price = 83.34m}})
                .ConvertToSaleItems();
            var cart = new Cart();
            cart.Add(saleItems);
            const decimal expected = 83.34m*3*1.2m;
            #endregion
            // act
            new EuropeTaxes().ApplyTaxes(cart);
            // assert
            Assert.Equal(expected, cart.TotalSalePrice);
        }
    }
    

    Regel 8: Testen Sie keine Methodenalgorithmen


    Hier wurde der Name der Regel erfolglos ausgewählt, hat aber noch nicht die besten gefunden. Unter den „Mochisten“ (diejenigen, die in den Tests nass werden) gibt es diejenigen, die die Anzahl der Aufrufe bestimmter Methoden, den Aufruf selbst usw. überprüfen. Mit anderen Worten, sie überprüfen die interne Funktionsweise der Methoden. Dies ist genauso schlecht wie private Tests. Der Unterschied besteht nur in der Höhe der Anwendung einer solchen Prüfung. Dieser Ansatz führt wiederum zu vielen fragilen Tests, weshalb TDD von einigen nicht als normal empfunden wird.

    Regel 9: Ändern Sie Legacy-Code nicht ohne Tests


    Dies ist die wichtigste Regel, weil spiegelt den Wunsch des Teams wider, einem solchen Weg zu folgen. Ohne den Wunsch, sich in diese Richtung zu bewegen, hat alles, was oben gesagt wurde, keine besondere Bedeutung. Weil Wenn der Entwickler TDD nicht verwenden möchte (seine Bedeutung nicht versteht, die Vorteile nicht sieht usw.), werden seine tatsächlichen Vorteile durch die ständige Diskussion darüber, wie schwierig und ineffektiv es ist, untergraben.

    Wenn Sie TDD anwenden möchten, besprechen Sie dies in einem Team, fügen Sie es zur Definition von Fertig hinzu und bewerben Sie sich. Zuerst wird es schwer, wie bei allem Neuen. Wie jede Kunst erfordert TDD ständige Übung, und das Vergnügen kommt, wenn Sie lernen. Allmählich wird es viele schriftliche Komponententests geben, Sie werden beginnen, die Gesundheit Ihres Systems zu spüren und die Einfachheit des Schreibens von Code zu schätzen, indem Sie die Anforderungen in der ersten Phase beschreiben. Es gibt TDD-Studien, die an echten Großprojekten bei Microsoft und IBM durchgeführt wurden und eine Reduzierung der Fehler in Produktionssystemen von 40% auf 80% zeigen. (siehe Link unten).

    Optional


    1. Buch „Effektiv mit Legacy-Code arbeiten“ von Michael Feathers
    2. TDD, wenn Sie im Legacy-Code bis zum Hals sind
    3. Versteckte Abhängigkeiten auflösen
    4. Der Legacy-Code-Lebenszyklus
    5. Sollten Sie private Methoden für eine Klasse testen?
    6. Unit Testing Interna
    7. 5 Häufige Missverständnisse über TDD- und Unit-Tests
    8. Einführung in Unit Testing 5: Invasion von Legacy-Code im Namen der Testbarkeit
    9. Gesetz des Demeters

    Jetzt auch beliebt: