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:
-
Rot. Wir schreiben einen fehlgeschlagenen Test (einschließlich möglicher Kompilierungsfehler). Wir führen die Testsuite aus, um die fehlgeschlagenen Tests zu überprüfen.
-
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.
-
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.
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
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
)
}
}
Deklaration der Verpackung
Importiertes "Test"-Paket, das später in
t.Errorf
verwendet wirdUnsere Prüfmethode, die mit
Test
beginnen und ein*testing.T
Argument haben mussStruktur, die "USD 5." darstellt
Dollar
existiert noch nichtZu prüfende Methode:
Times
- die es ebenfalls noch nicht gibtVergleich des tatsächlichen Wertes mit dem erwarteten Wert
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 func
tion 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'
)
;
let
fiver
=
new
Dollar
(
5
)
;
let
tenner
=
fiver
.
times
(
2
)
;
assert
.
strictEqual
(
tenner
.
amount
,
10
)
;
Importieren des Pakets
assert
, das später für die Assertion benötigt wirdObjekt, das "USD 5." darstellt
Dollar
existiert noch nichtZu prüfende Methode:
times
- die es ebenfalls noch nicht gibtVergleich 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
class
TestMoney
(
unittest
.
TestCase
)
:
def
testMultiplication
(
self
)
:
fiver
=
Dollar
(
5
)
tenner
=
fiver
.
times
(
2
)
self
.
assertEqual
(
10
,
tenner
.
amount
)
if
__name__
==
'
__main__
'
:
unittest
.
main
(
)
Importiere das Paket
unittest
, das für die SuperklasseTestCase
benötigt.Unsere Testklasse, die eine Unterklasse der Klasse
unittest.TestCase
sein muss.Der Name unserer Methode muss mit
test
beginnen, um als Prüfmethode zu gelten.Das Objekt, das "USD 5."
Dollar
repräsentiert, existiert noch nicht.Zu prüfende Methode:
times
- die es ebenfalls noch nicht gibt.Vergleich des tatsächlichen Wertes mit dem erwarteten Wert in einer
assertEqual
Erklärung.Das
main
Idiom sorgt dafür, dass diese Klasse als Skript ausgeführt werden kann.
Python erfordert import
das Paket unittest
, die Erstellung einer Klasse, die TestCase
untergeordnet ist, und def
die 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!
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:
-
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.
-
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 func
tion 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.00
s
)
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'
)
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
)
{
this
.
amount
=
amount
;
}
times
(
multiplier
)
{
return
new
Dollar
(
10
)
;
}
}
Die Funktion
constructor
wird immer dann aufgerufen, wenn einDollar
Objekt erstellt wird.Initialisiere die Variable
this.amount
mit dem angegebenen Parameter.Die Methode
times
benötigt einen Parameter.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
)
:
self
.
amount
=
amount
def
times
(
self
,
multiplier
)
:
return
Dollar
(
10
)
Die Funktion
__init__
wird immer dann aufgerufen, wenn einDollar
Objekt erstellt wird.Initialisiere die Variable
self.amount
mit dem angegebenen Parameter.Die Methode
times
benötigt einen Parameter.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.000
s
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:
-
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. -
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 wir10
sowohl in unseren Test als auch in unserenDollar
Code. Wir sollten erkennen, dass das10
in der EntitätDollar
in Wirklichkeit5 * 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
}
}
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
.
git
commit
-m
"feat: first green test"
Füge alle Dateien, einschließlich aller Änderungen, zum Git-Index hinzu.
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
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
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
)
Author:
Saleem
Siddiqui
...
Date:
Sun
Mar
7
12:26:06
2021
-0600
feat:
first
green
test
Dies ist der erste Commit mit seinem vollständigen SHA-Hash.
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.