Kapitel 1. Das Geldproblem

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Ich würde keinen Pfifferling für die Einfachheit diesseits der Komplexität geben, aber ich würde mein Leben für die Einfachheit auf der anderen Seite der Komplexität geben.

Oliver Wendell Holmes Jr.

Unsere Entwicklungsumgebung ist fertig. In diesem Kapitel lernen wir die drei Phasen kennen, die die testgetriebene Entwicklung unterstützen. Anschließend schreiben wir unser erstes Code-Feature mit Hilfe der testgetriebenen Entwicklung.

Rot-Grün-Refactor: Die Bausteine von TDD

Die testgetriebene Entwicklung folgt einem dreistufigen Prozess:

  1. Rot. Wir schreiben einen fehlgeschlagenen Test (einschließlich möglicher Kompilierungsfehler). Wir führen die Testsuite aus, um die fehlgeschlagenen Tests zu überprüfen.

  2. Grün. Wir schreiben gerade so viel Produktionscode, dass der Test grün wird. Wir führen die Testsuite aus, um dies zu überprüfen.

  3. Refactor. Wir beseitigen alle Code-Muffel. Diese können durch Duplikation, fest kodierte Werte oder unsachgemäße Verwendung von Sprach-Idiomen entstehen (z. B. die Verwendung einer ausführlichen Schleife anstelle eines eingebauten Iterators). Wenn wir während des Refactorings einen Test kaputt machen, müssen wir ihn vorrangig zurückbekommen, bevor wir diese Phase beenden.

Dies ist der Rot-Grün-Refactor-Zyklus (RGR), der in Abbildung 1-1 dargestellt ist. Die drei Phasen dieses Zyklus sind die wesentlichen Bausteine der testgetriebenen Entwicklung. Der gesamte Code, den wir in diesem Buch entwickeln, wird diesem Zyklus folgen.

The Red Green Refactor (RGR) cycle. Red: write a failing test. Green: write just enough production code to pass the test. Refactor: remove code smells, e.g. duplication or poor design, without adding new features.
Abbildung 1-1. Der Rot-Grün-Refactor-Zyklus ist das Fundament, auf dem die testgetriebene Entwicklung ruht
Wichtig

Die drei Phasen des Rot-Grün-Refactor-Zyklus sind die wesentlichen Bausteine von TDD.

Was ist das Problem?

Wir haben ein Geldproblem. Nein, nicht das, das fast jeder hat: nicht genug davon zu haben! Es ist eher ein "Wir wollen den Überblick über unser Geld behalten"-Problem.

Angenommen, wir müssen eine Tabellenkalkulation erstellen, um Geld in mehr als einer Währung zu verwalten, vielleicht um ein Aktienportfolio zu verwalten.

Lager Börse Aktien Aktienkurs Gesamt

IBM

NASDAQ

100

124 USD

12400 USD

BMW

DAX

400

75 EUR

30000 EUR

Samsung

KSE

300

68000 KRW

20400000 KRW

Um diese Tabelle zu erstellen, müssen wir einfache arithmetische Operationen mit Zahlen in einer beliebigen Währung durchführen:

5 USD × 2 = 10 USD

10 EUR × 2 = 20 EUR

4002 KRW / 4 = 1000,5 KRW

Wir würden auch gerne zwischen Währungen umrechnen. Wenn wir zum Beispiel 1 EUR umtauschen, erhalten wir 1,2 USD, und wenn wir 1 USD umtauschen, erhalten wir 1100 KRW:

5 USD + 10 EUR = 17 USD

1 USD + 1100 KRW = 2200 KRW

Jeder der oben genannten Punkte wird ein (klitzekleines) Feature sein, das wir mit TDD implementieren werden. Wir haben bereits mehrere Funktionen zu implementieren. Damit wir uns auf eine Sache konzentrieren können, heben wir die Funktion, an der wir gerade arbeiten , fett hervor. Wenn wir mit einem Feature fertig sind, streichen wires durch, um unsere Zufriedenheit zu signalisieren.

Wo sollen wir also anfangen? Falls der Titel dieses Buches nicht schon verrät, dass wir mit dem Schreiben eines Tests beginnen.

Unsere erste fehlgeschlagene Prüfung

Beginnen wir mit der Implementierung des allerersten Features in unserer Liste:

5 USD × 2 = 10 USD

10 EUR × 2 = 20 EUR

4002 KRW / 4 = 1000,5 KRW

5 USD + 10 EUR = 17 USD

1 USD + 1100 KRW = 2200 KRW

Wir beginnen damit, einen fehlgeschlagenen Test zu schreiben, der der roten Phase des RGR-Zyklus entspricht.

Geh

In einer neuen Datei namens money_test.go im Ordner go schreiben wir unseren ersten Test:

package main 1

import (
    "testing" 2
)

func TestMultiplication(t *testing.T) { 3
    fiver := Dollar{ 4
        amount: 5,
    }
    tenner := fiver.Times(2) 5
    if tenner.amount != 10 { 6
        t.Errorf("Expected 10, got: [%d]", tenner.amount) 7
    }
}
1

Deklaration der Verpackung

2

Importiertes "Test"-Paket, das später in t.Errorf verwendet wird

3

Unsere Prüfmethode, die mit Test beginnen und ein *testing.T Argument haben muss

4

Struktur, die "USD 5." darstellt Dollar existiert noch nicht

5

Zu prüfende Methode: Times- die es ebenfalls noch nicht gibt

6

Vergleich des tatsächlichen Wertes mit dem erwarteten Wert

7

Sicherstellen, dass die Prüfung fehlschlägt, wenn der erwartete Wert nicht mit dem tatsächlichen Wert übereinstimmt

Diese Prüffunktion enthält ein wenig Boilerplate-Code.

Die package main deklariert, dass der gesamte nachfolgende Code Teil des main Pakets ist. Dies ist eine Voraussetzung für eigenständig ausführbare Go-Programme. Die Paketverwaltung ist eine ausgeklügelte Funktion in Go. Sie wird in Kapitel 5 ausführlicher behandelt.

Als Nächstes importieren wir das Paket testing mit der Anweisung import. Dieses Paket wird in dem Einheitstest verwendet.

Der Einheitstest function ist der größte Teil des Codes. Wir deklarieren eine Entität, die "5 USD" repräsentiert. Das ist die Variable mit dem Namen fiver, die wir mit einer Struktur initialisieren, die 5 im Feld amount enthält. Dann multiplizieren wir fiver mit 2. Und wir erwarten, dass das Ergebnis 10 Dollar ist, d.h. eine Variable tenner, deren Feld amount gleich 10 sein muss. Wenn das nicht der Fall ist, geben wir eine schön formatierte Fehlermeldung mit dem tatsächlichen Wert aus (was auch immer das sein mag).

Wenn wir diesen Test mit "go test -v ." aus dem TDD Project Root Ordner ausführen, sollten wir einen Fehler erhalten:

... undefined: Dollar
FAIL	tdd [build failed]
FAIL

Wir bekommen die Nachricht laut und deutlich: Das ist unser erster fehlgeschlagener Test!

Tipp

"go test -v ." führt die Tests im aktuellen Ordner durch, und "go test -v ./..."1 führt die Tests im aktuellen Ordner und allen Unterordnern durch. Der Schalter -v erzeugt eine ausführliche Ausgabe.

JavaScript

In einer neuen Datei namens test_money.js im Ordner js schreiben wir unseren ersten Test:

const assert = require('assert'); 1

let fiver = new Dollar(5); 2
let tenner = fiver.times(2); 3
assert.strictEqual(tenner.amount, 10); 4
1

Importieren des Pakets assert, das später für die Assertion benötigt wird

2

Objekt, das "USD 5." darstellt Dollar existiert noch nicht

3

Zu prüfende Methode: times- die es ebenfalls noch nicht gibt

4

Vergleich des tatsächlichen Wertes mit dem erwarteten Wert in einer strictEqual assert-Anweisung

JavaScript hat nur wenig Boilerplate-Code - die einzige Zeile neben dem Testcode ist die Anweisung require. Damit haben wir Zugriff auf das NPM-Paket assert.

Nach dieser Zeile folgen die drei Codezeilen, die unseren Test bilden. Wir erstellen ein Objekt, das 5 USD darstellt, multiplizieren es mit 2 und erwarten, dass das Ergebnis 10 ist.

Wichtig

ES2015 führte das let Schlüsselwort für die Deklaration von Variablen und das const Schlüsselwort für die Deklaration von Konstanten.

Wenn wir diesen Code aus dem TDD-Projektstammordner mit node js/test_money.js ausführen, sollten wir eine Fehlermeldung erhalten, die wie folgt beginnt:

ReferenceError: Dollar is not defined

Das ist unser erster fehlgeschlagener Test. Hurra!

Tipp

node file.js führt den JavaScript-Code in file.js aus und erzeugt eine Ausgabe. Wir verwenden diesen Befehl, um unsere Tests durchzuführen.

Python

In einer neuen Datei namens test_money.py im Ordner py schreiben wir unseren ersten Test:

import unittest 1

class TestMoney(unittest.TestCase): 2
  def testMultiplication(self): 3
    fiver = Dollar(5) 4
    tenner = fiver.times(2) 5
    self.assertEqual(10, tenner.amount) 6

if __name__ == '__main__': 7
    unittest.main()
1

Importiere das Paket unittest, das für die Superklasse TestCase benötigt.

2

Unsere Testklasse, die eine Unterklasse der Klasse unittest.TestCase sein muss.

3

Der Name unserer Methode muss mit test beginnen, um als Prüfmethode zu gelten.

4

Das Objekt, das "USD 5." Dollar repräsentiert, existiert noch nicht.

5

Zu prüfende Methode: times- die es ebenfalls noch nicht gibt.

6

Vergleich des tatsächlichen Wertes mit dem erwarteten Wert in einer assertEqual Erklärung.

7

Das main Idiom sorgt dafür, dass diese Klasse als Skript ausgeführt werden kann.

Python erfordert importdas Paket unittest, die Erstellung einer Klasse, die TestCase untergeordnet ist, und defdie Erstellung einer Funktion, deren Name mit test beginnt. Um die Klasse als eigenständiges Programm ausführen zu können, brauchen wir das übliche Python-Idiom, das die Funktion unittest.main() ausführt, wenn test_money.py direkt ausgeführt wird.

Die Testfunktion beschreibt, wie wir erwarten, dass unser Code funktioniert. Wir definieren eine Variable mit dem Namen fiver und initialisieren sie für eine gewünschte (aber noch zu erstellende) Klasse Dollar mit 5 als Konstruktorargument. Dann multiplizieren wir fiver mit 2 und speichern das Ergebnis in einer Variablen tenner. Schließlich erwarten wir, dass amount in tenner zu 10 wird.

Wenn wir diesen Code aus dem Ordner TDD_PROJECT_ROOT mit python3 py/test_money.py -v ausführen, erhalten wir einen Fehler:

NameError: name 'Dollar' is not defined

Das ist unser erster fehlgeschlagener Test. Hurra!

Tipp

python3 file.py -v führt den Python-Code in file.py aus und erzeugt eine ausführliche Ausgabe. Wir verwenden diesen Befehl, um unsere Tests durchzuführen.

Auf Grün setzen

Wir haben unsere Tests so geschrieben, wie wir sie erwarten würden, und dabei alle Syntaxfehler vorerst ignoriert. Ist das klug?

Ganz am Anfang - und da sind wir jetzt - ist es klug, mit dem kleinsten Stückchen Code zu beginnen, das uns auf den Weg zum Fortschritt bringt. Natürlich schlagen unsere Tests fehl, weil wir nicht definiert haben, was Dollar ist. Das mag der perfekte Zeitpunkt sein, um zu sagen: "Duh!" Aber aus diesen beiden Gründen ist ein bisschen Geduld angebracht:

  1. Wir haben gerade den ersten Schritt unseres ersten Tests hinter uns gebracht - das Erreichen von Rot. Das ist nicht nur der Anfang, es ist der Anfang vom Anfang.

  2. Wir können (und werden) die Schritte beschleunigen, während wir vorankommen. Aber es ist wichtig zu wissen, dass wir auch langsamer werden können, wenn wir es brauchen.

Die nächste Phase im RGR-Zyklus besteht darin, grün zu werden.

Es ist klar, dass wir eine Abstraktion Dollar einführen müssen. In diesem Abschnitt wird beschrieben, wie wir diese und andere Abstraktionen einführen, um unseren Test zu bestehen.

Geh

Füge ein leeres Dollar struct an am Ende von money_test.go hinzu.

type Dollar struct {
}

Wenn wir den Test jetzt ausführen, erhalten wir einen neuen Fehler:

... unknown field 'amount' in struct literal of type Dollar

Fortschritt!

Die Fehlermeldung weist uns darauf hin, dass wir ein Feld namens amount in unsere Dollar Struktur einfügen sollen. Das tun wir also, wobei wir zunächst einen int Datentyp verwenden (der für unser Ziel ausreichend ist):

type Dollar struct {
    amount int
}

Wenn du Dollar struct hinzufügst, führt das ziemlich sicher zum nächsten Fehler:

... fiver.Times undefined (type Dollar has no field or method Times)

Wir sehen hier ein Muster: Wenn etwas (ein Feld oder eine Methode) undefiniert ist, erhalten wir diesen undefined Fehler von der Go-Laufzeit. Wir werden diese Informationen nutzen, um unsere TDD-Zyklen in Zukunft zu beschleunigen. Für den Moment fügen wir eine function namens Times hinzu. Aus unserem Test wissen wir, dass diese Funktion eine Zahl (den Multiplikator) nehmen und eine andere Zahl (das Ergebnis) zurückgeben muss.

Aber wie sollen wir das Ergebnis berechnen? Wir kennen die Grundrechenarten: wie man zwei Zahlen multipliziert. Aber wenn wir den einfachsten Code schreiben würden, der funktioniert, wäre es gerechtfertigt, dass wir immer das Ergebnis zurückgeben, das unser Test erwartet, also eine Struktur, die 10 Dollar darstellt:

func (d Dollar) Times(multiplier int) Dollar {
    return Dollar{10}
}

Wenn wir unseren Code jetzt ausführen, sollten wir eine kurze und knappe Antwort auf unserem Terminal erhalten:

=== RUN   TestMultiplication
--- PASS: TestMultiplication (0.00s)
PASS

Das ist das Zauberwort: Wir haben unseren Test PASS gemacht!

JavaScript

In test_money.js definierst du direkt nach die Zeile const assert = require('assert'); und eine leere Klasse namens Dollar:

class Dollar {
}

Wenn wir jetzt die Datei test_money.js ausführen, erhalten wir einen Fehler:

TypeError: fiver.times is not a function

Fortschritt! Die Fehlermeldung besagt eindeutig, dass für das Objekt mit dem Namen fiver keine Funktion mit dem Namen times definiert ist. Führen wir sie also innerhalb der Klasse Dollar ein:

class Dollar {
    times(multiplier) {
    }
}

Das Ausführen des Tests führt nun zu einem neuen Fehler:

TypeError: Cannot read properties of undefined (reading 'amount') 1
1

Diese Meldung stammt von Node.js v16; v14 erzeugt eine etwas andere Fehlermeldung

Unser Test erwartet ein Objekt mit einer Eigenschaft amount. Da wir von unserer Methode times nichts zurückgeben, ist der Rückgabewert undefined, der keine Eigenschaft amount (oder eine andere Eigenschaft) hat.

Tipp

In der Sprache JavaScript werden bei Funktionen und Methoden keine Rückgabetypen explizit deklariert. Wenn wir das Ergebnis einer Funktion untersuchen, die nichts zurückgibt, werden wir feststellen, dass der Rückgabewert undefined ist.

Wie können wir also unseren Test grün gestalten? Was ist das Einfachste, was funktionieren könnte? Wie wäre es, wenn wir immer ein Objekt erstellen, das 10 USD repräsentiert, und es zurückgeben?

Lass es uns ausprobieren. Wir fügen eine constructor hinzu, die Objekte auf einen bestimmten Betrag initialisiert, und eine times Methode, die hartnäckig "10 USD"-Objekte erstellt und zurückgibt:

class Dollar {
    constructor(amount) { 1
        this.amount = amount; 2
    }

    times(multiplier) { 3
        return new Dollar(10); 4
    }
}
1

Die Funktion constructor wird immer dann aufgerufen, wenn ein Dollar Objekt erstellt wird.

2

Initialisiere die Variable this.amount mit dem angegebenen Parameter.

3

Die Methode times benötigt einen Parameter.

4

Einfache Umsetzung: immer 10 Dollar zurückgeben.

Wenn wir unseren Code jetzt ausführen, sollten wir keine Fehler erhalten. Das ist unser erster grüner Test!

Wichtig

Da strictEqual und die anderen Methoden des Pakets assert nur dann eine Ausgabe erzeugen, wenn die Assertions fehlschlagen, ist ein erfolgreicher Testlauf ziemlich still und gibt keine Ausgabe aus. Wir werden dieses Verhalten in Kapitel 6 verbessern.

Python

Da 'Dollar' is not defined, lass uns in test_money.py vor unserer TestMoney Klasse definieren:

class Dollar:
  pass

Wenn wir jetzt unseren Code ausführen, erhalten wir einen Fehler:

TypeError: Dollar() takes no arguments

Fortschritt! Der Fehler sagt uns eindeutig, dass es derzeit keine Möglichkeit gibt, Objekte mit Argumenten zu initialisieren. Dollar Objekte mit Argumenten zu initialisieren, wie die 5 und 10, die wir in unserem Code haben. Beheben wir das Problem, indem wir den kürzest möglichen Initialisierer bereitstellen:

class Dollar:
  def __init__(self, amount):
    pass

Jetzt ändert sich die Fehlermeldung aus unserem Test:

AttributeError: 'Dollar' object has no attribute 'times'

Wir sehen hier ein Muster: Unser Test schlägt immer noch fehl, aber jedes Mal aus etwas anderen Gründen. Wenn wir unsere Abstraktionen definieren - zuerst Dollar und dann ein amount Feld - werden die Fehlermeldungen "besser". Das ist ein Markenzeichen von TDD: stetiger Fortschritt in einem Tempo, das wir kontrollieren.

Beschleunigen wir die Dinge ein wenig, indem wir eine times Funktion definieren und ihr das Mindestverhalten geben, um grün zu werden. Was ist das notwendige Mindestverhalten? Die Rückgabe eines "Zehn-Dollar-Objekts", das für unseren Test benötigt wird, natürlich!

class Dollar:
  def __init__(self, amount): 1
    self.amount = amount 2

  def times(self, multiplier): 3
    return Dollar(10) 4
1

Die Funktion __init__ wird immer dann aufgerufen, wenn ein Dollar Objekt erstellt wird.

2

Initialisiere die Variable self.amount mit dem angegebenen Parameter.

3

Die Methode times benötigt einen Parameter.

4

Eine einfache Umsetzung bedeutet, dass du immer 10 Dollar zurückbekommst.

Wenn wir unseren Test jetzt durchführen, erhalten wir eine kurze und knappe Antwort:

Ran 1 test in 0.000s

OK

Es ist möglich, dass der Test nicht unter 0.000s läuft, aber lass uns das Zauberwort OK nicht aus den Augen verlieren. Dies ist unser erster grüner Test!

Aufräumen

Fühlst du dich verwirrt, weil wir in unseren Tests den Wert "10 USD" fest einkodiert haben? Keine Sorge: In der Refactoring-Phase können wir dieses Unbehagen beseitigen, indem wir herausfinden, wie wir den fest codierten und doppelten Wert "10 USD" entfernen können.

Refactor ist die dritte und letzte Phase des RGR-Zyklus. Auch wenn wir zu diesem Zeitpunkt noch nicht viele Codezeilen haben, ist es trotzdem wichtig, dass alles ordentlich und kompakt bleibt. Wenn wir Formatierungsfehler oder auskommentierte Codezeilen haben, ist es jetzt an der Zeit, sie zu bereinigen.

Noch wichtiger ist es, Doppelungen zu entfernen und den Code lesbar zu machen. Auf den ersten Blick sieht es so aus, als ob es in den etwa 20 Codezeilen, die wir geschrieben haben, keine Überschneidungen geben kann. Es gibt jedoch bereits ein paar subtile, aber wichtige Überschneidungen.

Wir können diese Duplikation finden, indem wir ein paar Macken in unserem Code bemerken:

  1. Wir haben gerade genug Code geschrieben, um zu überprüfen, dass "eine Verdopplung von 5 Dollar 10 Dollar ergibt". Wenn wir uns entscheiden, unseren bestehenden Test so zu ändern, dass er sagt: "Wenn wir 10 Dollar verdoppeln, sollten wir 20 Dollar erhalten" - eine ebenso sinnvolle Aussage - müssen wir sowohl unseren Test als auch unseren Dollar Code ändern. Es besteht eine Abhängigkeit, eine logische Kopplung, zwischen den beiden Codesegmenten. Im Allgemeinen sollte eine solche Kopplung vermieden werden.

  2. Sowohl in unserem Test als auch in unserem Code hatten wir die magische Zahl 10. Wie sind wir auf diese Zahl gekommen? Offensichtlich haben wir in unserem Kopf gerechnet. Uns ist klar, dass wir bei einer Verdopplung von 5 Dollar 10 Dollar erhalten sollten. Also schrieben wir 10 sowohl in unseren Test als auch in unseren Dollar Code. Wir sollten erkennen, dass das 10 in der Entität Dollar in Wirklichkeit 5 * 2 ist. Diese Erkenntnis würde es uns ermöglichen, diese Verdopplung zu entfernen.

Duplizierter Code ist oft das Symptom eines zugrunde liegenden Problems: eine fehlende Codeabstraktion oder eine schlechte Kopplung zwischen verschiedenen Teilen des Codes.2

Lass uns die Duplikation entfernen und damit auch die Kopplung loswerden.

Geh

Ersetze die 10 in der Funktion Times durch ihr Äquivalent 5 * 2:

func (d Dollar) Times(multiplier int) Dollar {
    return Dollar{5 * 2}
}

Der Test sollte immer noch grün sein.

Wenn wir es auf diese Weise schreiben, erkennen wir die fehlende Abstraktion. Die hart kodierte 5 ist in Wirklichkeit d.amount und die 2 ist die multiplier. Wenn wir diese hart kodierten Zahlen durch die richtigen Variablen ersetzen, erhalten wir die nicht-triviale Implementierung:

func (d Dollar) Times(multiplier int) Dollar {
    return Dollar{d.amount * multiplier}
}

Juhu! Der Test besteht immer noch und wir haben die Doppelung und die Kopplung entfernt.

Es gibt noch ein letztes Mal etwas zu bereinigen.

In unserem Test haben wir bei der Initialisierung einer Dollar Struktur ausdrücklich den Feldnamen amount verwendet. Es ist auch möglich, Feldnamen bei der Initialisierung einer Struktur wegzulassen, wie wir es in unserer Methode Times getan haben.3 Beide Methoden - explizite Namen verwenden oder nicht - funktionieren. Es ist jedoch wichtig, konsistent zu sein. Ändern wir die Funktion Times, um den Feldnamen anzugeben:

func (d Dollar) Times(multiplier int) Dollar {
    return Dollar{amount: d.amount * multiplier}
}
Tipp

Erinnere dich daran, go fmt ./... regelmäßig auszuführen, um Formatierungsfehler im Code zu beheben.

JavaScript

Ersetzen wir das 10 in der Methode times durch sein Äquivalent 5 * 2:

    times(multiplier) {
        return new Dollar(5 * 2);
    }

Der Test sollte immer noch grün sein.

Die fehlende Abstraktion ist jetzt klar. Wir können 5 durch this.amount und 2 durch multiplier ersetzen:

    times(multiplier) {
        return new Dollar(this.amount * multiplier);
    }

Juhu! Der Test ist immer noch grün und wir haben sowohl die doppelte 10 als auch die Kupplung eliminiert.

Python

Ersetzen wir die 10 in der Methode times durch die entsprechende 5 * 2:

  def times(self, multiplier):
    return Dollar(5 * 2)

Der Test bleibt wie erwartet grün.

Das zeigt die zugrunde liegende Abstraktion. Die 5 ist in Wirklichkeit self.amount und die 2 ist die multiplier:

  def times(self, multiplier):
    return Dollar(self.amount * multiplier)

Hurra! Der Test bleibt grün, und die Verdoppelung und die Kopplung sind weg.

Unsere Veränderungen festschreiben

Wir haben unser erstes Feature mit TDD fertiggestellt. Damit wir das nicht vergessen, ist es wichtig, dass wir unseren Code in regelmäßigen Abständen an die Versionskontrolle übergeben.

Ein grüner Test ist ein hervorragender Ort, um Code festzulegen.

In einem Shell-Fenster geben wir diese beiden Befehle ein:

git add . 1
git commit -m "feat: first green test" 2
1

Füge alle Dateien, einschließlich aller Änderungen, zum Git-Index hinzu.

2

Commit den Git-Index in das Repository mit der angegebenen Nachricht.

Vorausgesetzt, der Code für alle drei Sprachen befindet sich in den richtigen Ordnern, sollten wir eine Meldung wie diese erhalten.

[main (root-commit) bb31b94] feat: first green test 1
 4 files changed, 56 insertions(+)
 create mode 100644 go/go.mod
 create mode 100644 go/money_test.go
 create mode 100644 js/test_money.js
 create mode 100644 py/test_money.py
1

Die Hexadezimalzahl bb31b94 steht für die ersten Ziffern des eindeutigen "SHA-Hashes", der mit dem Commit verknüpft ist. Sie ist für jede Person (und jeden Commit) anders.

Das bedeutet, dass alle unsere Dateien sicher in unserem Git-Repository für die Versionskontrolle sind. Wir können dies überprüfen, indem wir den Befehl git log auf unserer Shell ausführen, der eine ähnliche Ausgabe wie die folgende erzeugen sollte:

commit bb31b94e90029ddeeee89f3ca0fe099ea7556603 (HEAD -> main) 1
Author: Saleem Siddiqui ...
Date:   Sun Mar 7 12:26:06 2021 -0600

    feat: first green test 2
1

Dies ist der erste Commit mit seinem vollständigen SHA-Hash.

2

Das ist die Nachricht, die wir für unseren ersten Commit geschrieben haben.

Es ist wichtig zu wissen, dass sich das Git-Repository, in das wir unseren Code übertragen haben, auch in unserem lokalen Dateisystem befindet. (Es befindet sich im Ordner .git unter unserem TDD_PROJECT_ROOT). Das schützt uns zwar nicht vor versehentlich verschüttetem Kaffee auf unserem Computer (benutze immer einen Deckel), aber es gibt uns die Gewissheit, dass wir zu einer früheren, bekanntermaßen guten Version zurückkehren können, wenn wir uns irgendwo verheddert haben. In Kapitel 13 werden wir unseren gesamten Code in ein GitHub-Repository stellen.

Wir werden diese Strategie, unseren Code in unser lokales Git-Repository zu übertragen, in jedem Kapitel anwenden und dabei dieselben Befehle verwenden.

Wichtig

Wir werden die beiden Befehle git add . und git commit -m _commit message_ verwenden, um unseren Code in den einzelnen Kapiteln häufig zu committen.

Das Einzige, was sich ändert, ist die Commit-Nachricht, die dem semantischen Commit-Stil folgt und eine kurze, einzeilige Beschreibung der Änderungen enthält.

Tipp

Die git commit Nachrichten in diesem Buch folgen dem semantischen Commit-Stil.

Wo wir sind

In diesem Kapitel wurde die testgetriebene Entwicklung mit dem ersten Rot-Grün-Refactor-Zyklus eingeführt. Nachdem wir unser erstes kleines Feature erfolgreich implementiert haben, können wir es abhaken. Hier ist der Stand unserer Feature-Liste:

5 USD × 2 = 10 USD

10 EUR × 2 = 20 EUR

4002 KRW / 4 = 1000,5 KRW

5 USD + 10 EUR = 17 USD

1 USD + 1100 KRW = 2200 KRW

Nehmen wir uns einen Moment Zeit, um unseren Code zu überprüfen und zu genießen, bevor wir uns der nächsten Herausforderung zuwenden. Der Quellcode für alle drei Sprachen ist unten abgebildet. Er ist auch im GitHub-Repository verfügbar. Der Kürze halber werden wir in zukünftigen Kapiteln nur noch den Namen des entsprechenden Zweigs aufführen.

Geh

So sieht die Datei money_test.go im Moment aus:

package main

import (
    "testing"
)

func TestMultiplication(t *testing.T) {
    fiver := Dollar{amount: 5}
    tenner := fiver.Times(2)
    if tenner.amount != 10 {
        t.Errorf("Expected 10, got: [%d]", tenner.amount)
    }
}

type Dollar struct {
    amount int
}

func (d Dollar) Times(multiplier int) Dollar {
    return Dollar{amount: d.amount * multiplier}
}

JavaScript

So sieht die Datei test_money.js zu diesem Zeitpunkt aus:

const assert = require('assert');

class Dollar {
    constructor(amount) {
      this.amount = amount;
    }

    times(multiplier) {
        return new Dollar(this.amount * multiplier);
    }
}

let fiver = new Dollar(5);
let tenner = fiver.times(2);
assert.strictEqual(tenner.amount, 10);

Python

So sieht die Datei test_money.py im Moment aus:

import unittest

class Dollar:
  def __init__(self, amount):
    self.amount = amount

  def times(self, multiplier):
    return Dollar(self.amount * multiplier)

class TestMoney(unittest.TestCase):
  def testMultiplication(self):
    fiver = Dollar(5)
    tenner = fiver.times(2)
    self.assertEqual(10, tenner.amount)

if __name__ == '__main__':
    unittest.main()
Tipp

Der Code für dieses Kapitel befindet sich in einem Zweig namens "chap01" im GitHub-Repository. Es gibt einen Zweig für jedes Kapitel, in dem Code entwickelt wird.

In Kapitel 2 werden wir die Dinge beschleunigen, indem wir ein paar weitere Funktionen einbauen.

1 Die drei Punkte in "go test -v ./..." und "go fmt ./..." sind wörtlich zu nehmen; das sind die einzigen Stellen in diesem Buch, an denen sie nicht für ausgelassenen Code stehen!

2 Hier lohnt es sich, die Meinung von Kent Beck zu zitieren: "Wenn die Abhängigkeit das Problem ist, ist die Verdoppelung das Symptom."

3 Wenn es mehrere Felder in der Struktur gibt - was derzeit nicht der Fall ist -, muss entweder die Reihenfolge der Felder in der Strukturdefinition und bei der Initialisierung gleich sein oder die Feldnamen müssen bei der Strukturinitialisierung angegeben werden. Siehe https://gobyexample.com/structs.

Get Testgetriebene Entwicklung lernen now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.