Testgetriebene Entwicklung mit C++ - Jeff Langr - E-Book

Testgetriebene Entwicklung mit C++ E-Book

Jeff Langr

4,9

Beschreibung

Testgetriebene Entwicklung (TDD) ist eine moderne Methode in der Softwareentwicklung, mit der Programmierer und Tester die Anzahl der Fehler im System erheblich verringern, wartungsfreundlicheren Code schreiben und die Software gefahrlos an geänderte Anforderungen anpassen können. Dieses Buch vermittelt praktische TDD-Kenntnisse und beschreibt die Probleme und Vorteile der Verwendung dieser Technik für C++-Systeme. Die vielen ausführlichen Codebeispiele führen schrittweise von den Grundlagen von TDD zu anspruchsvollen Themen: • TDD verwenden, um C++-Altsysteme zu verbessern • Problematische Systemabhängigkeiten erkennen und handhaben • Abhängigkeiten in C++ injizieren • Frameworks für C++ einsetzen, die TDD unterstützen • C++11-Features nutzen, die die Anwendung von TDD erleichtern Unabhängig davon, ob Sie viel Erfahrung mit Unit Tests haben oder ein absoluter Neuling auf diesem Gebiet sind, lernen Sie mit diesem Buch die testgetriebene Entwicklung in C++ erfolgreich anzuwenden.

Sie lesen das E-Book in den Legimi-Apps auf:

Android
iOS
von Legimi
zertifizierten E-Readern
Kindle™-E-Readern
(für ausgewählte Pakete)

Seitenzahl: 527

Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:

Android
iOS
Bewertungen
4,9 (18 Bewertungen)
16
2
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Jeff Langr ist Gründer und Inhaber von Langr Software Solutions, Inc., Colorado, USA. Er arbeitet seit über 25 Jahren als Software-entwickler und hat langjährige Erfahrung in der Programmierung, mit objektorientiertem Design, Test-Driven Development (TDD) sowie agilen Methoden. Neben Hunderten von Artikeln in Zeitschriften zu diesen Themen ist er Autor von mehreren Büchern (u.a. Agile in a Flash, Agile Java).

Sie erreichen ihn unter: [email protected]

Zu diesem Buch – sowie zu vielen weiteren dpunkt.büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+:www.dpunkt.de/plus

Testgetriebene Entwicklung mit C++

Sauberer Code. Bessere Produkte.

Jeff Langr

Fachlektorat: Sven Günther, Hamburg

Lektorat: Christa Preisendanz

Übersetzung: G&U Language & Publishing Services GmbH, www.gundu.com

Satz: G&U Language & Publishing Services GmbH, www.gundu.com

Herstellung: Birgit Bäuerlein

Umschlaggestaltung: Helmut Kraus, www.exclam.de

Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, 33100 Paderborn

Bibliografische Information der Deutschen Nationalbibliothek

Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.deabrufbar.

ISBN

Buch 978-3-86490-189-8

PDF 978-3-86491-581-9

ePub 978-3-86491-582-6

1. Auflage 2014

Copyright © 2014 dpunkt.verlag GmbH

Wieblinger Weg 17

69123 Heidelberg

Authorized translation from the English language edition, entitled »Modern C++ Programming with Test-Driven Development«, 1st Edition, ISBN-13: 978-1-937785-48-2, by Jeff Langr, published by The Pragmatic Programmers, LLC., Copyright © 2013.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. German language edition published by dpunkt.verlag GmbH, Copyright © 2014.

Autorisierte Übersetzung der englischsprachigen Originalausgabe mit dem Titel »Modern C++ Programming with Test-Driven Development« von Jeff Langr. ISBN-13: 978-1-937785-48-2, erschienen bei The Pragmatic Programmers, LLC.; Copyright © 2013

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten.

Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.

Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.

5 4 3 2 1 0

Inhaltsverzeichnis

Geleitwort

Einleitung

1    Einführung

1.1      Setup

1.2      Die Beispiele

1.3      C++-Compiler

1.4      CMake

1.5      Google Mock

1.6      CppUTest

1.7      libcurl

1.8      JsonCpp

1.9      rlog

1.10    Boost

1.11    Beispiele erstellen und Tests ausführen

1.12    Teardown

2    Testgetriebene Entwicklung: Ein erstes Beispiel

2.1      Setup

2.2      Der Soundex-Algorithmus

2.3      Erste Schritte

2.4      Unsauberen Code korrigieren

2.5      Schrittweises Vorgehen

2.6      Fixtures

2.7      Denkprozesse bei TDD

2.8      Testen und testgetriebene Entwicklung im Vergleich

2.9      Was wäre, wenn ...?

2.10    Eins nach dem anderen!

2.11    Die Länge einschränken

2.12    Vokale fallen lassen

2.13    Tests übersichtlich gestalten

2.14    Querdenken beim Testen

2.15    Zurück zum Thema

2.16    Refactoring zu Funktionen mit nur je einer Aufgabe

2.17    Der letzte Schliff

2.18    Welche Tests fehlen noch?

2.19    Unsere Lösung

2.20    Die Soundex-Klasse

2.21    Teardown

3    Testgetriebene Entwicklung: Grundlagen

3.1      Setup

3.2      Unit Tests und Grundlagen von TDD

3.3      Der TDD-Zyklus: Rot – Grün – Refactoring

3.4      Die drei Regeln von TDD

3.5      Verfrühtes Bestehen von Tests

3.6      Die richtige Einstellung für den erfolgreichen Einsatz von TDD

3.7      Techniken für den Erfolg

3.8      Teardown

4    Tests konstruieren

4.1      Setup

4.2      Aufbau

4.3      Schnelle Tests, langsame Tests, Filter und Suiten

4.4      Assertions (Zusicherungen)

4.5      Private Daten untersuchen

4.6      Testen und testgetriebene Entwicklung im Vergleich: Parametrisierte Tests und andere Spielereien

4.7      Teardown

5    Testdoubles

5.1      Setup

5.2      Herausforderungen durch Abhängigkeiten

5.3      Testdoubles

5.4      Ein selbst gebautes Testdouble

5.5      Die Testabstraktion bei der Verwendung von Testdoubles verbessern

5.6      Mock-Frameworks verwenden

5.7      Testdoubles platzieren

5.8      Ein anderes Vorgehen beim Design

5.9      Strategien zur Verwendung von Testdoubles

5.10    Verschiedenes zum Thema Testdoubles

5.11    Teardown

6    Inkrementelles Design

6.1      Setup

6.2      Einfaches Design

6.3      Was ist mit dem Design im Voraus?

6.4      Hindernisse für das Refactoring

6.5      Teardown

7    Qualitativ hochwertige Tests

7.1      Setup

7.2      Tests nach dem FIRST-Prinzip

7.3      Eine Zusicherung pro Test

7.4      Testabstraktion

7.5      Teardown

8    Herausforderungen durch Legacy-Code

8.1      Setup

8.2      Legacy-Code

8.3      Kernsätze

8.4      Die Altanwendung

8.5      Die Denkweise der testgetriebenen Entwicklung

8.6      Sicheres Refactoring zur Unterstützung von Tests

8.7      Tests zur Beschreibung des vorhandenen Verhaltens hinzufügen

8.8      Ablenkungen durch die Realitäten des vorhandenen Codes

8.9      Ein Testdouble für rlog erstellen

8.10    Testgetriebene Änderungen

8.11    Eine neue Story

8.12    Schnellere Tests finden

8.13    Mondo Extracto

8.14    Verwendung einer Membervariablen

8.15    Verwendung eines Mocks

8.16    Alternative Injektionstechniken

8.17    Umfassende Änderungen mit der Mikado-Methode

8.18    Die Mikado-Methode im Überblick

8.19    Methoden mit Mikado verschieben

8.20    Weitere Überlegungen zur Mikado-Methode

8.21    Lohnt sich der Aufwand?

8.22    Teardown

9    TDD für Threads

9.1      Setup

9.2      Grundprinzipien für die testgetriebene Thread-Entwicklung

9.3      GeoServer

9.4      Performance-Anforderungen

9.5      Eine asynchrone Lösung gestalten

9.6      Dies ist immer noch einfaches TDD

9.7      »Einfädeln«

9.8      Probleme der Parallelverarbeitung offenlegen

9.9      Client-Threads im Test erstellen

9.10    Mehrere Threads in ThreadPool erstellen

9.11    Zurück zu GeoServer

9.12    Teardown

10  Weitere Aspekte von TDD

10.1    Setup

10.2    TDD und die Performance

10.3    Unit Tests, Integrationstests und Akzeptanztests

10.4    Transformation Priority Premise (TPP)

10.5    Zusicherungen zuerst schreiben

10.6    Teardown

11  Wachstum und Pflege von TDD

11.1    Setup

11.2    Laien TDD erklären

11.3    Die Todesspirale schlechter Tests (»SCUMmy-Kreislauf«)

11.4    Pair Programming

11.5    Katas und Dojos

11.6    Metriken zur Codeabdeckung wirkungsvoll anwenden

11.7    Kontinuierliche Integration

11.8    Teamstandards für TDD aufstellen

11.9    Mit der Community auf dem Laufenden bleiben

11.10    Teardown

A    Unit-Test-Frameworks im Vergleich

A.1    Setup

A.2    Funktionen von TDD-Unit-Test-Frameworks

A.3    Hinweise zu Google Mock

A.4    Hinweise zu CppUTest

A.5    Andere Frameworks für Unit Tests

A.6    Teardown

B    Code-Kata: Umrechner für römische Zahlen

B.1    Setup

B.2    Los geht’s!

B.3    Übung macht den Meister

B.4    Teardown

Literatur

Index

Geleitwort

Lassen Sie sich von dem Titel nicht irreführen!

Was Sie hier vor sich haben, ist ein wirklich sehr, sehr gutes Buch über Designprinzipien, Programmiertechniken, testgetriebene Entwicklung und handwerkliches Können – und dann geben die ihm einen Titel wie Modern C++ Programming with Test-Driven Development!1 Seufz!

Verstehen Sie mich nicht falsch: In diesem Buch geht es tatsächlich um moderne C++-Programmierung. Als C++-Entwickler wird Ihnen der Code in diesem Buch gefallen. Es ist randvoll mit wirklich interessantem und gut geschriebenem C++-Code und enthält wohl mehr Code als Text. Blättern Sie mal das Buch durch und schauen Sie sich an, wie viele Seiten ohne Code es gibt. Nicht viele, möchte ich wetten. Wenn Sie also nach einem guten Buch suchen, das Ihnen die modernen Techniken von C++ beibringt, dann halten Sie schon das richtige in Ihren Händen.

In diesem Buch geht es aber um mehr als nur um moderne C++-Entwicklung – um viel mehr. Erstens ist es wahrscheinlich die vollständigste und verständlichste Darstellung der testgetriebenen Entwicklung (Test-Driven Development, TDD), die ich je gesehen habe (und ich habe eine Menge gesehen!). Praktisch jeder Aspekt von TDD, dem wir in den letzten anderthalb Jahrzehnten begegnet sind, wird hier behandelt, von instabilen Tests zu Mocks, von der Londoner zur Clevelander Schule, von der Regel »eine Annahme pro Test« bis zum Muster »Given-When-Then«. All das und noch viel mehr finden Sie in diesem Buch. Es ist aber keine Sammlung unzusammenhängender akademischer Texte. Im Gegenteil, in diesem Buch werden die einzelnen Aspekte anhand von Beispielen und Fallstudien durchgearbeitet. Es zeigt die Probleme und Lösungen in Form von Code.

Müssen Sie C++-Entwickler sein, um dieses Buch zu verstehen? Natürlich nicht. Der C++-Code ist so sauber und so gut geschrieben und die Prinzipien sind so deutlich, dass Sie auch als Java-, C#-, C- oder sogar Ruby-Programmierer keine Schwierigkeiten damit haben werden.

Und dann sind da die Designprinzipien. Menschenskind, dieses Buch ist ja eine Anleitung zum guten Design! Sie lernen hier Schritt für Schritt ein Prinzip nach dem anderen kennen, ein Problem nach dem anderen und eine Technik nach der anderen: vom Single-Responsibility-Prinzip zur Umkehrung von Abhängigkeiten, von der Isolierung der Schnittstelle zu den agilen Prinzipien des einfachen Designs, von DRY zu »tell, don’t ask«. Dieses Buch ist eine Goldgrube an Ideen und Lösungen für das Softwaredesign. Und auch hier werden diese Ideen wiederum im Zusammenhang mit praktischen Problemen und praktischen Lösungen in echtem Code dargestellt.

Außerdem geht es auch noch um Programmiertechniken. Dieses Buch ist randvoll damit, von kleinen Methoden bis zum Pair Programming, von Katas bis zu Variablennamen. Sie finden hier nicht nur haufenweise Code, aus dem Sie gute Vorgehensweisen ableiten können – der Autor bearbeitet jeden einzelnen Punkt auch mit genau der richtigen Menge an Diskussion und ausführlicher Darstellung.

Der Titel dieses Buches ist komplett falsch. Dies ist kein Buch über C++-Programmierung, sondern über solide Software-Handwerkskunst, das nur zufällig C++ als Sprache für die Beispiele verwendet. Ein besserer Titel wäre gewesen: Software Craftsmanship: With Examples in Modern C++.

Wenn Sie also ein Entwickler für Java, C#, Ruby, Python, PHP, VB oder gar COBOL sind, sollten Sie dieses Buch lesen. Lassen Sie sich nicht von dem »C++« im Titel abschrecken. Lesen Sie das Buch trotzdem. Und lesen Sie dabei den Code! Er ist nicht schwer zu verstehen. Während Sie bei der Lektüre gute Designprinzipien, Programmiertechniken, handwerkliches Können und testgetriebene Entwicklung lernen, werden Sie wahrscheinlich auch feststellen, dass ein bisschen C++ niemandem weh tut.

Robert »Uncle Bob« MartinGründer von Object Mentor Inc.

Einleitung

Trotz der derzeitigen explosionsartigen Vermehrung von Programmiersprachen ist C++ nach wie vor im Einsatz. Laut dem Tiobe-Index vom Juli 2013 handelt es sich dabei um die am vierthäufigsten verwendete Programmiersprache. (Den aktuellen Index finden Sie auf http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html.) Der ISO-Standard 2011 (ISO/IEC 14822:2011, besser bekannt als C++11) ergänzt C++ um weitere Merkmale, die die Akzeptanz dieser Sprache steigern – oder zumindest die Einwände gegen ihre Verwendung abschwächen.

C++ ist nach wie vor eine der besten Möglichkeiten, um hochleistungsfähige Lösungen zu erstellen. Wenn Sie die Produkte Ihres Unternehmens auf Ihrer eigenen Hardware einsetzen, dann verfügen Sie wahrscheinlich selbst über ein ziemlich großes System, das in C++ geschrieben wurde. Ist Ihr Unternehmen schon seit den 1990er-Jahren oder noch länger im Geschäft, dann haben Sie wahrscheinlich ein langjähriges C++-System, und die Chancen stehen nicht schlecht, dass es Ihnen auch in den nächsten Jahren noch erhalten bleibt.

Vorausgesetzt Sie arbeiten mit C++, kommen Ihnen jetzt vielleicht einige Einwände in den Sinn:

Wir schreiben das Jahr 2014! Warum soll ich mich wieder mit dieser schwierigen (wenn auch unterhaltsamen) Sprache beschäftigen, die ich schon vor Jahren aufgegeben habe? Wie kann ich damit leben, ohne mir dabei selbst zu schaden?

Ich bin ein erfahrener C++-Experte. Ich kenne diese leistungsfähige Sprache in- und auswendig und wende sie schon seit Jahren erfolgreich an. Warum sollte ich meine Arbeitsweise ändern?

Wer bezahlt mich dafür, dass ich mich hiermit beschäftige?

Ich selbst habe Anfang der 1990er-Jahre zum ersten Mal mit C++ gearbeitet, bevor Dinge wie Templates (und Template-Metaprogrammierung!), RTTI (RunTime Type Information), STL (Standard Template Library) und Boost aufkamen. Seitdem musste ich einige wenige Male zu dieser leistungsstarken Sprache zurückkehren, was mich immer ein kleines bisschen erschreckt hat. Wie bei jeder anderen Sprache ist es natürlich auch bei C++ möglich, sich ins eigene Fleisch zu schneiden, aber dabei merken Sie es manchmal gar nicht, bis es zu spät ist. Außerdem wird der Schaden höchstwahrscheinlich größer sein als bei der Verwendung von anderen Sprachen.

Wenn Sie schon seit Jahren mit C++ arbeiten, haben Sie sich wahrscheinlich viele Idiome und Techniken angewöhnt, um für eine hohe Qualität des Codes zu sorgen. Eingefleischte C++-Veteranen gehören zu den sorgfältigsten Entwicklern, da große Vorsicht und Achtsamkeit erforderlich sind, um so lange mit C++ zu überleben.

Angesichts dessen könnte man denken, dass die Qualität des Codes von C++-Systemen sehr hoch sein sollte. Trotzdem weisen viele C++-Systeme dieselben Probleme auf, die wir unabhängig von der Sprache immer wieder vorfinden:

Riesige Quellcodedateien mit Tausenden von Zeilen

Memberfunktionen mit Hunderten oder Tausenden von Zeilen unverständlichen Codes

Datenträger voll totem Quellcode

Build-Zeiten von mehreren Stunden

Hohe Anzahl an Fehlern

Zu verzwickte Logik, um schnelle Korrekturen gefahrlos anzubringen

Redundanter Code in Dateien, Klassen und Modulen

Code durchsetzt mit längst überholten Programmiertechniken

Sind diese Mängel unvermeidlich? Nein! Mit testgetriebener Entwicklung können Sie die Entropie Ihres Systems bekämpfen! Sie können damit vielleicht sogar Ihre Begeisterung für die Entwicklungsarbeit wieder neu anfachen.

Wenn Sie einfach nur an der Bezahlung interessiert sind, dann gibt es für Sie genügend Arbeitgeber, die Sie als C++-Entwickler beschäftigen würden. C++ ist jedoch eine sehr technische und nuancenreiche Sprache. Ihr unachtsamer Gebrauch kann zu Mängeln, sporadischen Ausfällen und mehrtägigen Debugsitzungen führen – lauter Dinge, die Ihr Arbeitsverhältnis und Ihre Bezahlung gefährden. Auch hier kann sich testgetriebene Entwicklung als hilfreich erweisen.

Der Aufwand, der dazu erforderlich ist, den Funktionsumfang eines solchen umfangreichen, langjährigen C++-Systems zu erweitern, ist fast immer deprimierend und schwer einzuschätzen. Um einige wenige Codezeilen zu ändern, benötigen Sie oft Stunden oder gar Tage, um die entsprechende Passage zu verstehen. Die Produktivität wird noch weiter dadurch herabgesetzt, dass die Entwickler stundenlang warten müssen, bis endlich klar ist, ob die Änderungen kompiliert werden können, und es dauert noch länger, um zu sehen, ob sich diese Änderungen mit dem Rest des Systems vertragen.

Das muss aber nicht so sein. Testgetriebene Entwicklung (Test-Driven Development, TDD) ist eine in den späten 1990er-Jahren erfundene Technik für das Softwaredesign, mit der Sie Ihr C++-System niederzwingen und unter Kontrolle halten können, während Sie neue Funktionalität hinzufügen. Die Idee, erst die Tests und dann den Code zu schreiben, besteht schon erheblich länger. Der formale TDD-Zyklus wurde jedoch von Ward Cunningham und Kent Beck erfunden – siehe »Test Driven Development: By Example« [Bec02].

Der Hauptzweck dieses Buches besteht darin, Ihnen eine disziplinierte Vorgehensweise für die Anwendung von TDD beizubringen. Sie werden dabei Folgendes lernen:

Die grundlegenden Mechanismen von TDD

Die möglichen Vorteile von TDD

Designmängel mithilfe von TDD gleich im Ansatz bekämpfen

Probleme und Kosten bei der Verwendung von TDD

Verkürzen oder gar Vermeiden von Debugsitzungen dank TDD

Pflegen von TDD

Eignet sich das auch für mich und mein System?

»Was soll dieser ganze Zirkus um Unit Tests? Das hilft mir doch alles nicht viel weiter!«

Vielleicht haben Sie sich schon einmal an Unit Tests versucht. Vielleicht haben Sie gerade jetzt damit zu kämpfen, Unit Tests für ein Altsystem zu schreiben. Angesichts dieser Erfahrungen mag es für Sie so aussehen, als eigne sich TDD für die seltenen Fälle, in denen jemand das Glück hat, an einem neuen System zu arbeiten. Löst es aber die Probleme, die sich bei Ihrer täglichen Arbeit an einem festgefahrenen, anspruchsvollen C++-System stellen?

TDD ist ein sehr nützliches Werkzeug, aber natürlich kein Allheilmittel für den Umgang mit Altsystemen. Zwar können Sie viele der neuen Features, die Sie dem System hinzufügen, mithilfe von TDD entwickeln, aber Sie müssen auch damit beginnen, den überflüssigen Code zu entfernen, der sich im Laufe der Jahre angesammelt hat. Zusätzliche Strategien und Taktiken für eine fortgesetzte Bereinigung sind erforderlich. Dazu müssen Sie Techniken zum Auflösen von Abhängigkeiten und für die gefahrlose Änderung von Code lernen, die Michael Feathers in seinem unverzichtbaren Buch »Effektives Arbeiten mit Legacy Code« [Fea04] vorstellt. Sie müssen wissen, wie Sie ein groß angelegtes Refactoring angehen, ohne entsprechende Probleme hervorzurufen. Dazu sollten Sie die Mikado-Methode kennenlernen (siehe »Behead Your Legacy Beast: Refactor and Restructure Relentlessly with the Mikado Method« [BE12]). In diesem Buch lernen Sie solche Hilfstechniken und mehr.

Es nützt gewöhnlich nicht viel, einfach Unit Tests für bereits bestehenden Code hinzuzufügen (was ich als TAD für Test-After Development bezeichne), da Sie dabei immer damit zu kämpfen haben, »dass das System so ist, wie es ist«. Sie können Tausende von Mannstunden in das Schreiben von Tests investieren, ohne die Qualität des Systems dadurch messbar zu steigern.

Wenn Sie TDD einsetzen, um Ihr System zu gestalten, dann wird Ihr Design definitionsgemäß testfähig. Sie werden dabei auch andere Designs entwickeln als ohne TDD, und zwar Designs, die in vieler Hinsicht besser sind. Je besser Sie verstehen, was gutes Design bedeutet, umso stärker wird Ihnen TDD dabei helfen, es zu erreichen.

Um Ihnen den Wechsel zu einer neuen Ansicht über Design zu erleichtern, betone ich in diesem Buch die Prinzipien für gutes Codemanagement, z.B. die SOLID-Prinzipien für objektorientiertes Design, die in »Agile Software Development, Principles, Patterns, and Practices« [Mar02] beschrieben werden. Ich zeige Ihnen, wie ein Gefühl für gutes Design die weiter gehende Entwicklung und Produktivität fördert und wie TDD aus Ihnen einen aufmerksamen Entwickler macht, der konsistentere und zuverlässigere Ergebnisse erzielt.

Zielgruppe

Dieses Buch ist für C++-Entwickler unabhängig von ihrem Kenntnisstand gedacht – für Anfänger, die gerade einmal ein grundlegendes Verständnis gewonnen haben, bis hin zu alten Hasen, die auch in die esoterischen Aspekte der Sprache eingeweiht sind. Wenn Sie längere Zeit nicht mehr mit C++ gearbeitet haben, werden Sie feststellen, dass Sie sich durch die schnellen Feedbackzyklen von TDD schnell wieder daran gewöhnen werden.

Der Hauptzweck dieses Buches besteht zwar darin, Ihnen TDD beizubringen, doch werden Sie auch unabhängig von Ihren TDD-Kenntnissen viel Wertvolles darin finden. Wenn Ihnen das Prinzip, Unit Tests für Ihren Code zu schreiben, noch völlig unbekannt ist, lernen Sie hier Schritt für Schritt die Grundlagen von TDD kennen. Wenn für Sie TDD noch relativ neu ist, werden Sie überall in diesem Buch viel nützliches Fachwissen entdecken, das Ihnen auf einfache Weise durch praktische Beispiele nahegebracht wird. Aber auch erfahrene TDD-Entwickler werden hier einen reichen Schatz an Kenntnissen finden, ein fundierteres theoretisches Fundament für die praktische Anwendung erhalten sowie neue Aspekte kennenlernen, die eine genauere Untersuchung wert sind.

Sollten Sie noch skeptisch sein, können Sie sich TDD hier aus verschiedenen Blickwinkeln ansehen. Ich werde Ihnen immer wieder zeigen, warum TDD meiner Meinung nach gut funktioniert, und ich werde Ihnen auch meine Erfahrungen mit den Projekten weitergeben, in denen es nicht gut funktioniert hat, und die Gründe dafür nennen. Dieses Buch ist keine Werbebroschüre, sondern eine mit offenen Augen durchgeführte Untersuchung einer umwälzenden Technik.

Unabhängig von Ihrem Hintergrund werden Sie auch Hinweise dazu finden, wie Sie TDD in Ihrem Team pflegen und erhalten können. Es ist leicht, mit TDD anzufangen, aber Ihr Team wird unterwegs auf anspruchsvolle Herausforderungen stoßen. Wie können Sie es vermeiden, dass Ihr Wechsel zu TDD durch diese Probleme aus der Bahn geworfen wird? Wie verhindern Sie solche Katastrophen? In Kapitel 11 stelle ich einige Ideen vor, die nach meiner Erfahrung gut funktioniert haben.

Was brauchen Sie noch?

Um den Code der Beispiele in diesem Buch zu schreiben, brauchen Sie natürlich einen Compiler und ein Unit-Test-Framework. Für einige Beispiele sind auch Bibliotheken von Drittanbietern erforderlich. Nachfolgend sehen wir uns diese drei Elemente an. Weitere Einzelheiten darüber erfahren Sie in Kapitel 1.

Das Unit-Test-Framework

Unter den Dutzenden von verfügbaren Unit-Test-Frameworks für C++ habe ich mich bei den meisten Beispielen in diesem Buch für Google Mock entschieden (das auf Google Test aufbaut). Bei einer Websuche nach einem solchen Framework erhält Google Mock zurzeit die meisten Treffer. Hauptsächlich aber habe ich es gewählt, da es die Hamcrest-Schreibweise unterstützt (eine Form von Zusicherungen auf der Grundlage von Matchern, mit der Sie sehr aussagekräftige Tests schreiben können). Anhand der Informationen in Kapitel 1 können Sie sich schnell in Google Mock einarbeiten.

Dieses Buch ist jedoch weder eine umfangreiche Abhandlung über Google Mock noch eine Werbebroschüre dafür, sondern es soll Ihnen TDD beibringen. Sie lernen aber genug über Google Mock, um TDD darin wirksam einsetzen zu können.

Für einige der Beispiele werden Sie auch ein anderes Unit-Test-Framework verwenden, nämlich CppUTest. Sie werden feststellen, dass es ziemlich einfach ist, sich mit einem weiteren solchen Framework vertraut zu haben. Das sollte bei Ihnen auch jegliche Bedenken ausräumen, wenn Sie etwas anderes verwenden als Google Mock oder CppUTest.

Wenn Sie schon ein anderes Framework im Einsatz haben, etwa CppUnit oder Boost.Test, dann machen Sie sich keine Sorgen. In der prinzipiellen Verwendung und oft auch in der Implementierung weisen sie viele Ähnlichkeiten mit Google Mock auf. Sie können den Ausführungen problemlos folgen und die TDD-Beispiele in praktisch jedem anderen der verfügbaren C++-Unit-Test-Frameworks ausprobieren. Worauf Sie bei der Auswahl eines solchen Frameworks unbedingt achten sollten, erfahren Sie in Anhang A.

Für Mocks und Stubs (siehe Kap. 5) wird in den meisten Beispielen in diesem Buch Google Mock verwendet. Natürlich arbeiten Google Mock und Google Test zusammen, aber Sie können Google Mock auch mit einem anderen Unit-Test-Framework erfolgreich einsetzen.

Der Compiler

Sie benötigen einen Compiler mit Unterstützung für C++11. Der Beispielcode zu diesem Buch wurde ursprünglich in gcc geschrieben und funktioniert ohne Anpassungen unter Linux und Mac OS. Wie Sie die Beispiele auch unter Windows zum Laufen bekommen, erfahren Sie in Kapitel 1. In allen Beispielen wird STL verwendet, ein wesentlicher Aspekt der modernen C++-Entwicklung für viele Plattformen.

Bibliotheken von Drittanbietern

In einigen der Beispiele werden frei erhältliche Bibliotheken von Drittanbietern verwendet. Eine Liste der Bibliotheken zum Download finden Sie in Kapitel 1.

Wie Sie dieses Buch lesen sollten

Die Kapitel in diesem Buch habe ich als möglichst eigenständige Abschnitte gestaltet. Somit können einzelne Kapitel herausgegriffen und durchgearbeitet werden, ohne alle anderen Kapitel lesen zu müssen. Wo es angebracht ist, habe ich Querverweise verwendet, sodass Sie korrespondierende Stellen auf einfache Weise finden können.

Jedes Kapitel beginnt mit einem kurzen Überblick und endet mit einer Zusammenfassung sowie einer Vorschau auf das nächste Kapitel. Als Namen dieser Abschnitte habe ich die Bezeichnungen für die Initialisierungs- und Aufräumphasen verwendet, wie sie in vielen Unit-Test-Frameworks gebraucht werden: Setup und Teardown.

Der Quellcode

Das Buch enthält zahlreiche Codebeispiele. Meistens ist ein Dateiname dazu angegeben. Den kompletten Beispielcode zu diesem Buch finden Sie auf http://pragprog.com/book/lotdd/modern-c-programming-with-test-drivendevelopment sowie auf meiner GitHub-Seite http://github.com/jlangr.

Innerhalb der Codesammlung sind die Beispiele nach Kapiteln geordnet, und innerhalb der Verzeichnisse für die einzelnen Kapitel finden Sie wiederum Verzeichnisse, die mit einer Versionsnummer versehen sind. (Dadurch ist es möglich, die fortschreitende Veränderung des Codes in den einzelnen Kapiteln zu zeigen.) Beispielsweise enthält das Listing mit der Bezeichnung c2/7/SoundexTest.cpp den Quellcode der Datei SoundexTest.cpp, die sich in der siebten Überarbeitung im Codeverzeichniss für Kapitel 2 (c2) befindet.

Diskutieren Sie über das Buch!

Bitte beteiligen Sie sich (in englischer Sprache) an dem Diskussionsforum auf https://groups.google.com/forum/?fromgroups#!forum/modern-cpp-with-tdd. Dieses Forum dient dazu, das Buch sowie TDD im Allgemeinen zu besprechen. Ich werde dort auch wichtige Informationen über dieses Buch einstellen.

Für TDD-Neulinge: Der Inhalt dieses Buches

Dieses Buch richtet sich zwar an alle, insbesondere aber an Entwickler, für die TDD noch etwas Neues ist. Die Anordnung der Kapitel ist eigens auf diese Zielgruppe zugeschnitten. Ich empfehle Ihnen sehr, die Übung in Kapitel 2, »Testgetriebene Entwicklung: Ein erstes Beispiel«, durchzuarbeiten. Dadurch erhalten Sie ein gutes Gefühl für die Ideen, die hinter TDD stehen. Lesen Sie das Kapitel nicht nur, sondern geben Sie den Code ein und sorgen Sie dafür, dass die Tests auch bestanden werden, wenn sie es sollen.

Die nächsten beiden Kapitel, »Testgetriebene Entwicklung: Grundlagen« (Kap. 3) und »Tests konstruieren« (Kap. 4), stellen ebenfalls grundlegende Lektüre dar. Sie zeigen, was TDD ist (und was es nicht ist) und wie Sie Ihre Tests aufbauen. Machen Sie sich mit dem Inhalt in diesen Kapiteln gut vertraut, bevor Sie zu Mocks übergehen (in Kap. 5, »Testdoubles«), einer Technik, die für die meisten Produktionssysteme unverzichtbar ist.

Das Kapitel über Design und Refactoring (Kap. 6, »Inkrementelles Design«) sollten Sie auf keinen Fall überspringen, auch dann nicht, wenn Sie schon zu wissen glauben, was inkrementelles Design bedeutet. Ein wesentlicher Grund für die Anwendung von TDD besteht darin, das Design ständig weiterentwickeln und den Code durch Refactorings kontinuierlich verbessern zu können. Die meisten Systeme weisen ein schlechtes Design und komplizierten Code auf, was teilweise daran liegt, dass die Entwickler nicht zu einem ausreichenden Refactoring bereit sind oder nicht wissen, wie das geht. Sie lernen hier, was dafür ausreichend ist und wie Sie die Vorteile kleiner, einfacher Systeme ausnutzen können.

Zum Abschluss der TDD-Techniken sehen wir uns in Kapitel 7, »Qualitativ hochwertige Tests«, eine Reihe von Möglichkeiten an, mit denen sich Ihre Investitionen in TDD noch stärker auszahlen. Einige dieser Techniken können den Unterschied zwischen bloßem Überleben und erfolgreicher Anwendung von TDD ausmachen.

Zwangsläufig werden Sie irgendwann mit einem bereits vorhandenen System zu tun haben, das nicht mithilfe von TDD entwickelt wurde. Einen Schnellkurs in Techniken zur Arbeit mit solchem Altcode bietet Kapitel 8, »Herausforderungen durch Legacy-Code«.

Darauf folgt Kapitel 9, »TDD für Threads«, in dem es um die Entwicklung von Multithread-Anwendungen mit TDD geht. Die testgetriebene Vorgehensweise bietet hier einige Überraschungen für Sie.

Kapitel 10, »Weitere Aspekte von TDD«, behandelt einige spezielle Gebiete und Probleme von TDD ausführlicher. Hier erläutern wir einige moderne Ideen zu TDD sowie alternative Vorgehensweisen, die ein wenig von dem abweichen, was Sie sonst in diesem Buch gelesen haben.

Am Ende wollen Sie wahrscheinlich erfahren, wie Sie TDD in Ihrem Team einführen und dafür sorgen können, dass sich Ihre Investitionen darin auch langfristig auszahlen. In Kapitel 11, »Wachstum und Pflege von TDD«, finden Sie schlussendlich einige Ideen, die Sie in Ihrem Unternehmen umsetzen können.

Für Leser mit TDD-Erfahrung

Es ist durchaus möglich, sich nach Belieben einzelne Kapitel herauszugreifen. Allerdings sind überall in diesem Buch viele unter großen Mühen erworbene Weisheiten verstreut.

Schreibweisen in diesem Buch

Alle Codeabschnitte sind vom Rest des Texts abgesetzt. Wenn im Text Codeelemente erwähnt werden, so werden dafür folgende Schreibweisen verwendet:

Klassennamen und Testnamen erscheinen in nicht proportionaler Schrift und in UpperCamelCase-Schreibweise.

Auch alle anderen Codeelemente werden in nicht proportionaler Schrift dargestellt:

funktionsnamen()

– Selbst wenn eine Funktion eine oder mehrere Parameter deklariert, wird sie mit leerer Argumentliste geschrieben. Memberfunktionen bezeichne ich manchmal auch als

Methoden

.

variablennamen

schlüsselwörter

Alle anderen Codebeispiele

Zur Vereinfachung und um kein Papier zu verschwenden, wird in den Listings häufig Code ausgelassen, der mit dem besprochenen Thema nichts zu tun hat. Stattdessen sehen Sie einen Kommentar mit Auslassungspunkten. Beispielsweise wird im folgenden Code der Rumpf der for-Schleife auf diese Weise ersetzt:

for

 (

int

 i = 0; i < count; i++) {   

// ... 

}

Wer ist »wir«?

Ich habe dieses Buch als Dialog zwischen Ihnen und mir geschrieben. Im Allgemeinen spreche ich Sie als Leser an. Wenn ich von mir selbst spreche, dann geht es gewöhnlich um meine persönlichen Erfahrungen, Meinungen oder Vorlieben. Dadurch mache ich deutlich, dass meine Äußerung keine allgemein anerkannte Regel ist (aber trotzdem eine gute Idee sein kann!).

Wenn es ans Schreiben der Programme geht, möchte ich nicht, dass Sie sich allein fühlen, vor allem da Sie ja noch lernen. Bei all den Codebeispielen in diesem Buch arbeiten wir zusammen.

Wer bin ich?

Ich programmiere seit 1980, als ich noch an der High School war, und beruflich seit 1982 (ich habe schon für die University of Maryland gearbeitet, während ich noch meinen Bachelor in Informatik machte). Im Jahr 2000 wechselte ich vom Entwickler zum Berater, wobei ich das Vergnügen hatte, für Bob Martin und gelegentlich für andere großartige Personen bei Object Mentor zu arbeiten.

2003 habe ich Langr Software Solutions für die Beratung und Schulung in der agilen Softwareentwicklung gegründet. Meistens wende ich zusammen mit anderen Entwicklern als Paar TDD an oder ich lehre diese Technik. Mir ist es sehr wichtig, zwischen meiner Beratungs- und Lehrtätigkeit und »echter« Entwicklungstätigkeit in einem richtigen Entwicklungsteam abzuwechseln, sodass ich immer auf dem neuesten Stand bleibe. Seit 2002 war ich als Vollzeitentwickler jeweils lange Zeit für vier verschiedene Unternehmen tätig.

Ich schreibe gern über Softwareentwicklung. Das gehört für mich dazu, um selbst tiefere Kenntnisse zu gewinnen, aber ich freue mich auch darüber, anderen zu helfen, guten Code zu schreiben. Dies ist mein viertes Buch. Zuvor habe ich »Essential Java Style: Patterns for Implementation« [Lan99] und »Agile Java: Crafting Code With Test-Driven Development« [Lan05] sowie als Koautor »Agile in a Flash« [OL11] zusammen mit Tim Ottinger geschrieben. Des Weiteren habe ich einige Kapitel zu »Clean Code: Refactoring, Patterns, Testen und Techniken für sauberen Code« [Mar09] von »Uncle Bob« Martin beigetragen. Ich habe über hundert Artikel für andere Websites als meine eigene geschrieben, führe regelmäßig mein eigenes Blog (auf http://langrsoft.com/jeff) und habe über hundert Blogeinträge für das Projekt »Agile in a Flash« auf http://agileinaflash.com verfasst oder mitverfasst.

Neben C++ habe ich ausführlich in mehreren anderen Sprachen programmiert: Java, Smalltalk, C, C# und Pascal sowie einige andere, die hier unerwähnt bleiben sollen. Zurzeit lerne ich Erlang und kann auch genügend Python und Ruby, um zu überleben. Außerdem habe ich mindestens ein weiteres Dutzend Sprachen ausprobiert, um zu sehen, wie sie sind (oder um ein kurzfristiges Projekt zu unterstützen).

Der C++-Stil in diesem Buch

Ich habe zwar umfassende Erfahrungen mit C++-Systemen aller Größen, aber für einen Sprachexperten halte ich mich trotzdem nicht. Ich habe die wichtigen Bücher von Meyers und Sutter sowie einige andere gelesen, und ich weiß, wie ich C++ für meine Zwecke einsetzen kann und wie ich den resultierenden Code aussagekräftig und wartungsfreundlich gestalte. Darüber hinaus bin ich mir auch der meisten nur für Eingeweihte bekannten Aspekte der Sprache bewusst, vermeide aber absichtlich Lösungen, für die sie erforderlich sind. Meine Definition von »cleverer Programmierung« bedeutet »schwer zu warten«. Ich möchte Sie in eine bessere Richtung führen.

Mein C++-Stil ist sehr stark objektorientiert (was zweifellos auf die viele Programmierarbeit in Smalltalk, Java und C# zurückgeht). Ich bevorzuge Code mit dem Gültigkeitsbereich einer Klasse. Die meisten Beispiele in diesem Buch folgen diesem Stil. Beispielsweise erstelle ich den Soundex-Code im ersten Beispiel (siehe Kap. 2) als Klasse, obwohl das nicht sein müsste. Ich mag es einfach so. Wenn es Sie stört, können Sie es auf Ihre Weise machen.

TDD bietet unabhängig von Ihrem C++-Stil viel Nutzen. Lassen Sie sich also durch meinen Stil nicht daran hindern, das wahre Potenzial dieser Technik auszuschöpfen. Die stärkere Betonung auf Objektorientiertheit vereinfacht jedoch die Einführung von Testdoubles (siehe Kap. 5), wenn Sie problematische Abhängigkeiten auflösen müssen. Wenn Sie sich in TDD vertiefen, werden Sie feststellen, dass sich Ihr Stil mit der Zeit in diese Richtung bewegt. Und das ist keine schlechte Sache!

Leider bin ich ein bisschen faul. Angesichts des geringen Umfangs der Beispiele habe ich die Verwendung von Namespaces auf das Minimum beschränkt. Für Produktionscode sollten Sie natürlich auf Namespaces zurückgreifen.

Außerdem gestalte ich meinen Code gern so stromlinienförmig wie möglich und vermeide daher das, was ich als optisch ablenkend empfinde. In den meisten Implementierungsdateien finden Sie daher usingnamespacestd;, was viele für eine schlechte Vorgehensweise halten. (Dadurch, dass wir die Klassen und Funktionen klein und zielgerichtet halten, sind solche und ähnliche Richtlinien wie »alle Funktionen sollten nur einen Rückgabewert haben« weniger nützlich.) Aber keine Sorge: TDD hindert Sie nicht daran, bei Ihren eigenen Standards zu bleiben, und ich werde es auch nicht tun.

Noch ein letztes Wort zu C++: Es ist eine umfangreiche Sprache. Ich bin mir sicher, dass es bessere Möglichkeiten gibt, den Code für einige der Beispiele in diesem Buch zu schreiben, und ich wette, dass es Bibliothekskonstrukte gibt, die ich nicht genutzt habe. Das Schöne an TDD ist, dass Sie eine Implementierung auf Dutzende Weisen umarbeiten können, ohne Gefahr zu laufen, etwas kaputt zu machen. Senden Sie mir trotzdem Ihre Verbesserungsvorschläge – aber nur, wenn Sie bereit sind, sie testgesteuert zu entwickeln!

Danksagungen

Ich danke meinem Lektor Michael Swaine und den großartigen Leuten bei Prag-Prog für die Anleitung und die erforderlichen Ressourcen, um dieses Buch fertigzustellen.

Danke, Onkel Bob, für das überschwängliche Vorwort!

Vielen Dank an Dale Stewart, meinen Fachgutachter, für die wertvolle Unterstützung, vor allem für die Rückmeldung und die Hilfe bei dem C++-Code im ganzen Buch.

Beim Schreiben habe ich immer um gnadenlos ehrliche Rückmeldung gebeten, und Bas Vodde hat mir genau das gegeben und mich mit umfangreicher Kritik zum gesamten Buch versorgt. Er war der unsichtbare Partner, den ich brauchte, um ehrlich zu mir selbst zu sein.

Mein besonderer Dank gilt Joe Miller, der die meisten Beispiele akribisch konvertiert hat, sodass sie unter Windows erstellt und ausgeführt werden können.

Vielen Dank auch an alle anderen Personen, die Ideen und unschätzbares Feedback zu diesem Buch beigesteuert haben: Steve Andrews, Kevin Brothaler, Marshall Clow, Chris Freeman, George Dinwiddie, James Grenning, Michael Hill, Jeff Hoffman, Ron Jeffries, Neil Johnson, Chisun Joung, Dale Keener, Bob Koss, Robert C. Martin, Paul Nelson, Ken Oden, Tim Ottinger, Dave Rooney, Tan Yeong Sheng, Peter Sommerlad und Zhanyong Wan. Ich bitte um Entschuldigung, falls ich irgendjemand nicht erwähnt haben sollte.

Ein Dankeschön auch an all diejenigen, die mir auf der Errata-Seite von Prag-Prog Rückmeldung gegeben haben: Bradford Baker, Jim Barnett, Travis Beatty, Kevin Brown, Brett DiFrischia, Jared Grubb, David Pol, Bo Rydberg, Jon Seidel, Marton Suranyi, Curtis Zimmerman und viele andere.

Noch einmal Dank an Tim Ottinger, der einige der Worte in der Einleitung sowie einige Ideen zu diesem Buch beigetragen hat. Ich habe dich als Mitverschwörer vermisst!

Danke an all diejenigen, die geholfen haben, dieses Buch besser zu machen, als ich es jemals allein hätte schaffen können!

Widmung

Dieses Buch ist all denen gewidmet, die mich fortwährend dabei unterstützen, das zu tun, was ich gern tue, insbesondere meiner Frau Kathy.

1 Einführung

1.1 Setup1

Zu den kniffligeren Aufgaben bei jedem Softwareprojekt gehört es, alles zu installieren und zum Laufen zu bekommen. In diesem Kapitel erfahren Sie, welche Werkzeuge Sie benötigen, um die in diesem Buch beschriebenen Beispiele zu erstellen und auszuführen. Außerdem lernen Sie einige wichtige Tipps kennen, um nicht die gleichen Fehler zu machen wie ich.

In den ersten Abschnitten erhalten Sie Informationen zur Einrichtung unter Linux und Mac OS. Empfehlungen für Windows-C++-Entwickler folgen in Abschnitt 1.3.3.

1.2 Die Beispiele

Die Quelldateien für dieses Buch können Sie von http://pragprog.com/titles/lotdd/source_code herunterladen. Die Beispiele sind nach Kapiteln geordnet.

Bei vielem von dem, was Sie über TDD lernen werden, geht es darum, Code inkrementell weiterzuentwickeln. Deshalb sind auch die Beispiele in den Kapiteln jeweils inkrementell erweiterte Versionen des gleichen Codes. Die Versionsnummern entsprechen dabei den Nummern der Unterverzeichnisse innerhalb des Verzeichnisses für das Kapitel. So befindet sich beispielsweise das erste Codebeispiel von Kapitel 2 in c2/1/SoundexTest.cpp, die zweite Version dagegen in c2/2/Soundex Test.cpp.

Der Beispielcode steht auch auf GitHub zur Verfügung (https://github.com/jlangr). Dort finden Sie für jedes Kapitel, das Code enthält, ein eigenes Repository. Beispielsweise enthält das Repository c2 das Soundex-Beispiel, das im zweiten Kapitel dieses Buches erstellt wird.

Die Versionsnummer für ein gegebenes Codebeispiel aus dem Buch entspricht einem Branch innerhalb eines GitHub-Repositorys. Beispielsweise finden Sie den Code für das Listing von c5/4/PlaceDescriptionService.cpp in der Datei Place DescriptionService.cpp innerhalb von Branch 4 des Repositorys c5.

Jedes Versionsverzeichnis enthält den notwendigen Quellcode einschließlich einer main-Funktion, um Tests auszuführen, und eines CMake-Build-Skripts. Um die Beispiele ausführen zu können, müssen Sie einige wenige Tools installieren und konfigurieren. Bei einigen Beispielen ist zusätzlich die Installation von Drittanbieter-Bibliotheken erforderlich.

Um die Beispiele zu erstellen, benötigen Sie einen C++11-konformen Compiler und ein Build-Tool. Bei den meisten ist Google Mock als Unit-Test-Werkzeug erforderlich. In drei Kapiteln wird für die Beispiele jedoch ein anderes Werkzeug für diesen Zweck verwendet, nämlich CppUTest.

Sie können die Quelldistribution ändern, um andere Compiler zu unterstützen (vor C++11) oder um ein anderes Build- oder ein anderes Unit-Test-Werkzeug zu verwenden. Zum Glück ist der Code der meisten Beispiele nicht umfangreich. Die einzige Ausnahme bildet der Bibliothekscode aus Kapitel 7.

In Tabelle 1-1 sind das Unterverzeichnis, das Unit-Test-Werkzeug und die zusätzlichen Drittanbieter-Bibliotheken angegeben, die für die Beispiele in den einzelnen Kapiteln erforderlich sind.

Kapitel

Verzeichnis

Unit-Test-Framework

Drittanbieter-Bibliotheken

2 Testgetriebene Entwicklung: Ein erstes Beispiel

c2

Google Mock

Keine

3 Testgetriebene Entwicklung: Grundlagen

c3

Google Mock

Keine

4 Tests konstruieren

c3

Google Mock

Keine

5 Testdoubles

c5

Google Mock

cURL, JsonCpp

6 Inkrementelles Design

c6

Google Mock

Boost (Gregorian)

7 Qualitativ hochwertige Tests

c7

Google Mock

Boost (Gregorian, Algorithm, Assign)

8 Herausforderungen durch Legacy-Code

wav

CppUTest

rlog, Boost (Filesystem)

9 TDD für Threads

c9

CppUTest

Keine

10 Weitere Aspekte von TDD

tpp

CppUTest

Keine

B Code-Kata: Umrechner für römische Zahlen

roman

Google Mock

Keine

Tab. 1–1 Verwendete Testframeworks und Drittanbieter-Bibliotheken

1.3 C++-Compiler

1.3.1 Ubuntu

Ursprünglich habe ich die Beispiele in diesem Buch unter Ubuntu 12.10 mit g++ 4.7.2 erstellt.

Zur Installation von g++ verwenden Sie folgenden Befehl:

sudo apt-get install build-essential

1.3.2 OS X

Die Beispiele in diesem Buch habe ich auch erfolgreich unter Mac OS X 10.8.3 (Mountain Lion) mithilfe eines gcc-Ports erstellt. Die Version 4.2 von gcc, die in der Zeit, als ich dieses Buch schrieb, mit Xcode ausgeliefert wurde, reicht zur erfolgreichen Kompilierung der C++-Beispiele nicht aus.

Um den gcc-Port zu installieren, benötigen Sie MacPorts, eine Infrastruktur, die die Installation von freier Software auf Ihrem Mac ermöglicht. Weitere Informationen darüber erhalten Sie auf http://www.macports.org/install.php.

Als Erstes sollten Sie MacPorts aktualisieren:

sudo port selfupdate

Installieren Sie anschließend den gcc-Port mit dem folgenden Befehl:

sudo port install gcc47

Die Ausführung dieses Befehls kann beträchtliche Zeit in Anspruch nehmen. (Sie können am Ende des port-Befehls auch die Variante +universal angeben. Dadurch wird die Kompilierung von Binärdateien sowohl für PowerPC- als auch für Intel-Architekturen ermöglicht.)

Nachdem Sie den gcc-Port erfolgreich installiert haben, müssen Sie ihn als Standard benennen:

sudo port select gcc mp-gcc47

Es ist sinnvoll, den Befehl zur Pfadnamenliste hinzuzufügen:

hash gcc

1.3.3 Windows

Um den Code auf Windows so zum Laufen zu bringen, wie er in diesem Buch (und damit auch in der Quelldistribution) erscheint, ist es am besten, einen MinGW-oder Cygwin-Port von g++ zu verwenden. Weitere Möglichkeiten sind unter anderem die CTP-Version des Microsoft Visual C++-Compilers von November 2012 sowie Clang. Aber zurzeit bieten diese keine ausreichende Unterstützung für den Standard C++11. In diesem Abschnitt gebe ich Ihnen einen kurzen Überblick über die Schwierigkeiten, die Beispiele unter Windows zum Laufen zu bekommen, sowie einige Lösungsvorschläge.

Visual-C++-Compiler, CTP-Version November 2012

Ein CTP-Release (Community Technology Preview) des Visual C++11-Compilers steht zum Download zur Verfügung.2 Beschrieben wird es in einem Blogeintrag3 des Visual C++-Teams.

Ein erster Versuch, die CTP-Version für die Beispiele in diesem Buch zu verwenden, ließ schnell die folgenden Probleme erkennen:

Die Memberinitialisierung innerhalb der Klasse scheint noch nicht vollständig unterstützt zu sein.

In der Bibliothek std ist die Unterstützung für C++11 besonders mangelhaft. Beispielsweise unterstützen die Collection-Klassen noch keine einheitlichen Initialisiererlisten. Auch gibt es noch keine Implementierung für std::unorde red_map.

Die von Google Mock und Google Test verwendeten variadischen Templates werden ebenfalls noch nicht vollständig unterstützt. Wenn Sie versuchen, Google Mock zu erstellen, wird ein Kompilierungsfehler ausgegeben. In einem solchen Fall müssen Sie den Präprozessordefinitionen einen Eintrag hinzufügen, der _VARIADIC_MAX für alle betroffenen Projekte auf 10 setzt. Weitere Informationen über die Behebung dieses Problems erhalten Sie auf http://stackoverflow.com/questions/12558327/google-test-in-visual-studio-2012.

Windows-Beispielcode

Kurz vor der Veröffentlichung dieses Buches habe ich mich bemüht, die Windows-Codebeispiele funktionsfähig zu machen (indem ich nicht unterstützte C++11-Elemente entfernt habe). Die umgearbeiteten Beispiele finden Sie in einem eigenen Satz von Repositorys (je eines pro Kapitel) auf meiner GitHub-Seite (https://github.com/jlangr). Weitere Informationen über die Windows-Beispiele veröffentliche ich nach und nach im Google-Groups-Forum unter https://groups.google.com/forum/?fromgroups#!forum/modern-cpp-with-tdd.

Die Windows-Repositorys auf GitHub enthalten Lösungsdateien (.sln) und Projektdateien (.vcxproj). Damit können Sie den Beispielcode in Visual Studio Express 2012 für Windows Desktop laden und mit MSBuild Tests für diese Beispiele erstellen und an der Befehlszeile ausführen.

Es sollte auch nicht allzu dramatisch sein, die Codebeispiele selbst umzuarbeiten. Eine Änderung der Initialisierung außerhalb der Klasse ist nicht schwer, und std::unordered_map können Sie einfach durch std::map ersetzen. Da viele der neuen Ergänzungen zu C++11 aus der Bibliothek boost::tr1 stammen, sollte es auch möglich sein, die Boost-Implementierungen direkt zu ersetzen.

Tipps zu Windows

Ich habe mich im Internet über eine Reihe von Hindernissen wie Kompilierungswarnungen und -fehler sowie andere Build-Probleme schlau gemacht. Dabei habe ich die in Tabelle 1-2 angegebenen Erkenntnisse gewonnen:

Fehler/Problem

Lösung

C297:'std:tuple': zu viele Template-Argumente

Fügen Sie die Präprozessordefinition _VARIADIC_MAX=10 hinzu (siehe http://stackover-flow.com/questions/8274588/c2977-stdtuple-toomany-template-argumentsmsvc11).

Das angegebene Plattform-Toolset (v110) ist nicht installiert oder ungültig.

Setzen Sie VisualStudioVersion auf 11.0.

Wo ist msbuild.exe?

Bei mir befindet sich diese Datei unter C:\Windows\Microsoft.NET\Frame-work\v4.0.30319.

Warnung C4996: 'std::_Copy_impl': Funktionsaufruf mit Parametern, die möglicherweise unsicher sind.

-D_SCL_SECURE_NO_WARNINGS

Das Konsolenfenster wird geschlossen, wenn Sie die Ausführung eines Tests mit abschließen.

Setzen Sie Konfigurationseigenschaften → Linker → System → Teilsystem auf Konsole (/SUBSYSTEM:CONSOLE).

Visual Studio versucht für Boost-Merkmale, die nur Headerdateien benötigen, automatisch eine Verknüpfung zu einer Bibliothek herzustellen.

Fügen Sie die Präprozessordirektive BOOST_ALL_NO_LIB hinzu.

Tab. 1–2 Fehler in Visual Studio und mögliche Lösungen

Viele der Lösungen für diese Probleme sind bereits in die Projektdateien eingearbeitet.

Vorschau auf Visual Studio 2013

Kurz vor Ablauf meiner Abgabefrist für allerletzte Änderungen an diesem Buch hat Microsoft erste Downloads für Visual Studio 2013 veröffentlicht, die eine erweiterte Konformität mit C++11 sowie die Unterstützung für einige vorgeschlagene Funktionen von C++14 zu bieten scheinen. Der Windows-Code auf GitHub ist zurzeit für die CTP-Version von November 2012 geeignet, aber es wird in Kürze neue Versionen geben, die C++11 noch besser nutzen, wenn wir (einige großartige Helfer und ich) in Visual Studio 2013 damit arbeiten. Ich hoffe, dass eine Windows-spezifische Version irgendwann gar nicht mehr notwendig sein wird. Freuen wir uns auf einen vollständig C++11-konformen Windows-Compiler!

1.4 CMake

Um plattformübergreifende Builds zu unterstützen, habe ich mich für CMake entschieden.

Die Version zum Erstellen der Beispiele für Ubuntu ist CMake 2.8.9. Zur Installation von CMake verwenden Sie folgenden Befehl:

sudo apt-get install cmake

Benutzer von OS X benötigen CMake 2.8.10.2. Die Installation können Sie mithilfe der Downloads auf http://www.cmake.org/cmake/resources/software.html durchführen.

Wenn Sie CMake für die bereitgestellten Build-Skripts ausführen, wird möglicherweise die folgende Fehlermeldung angezeigt:

Make Error: your CXX compiler: "CMAKE_CXX_COMPILER-NOTFOUND" was not found.Please set CMAKE_CXX_COMPILER to a valid compiler path or name.

Das bedeutet, dass kein geeigneter Compiler gefunden wurde. Das kann der Fall sein, wenn Sie gcc statt g++ installiert haben. Unter Ubuntu können Sie dieses Problem lösen, indem Sie build-essential installieren. Unter OS X definieren Sie CXX oder ändern die Definition dafür:

export CC=/opt/local/bin/x86_64-apple-darwin12-gcc-4.7.2export CXX=/opt/local/bin/x86_64-apple-darwin12-g++-mp-4.7

1.5 Google Mock

Das in vielen Beispielen dieses Buches verwendete Google Mock ist ein Framework zum Erstellen von Mocks (Attrappen) und zur Beschreibung von Annahmen. Es enthält das Unit-Test-Framework Google Test, wobei ich beide Begriffe in diesem Buch austauschbar verwende, der Einfachheit halber aber meistens nur von Google Mock schreibe. Für einige der Features, die ich als Bestandteile von Google Mock bezeichne, müssen Sie daher möglicherweise die Dokumentation von Google Test zurate ziehen.

Da Sie Google Mock mit den Beispielen verlinken, müssen Sie zunächst die Google-Mock-Bibliothek erstellen. Die folgenden Anleitungen helfen Ihnen dabei. Sie können sich auch die mit Google Mock mitgelieferte Datei README.txt ansehen, um ausführlichere Installationsanweisungen zu erhalten (siehe https://code.google.com/p/googlemock/source/browse/trunk/README).

1.5.1 Google Mock installieren

Die offizielle Google-Mock-Website ist https://code.google.com/p/googlemock/. Die Downloads finden Sie auf https://code.google.com/p/googlemock/downloads/list. Die Beispiele in diesem Buch wurden mit Google Mock 1.6.0 erstellt.

Entpacken Sie die heruntergeladene ZIP-Datei (z.B. gmock-1.6.0.zip) zum Beispiel in Ihrem Benutzerordner.

Erstellen Sie dann wie im folgenden Beispiel die Umgebungsvariable GMOCK_ HOME, die auf dieses Verzeichnis zeigt:

export GMOCK_HOME=/home/jeff/gmock-1.6.0

Unter Windows geht das wie folgt:

setx GMOCK_HOME c:\Users\jlangr\gmock-1.6.0

Unix

Wenn Sie unter Unix die Build-Anweisungen in der README-Datei überspringen wollen, können Sie auch, so wie ich es getan habe, mit den folgenden Schritten ans Ziel gelangen. Ich habe Google Mock mithilfe von CMake erstellt. Gehen Sie im Wurzelverzeichnis Ihrer Google-Mock-Installation (im Folgenden $GMOCK_HOME genannt) wie folgt vor:

mkdir mybuildcd mybuildcmake ..make

Das Build-Verzeichnis kann auch einen anderen Namen tragen, allerdings erwarten die Beispiele in diesem Buch mybuild. Wenn Sie diesen Namen ändern, müssen Sie auch alle CMakeLists.txt-Dateien anpassen.

Außerdem müssen Sie Google Test erstellen, das in Google Mock verschachtelt ist:

cd $GMOCK_HOME/gtestmkdir mybuildcd mybuildcmake ..make

Windows

In der Google-Mock-Distribution finden Sie die Datei .\msvc\2010\gmock.sln, die in Visual Studio 2010 und neueren Versionen funktionieren sollte. (Außerdem gibt es die Datei .\msvc\2005.gmock.sln, die für Visual Studio 2005 und 2008 vorgesehen ist.)

Um Google Mock in Visual Studio 2010 und 2012 zu kompilieren, müssen Sie die Projekte so einrichten, dass Sie die CTP von November 2012 nutzen. Öffnen Sie in den Projekteigenschaften Konfigurationseigenschaften → Allgemein → Plattformtoolsets und wählen Sie die CTP aus.

Die CTP bietet keine Unterstützung für variadische Templates. (In Visual Studio 2013 wird eine solche Unterstützung möglicherweise vorhanden sein.) Stattdessen werden solche Templates künstlich simuliert.4 Dazu müssen Sie mit einer Präprozessordefinition den Wert von _VARIADIC_MAX über die Standardeinstellung 5 hinaus anheben. Ein Wert von 10 ist gut geeignet.

Wenn Sie Projekte erstellen, die Google Mock nutzen, müssen Sie darin auf den richtigen Speicherort der Include- und Bibliotheksdateien verweisen. Öffnen Sie Konfigurationseigenschaften → Visual C++-Verzeichnisse und gehen Sie wie folgt vor:

Fügen Sie $(GMOCK_HOME)\msvc\2010\Debug zu den Bibliotheksverzeichnissen hinzu.

Fügen Sie $(GMOCK_HOME)\include zu den Include-Verzeichnissen hinzu.

Fügen Sie $(GMOCK_HOME)\gtest\include zu den Include-Verzeichnissen hinzu.

Fügen Sie gmock.lib unter Linker → Eingabe zu den zusätzlichen Abhängigkeiten hinzu.

Außerdem müssen Sie sicherstellen, dass Google Mock und Ihr Projekt mit demselben Speichermodell erstellt werden. Standardmäßig verwendet Google Mock /MTd.

1.5.2 Ein Main-Programm zum Ausführen von Google-Mock-Tests erstellen

Der Code für Beispiele in diesem Buch enthält jeweils eine main.cpp-Datei zur Verwendung mit Google Mock.

c2/1/main.cpp

#include "gmock/gmock.h"int main(int argc, char** argv) {   testing::InitGoogleMock(&argc, argv);   return RUN_ALL_TESTS();}

Die hier gezeigte main()-Funktion initialisiert als Erstes Google Mock und übergibt dabei alle eventuell bereitgestellten Befehlszeilenparameter. Weitere Informationen erhalten Sie unter http://code.google.com/p/googletest/wiki/Primer#Writing_the_main()_Function.

1.6 CppUTest

Bei CppUTest handelt es sich um ein weiteres Unit-Test-Framework für C++. Möglicherweise bevorzugen Sie es gegenüber Google Test/Google Mock, da es viele vergleichbare Features aufweist und überdies einen eingebauten Speicherleckdetektor bietet. Weitere Beispiele zur Verwendung von CppUTest finden Sie im Buch »Test Driven Development for Embedded C« von James Grenning [Gre 10].

1.6.1 CppUTest installieren

(Hinweis: Diese Anleitung gilt für CppUTest 3.3. Version 3.4 umfasst eine Reihe von Änderungen, wurde aber knapp vor meinem Abgabetermin veröffentlicht, sodass ich sie in diesem Buch nicht mehr berücksichtigen konnte.)

Die Website des Projekts CppUTest lautet http://www.cpputest.org/. Die Downloads finden Sie auf http://cpputest.github.io/cpputest/. Laden Sie die passende Datei herunter und entpacken Sie sie am besten in ein neues Verzeichnis namens cpputest innerhalb Ihres Benutzerordners.

Erzeugen Sie wie im folgenden Beispiel die Umgebungsvariable CPPUTEST_HOME:

export CPPUTEST_HOME=/home/jeff/cpputest

CppUTest können Sie mithilfe von make erstellen. Außerdem müssen Sie CppUTestExt erstellen, das Unterstützung für Mocks bietet:

cd $CPPUTEST_HOME./configuremakemake -f Makefile_CppUTestExt

Installieren Sie CppUTest mit dem Befehl make install in /usr/local/lib.

CppUTest können Sie auch mit CMake erstellen, wenn Ihnen das lieber ist. Für die Verwendung unter Windows werden Batchdateien für Visual Studio 2008 und 2010 bereitgestellt. Diese Dateien nutzen MSBuild.

1.6.2 Ein Main-Programm zum Ausführen von CppUTest-Tests erstellen

Der Code für das WAV-Reader-Beispiel in diesem Buch enthält die Datei test-main.cpp, die zur Verwendung mit CppUTest gedacht ist.

wav/1/testmain.cpp

#include "CppUTest/CommandLineTestRunner.h"int main(int argc, char** argv) {   return CommandLineTestRunner::RunAllTests(argc, argv);}

1.7 libcurl

libcurl bietet eine clientseitige Bibliothek zur URL-Übertragung, die HTTP und viele andere Protokolle unterstützt. Außerdem unterstützt sie das Tool cURL zur Übertragung von Befehlszeilen, weshalb ich die Bibliothek in diesem Buch als cURL bezeichne.

Die Website des Projekts cURL lautet http://curl.haxx.se/. Die Downloads finden Sie auf http://curl.haxx.se/download.html. Laden Sie die passende Datei herunter und entpacken Sie sie beispielsweise in Ihren Benutzerordner. Erzeugen Sie dann wie im folgenden Beispiel die Umgebungsvariable CURL_HOME:

export CURL_HOME=/home/jeff/curl-7.29.0

Um die Bibliothek zu erstellen, können Sie CMake verwenden:

cd $CURL_HOMEmkdir buildcd buildcmake ..make

1.8 JsonCpp

JsconCpp bietet Unterstützung für das Datenaustauschformat JSON (JavaScript Object Notation).

Die Website des Projekts JsonCpp lautet http://jsoncpp.sourceforge.net/. Die Downloads finden Sie auf http://sourceforge.net/projects/jsoncpp/files/. Laden Sie die passende Datei herunter und entpacken Sie sie zum Beispiel in Ihren Benutzerordner. Erzeugen Sie dann wie im folgenden Beispiel die Umgebungsvariable JSONCPP_HOME:

export JSONCPP_HOME=/home/jeff/jsoncpp-src-0.5.0

Für JsconCpp ist das Python-Build-System Scons erforderlich. Unter Ubuntu installieren Sie Scons wie folgt:

sudo apt-get install scons

Wechseln Sie in das Verzeichnis $JSONCPP_HOME und erstellen Sie die Bibliothek mithilfe von Scons:

scons platform=linux-gcc

Unter OS X geben Sie als Plattform linux-gcc an. Zumindest hat das bei meiner Installation funktioniert. Der Build-Vorgang für JsonCpp hat bei mir dazu geführt, dass die Datei $JSONCPP_HOME/libs/linux-gcc-4.7/libjson_linux-gcc-4.7_libmt.a angelegt wurde. Erstellen Sie wie folgt einen symbolischen Link dorthin:

cd $JSONCPP_HOME/libs/linux-gcc-4.7ln -s libjson_linux-gcc-4.7_libmt.a libjson_linux-gcc-4.7.a

1.9 rlog

rlog bietet eine Möglichkeit zur Protokollierung von Nachrichten für C++.

Die Website des Projekts rlog lautet https://code.google.com/p/rlog/. Laden Sie die passende Datei herunter und entpacken Sie sie zum Beispiel in Ihren Benutzerordner. Erzeugen Sie dann wie im folgenden Beispiel die Umgebungsvariable RLOG_HOME:

export RLOG_HOME=/home/jeff/rlog-1.4

Unter Ubuntu erstellen Sie rlog mit den folgenden Befehlen:

cd $RLOG_HOME./configuremake

Unter OS X konnte rlog nur nach der Anwendung eines Patches kompilieren. Weitere Informationen über dieses Problem sowie den Code des Patches finden Sie unter https://code.google.com/p/rlog/issues/detail?id=7. Ich habe den Code in dem dritten Kommentar verwendet (»This smaller diff ...«). Den Patchcode erhalten Sie auch in der Quelldistribution als code/wav/1/rlog.diff.

Um den Patch anzuwenden und rlog zu erstellen, gehen Sie folgendermaßen vor:

cd $RLOG_HOMEpatch -p1 [path to file]/rlog.diffautoreconf./configurecp /opt/local/bin/glibtool libtoolmakesudo make install

Der Befehl configure kopiert die Binärdatei libtool in das Verzeichnis rlog, aber dies ist nicht die von rlog erwartete Binärdatei! Der Befehl, der glibtool über libtool kopiert, korrigiert diesen Fehler.

Wenn der Patch bei Ihnen nicht funktioniert, können Sie versuchen, manuelle Änderungen vorzunehmen. In der Datei $RLOG_HOME/rlog/common.h.in finden Sie folgende Zeile:

# define RLOG_SECTION __attribute__ (( section("RLOG_DATA") ))

Ersetzen Sie sie durch Folgendes:

#ifdef _APPLE_# define RLOG_SECTION __attribute__ (( section("__DATA, RLOG_DATA") ))#else# define RLOG_SECTION __attribute__ (( section("RLOG_DATA") ))#endif

Sollten Sie dann immer noch Probleme damit haben, rlog zu erstellen (das ist sowohl unter Mac OS als auch unter Windows eine ziemliche Herausforderung!), verzweifeln Sie nicht! In dem Beispiel aus Abschnitt 8.9, in dem es um die Arbeit mit Legacy-Code geht, erfahren Sie, wie Sie komplett auf rlog verzichten können.

1.10 Boost

Boost bietet eine große Menge an grundlegenden C++-Bibliotheken.

Die Website des Projekts Boost lautet http://www.boost.org. Die Downloads finden Sie auf http://sourceforge.net/projects/boost/files/boost. Es werden regelmäßig aktualisierte Versionen bereitgestellt. Laden Sie die passende Datei herunter und entpacken Sie sie zum Beispiel in Ihren Benutzerordner. Erzeugen Sie dann wie im folgenden Beispiel Umgebungsvariablen sowohl für BOOST_ROOT als auch für die von Ihnen installierte Boost-Version:

export BOOST_ROOT=/home/jeff/boost_1_53_0export BOOST_VERSION=1.53.0

Viele Boost-Bibliotheken erfordern lediglich Headerdateien. Wenn Sie der vorstehenden Anweisung gefolgt sind, können Sie alle Beispiele, die Boost verwenden, erstellen. Die einzige Ausnahme bildet der Code in Kapitel 8.

Um ihn zu erzeugen, müssen Sie Bibliotheken erstellen und von Boost aus verlinken. Zum Erstellen habe ich folgende Befehle verwendet:

cd $BOOST_ROOT./bootstrap.sh --with-libraries=filesystem,system./b2

Diese Befehle sollten auch bei Ihnen funktionieren. Wenn nicht, lesen Sie die Anleitung unter http://ubuntuforums.org/showthread.php?t=1180792. (Beachten Sie jedoch, dass das Argument von bootstrap.sh nicht --with-library, sondern --with-libraries lauten muss.)

1.11 Beispiele erstellen und Tests ausführen

Nachdem Sie die passende Software installiert haben, können Sie alle Versionen der Beispiele erzeugen und anschließend die Tests ausführen. Im Verzeichnis für eine Version eines Beispiels erstellen Sie als Erstes mithilfe von CMake ein Makefile:

mkdir buildcd buildcmake ..

Der Legacy-Code (siehe Kap. 8) verwendet Bibliotheken von Boost, nicht nur die Header. CMakeLists.txt nutzt die Umgebungsvariable BOOST_ROOT, die Sie zweimal definiert haben: erstens ausdrücklich durch include_directories, um anzugeben, wo die Boost-Header zu finden sind, und zweitens implizit, wenn CMake find_package ausführt, um die Boost-Bibliotheken zu finden.

Wenn Sie einen Build des Legacy-Codes versuchen, erhalten Sie möglicherweise die Fehlermeldung, dass Boost nicht zu finden ist. In diesem Fall können Sie den Speicherort ändern, indem Sie bei der Ausführung von CMake einen Wert für BOOST_ROOT übergeben:

cmake -DBOOST_ROOT=/home/jeff/boost_1_53_0 ..

Anderenfalls müssen Sie dafür sorgen, dass Sie die Boost-Bibliotheken korrekt erstellt haben.

Nachdem Sie mit CMake ein Makefile angelegt haben, können Sie die Beispiele erstellen, indem Sie in deren Build-Verzeichnis wechseln und dort Folgendes ausführen:

make

Um Tests ablaufen zu lassen, führen Sie den folgenden Befehl ebenfalls im Build-Verzeichnis des Beispiels aus:

./test

Die ausführbare Datei für den Test des Bibliotheksbeispiels in Kapitel 7 finden Sie in build/libraryTests.

1.12 Teardown5

In diesem Kapitel haben Sie erfahren, was Sie benötigen, um die Beispiele in diesem Buch zu erstellen und auszuführen. Denken Sie immer daran, dass man am besten lernt, wenn man sich selbst die Finger schmutzig macht. Führen Sie die Beispiele also während der Lektüre aus.

Wenn Sie Probleme haben sollten, etwas einzurichten, wenden Sie sich zunächst an einen Vertrauten, der Ihnen eventuell weiterhelfen kann. Ein zweites Paar Augen findet oft schnell die Ursache für ein Problem, mit dem Sie lange gekämpft haben. Sie können auch die Webseite zu diesem Buch unter http://pragprog.com/titles/lotdd aufsuchen, wo Sie hilfreiche Tipps und ein Diskussionsforum finden. Wenn Sie und Ihr Helfer beide nicht mehr weiterkommen, senden Sie mir bitte eine E-Mail (in englischer Sprache).

2 Testgetriebene Entwicklung: Ein erstes Beispiel

2.1 Setup

Schreiben Sie einen Test, sorgen Sie dafür, dass er bestanden wird, und räumen Sie das Design auf. Mehr ist eigentlich nicht dran an TDD. Doch hinter diesen drei einfachen Schritten verbirgt sich ein hohes Maß an Know-how. Wenn Sie wissen, wie Sie sich TDD zunutze machen können, zahlt sich das in Form vieler Vorteile aus. Sollten Sie dagegen darauf verzichten, aus den Erfahrungen anderer zu lernen, werden Sie TDD wahrscheinlich schnell wieder aufgeben.

Sie werden jedoch nicht einfach ins kalte Wasser geworfen, sondern ich werde Sie durch die testgetriebene Entwicklung von Beispielcode führen, um Ihnen zu erklären, was bei den einzelnen Schritten geschieht. Am meisten lernen Sie, wenn Sie parallel zur Lektüre dieses Kapitels die Beispiele programmieren. Stellen Sie dazu sicher, dass Sie Ihre Umgebung passend eingerichtet haben (siehe Kap. 1).

Das Beispiel, das wir durcharbeiten werden, ist zwar nicht besonders groß, aber weder nutzlos noch trivial (allerdings auch nicht gerade nobelpreisverdächtig). Es bietet viele wichtige Erkenntnisse und veranschaulicht, wie Sie mit TDD einen relativ anspruchsvollen Algorithmus inkrementell ausarbeiten können.

Alles fertig zum Programmieren?

2.2 Der Soundex-Algorithmus

Suchmöglichkeiten sind in vielen Anwendungen erforderlich. Eine wirkungsvolle Suche sollte in der Lage sein, auch dann etwas zu finden, wenn der Benutzer den Suchbegriff falsch geschrieben hat. Mein Name wird ständig falsch geschrieben: Langer, Lang, Langur, Lange und Lutefisk sind nur einige Varianten. Natürlich möchte ich trotzdem gefunden werden.

In diesem Kapitel werden wir eine Soundex-Klasse testgetrieben entwickeln, um damit die Suchmöglichkeiten in einer Anwendung zu verbessern. Der bewährte Soundex-Algorithmus codiert Wörter in Form eines Buchstabens und dreier Ziffern, wobei ähnlich klingende Wörter in dieselbe Codierung überführt werden. Laut Wikipedia1 gelten für Soundex folgende Regeln:

Behalte den Anfangsbuchstaben bei. Verwirf alle weiteren Vorkommen von

a

,

e

,

i

,

o

,

u

,

y

,

h

,

w

.

Ersetze Konsonanten durch Ziffern (nach dem ersten Buchstaben):

b

,

f

,

p

,

v

: 1

c

,

g

,

j

,

k

,

q

,

s

,

x

,

z

: 2

d

,

t

: 3

l

: 4

m

,

n

: 5

r

: 6

Wenn zwei angrenzende Buchstaben in dieselbe Zahl umgewandelt werden, codiere sie stattdessen zu einer einzigen Zahl. Das Gleiche gilt, wenn zwei Buchstaben mit derselben Zahl durch

h

oder

w

getrennt sind. (Allerdings sind sie doppelt zu codieren, wenn sie durch einen Vokal getrennt sind.) Diese Regel gilt auch für den ersten Buchstaben.

Höre auf, wenn das Ergebnis aus einem Buchstaben und drei Ziffern besteht. Fülle ggf. mit Nullen auf.

2.3 Erste Schritte

Häufig besteht beim Thema TDD die irrige Annahme, dass man erst sämtliche Tests definieren müsste, bevor man mit der Implementierung beginnt. In Wirklichkeit jedoch konzentrieren Sie sich immer nur jeweils auf einen Test. Von diesem Test ausgehend betrachten Sie dann schrittweise das nächste Verhalten, das in das System einfließen soll.

Die allgemeine Vorgehensweise bei TDD besteht darin, immer zu versuchen, jeweils die einfachste Regel zu implementieren. (Eine genauere, formalere Vorgehensweise für TDD bietet TPP, siehe Abschnitt 10.4.) Für welches nützliche Verhalten ist das einfachste, kleinste Codeinkrement zu implementieren?

Wie fangen wir also damit an, unsere Soundex-Lösung testgetrieben zu entwickeln? Überlegen wir kurz, was die Implementierung der einzelnen Regeln mit sich bringt.

Soundex-Regel 3 scheint die komplizierteste zu sein, und Regel 4, die angibt, wann die Codierung beendet ist, lässt sich sinnvoller in Angriff nehmen, wenn durch die Implementierung der anderen Regeln schon etwas codiert wurde. Bei der zweiten Regel sollte der erste Buchstabe schon vorhanden sein. Beginnen wir also mit Regel 1. Das scheint die offensichtlichste Vorgehensweise zu sein.

Die erste Regel teilt uns mit, dass wir den ersten Buchstaben des Suchbegriffs beibehalten müssen und ... Aber halt! Wir wollen die Sache so klein halten wie nur möglich. Was geschieht, wenn das Wort nur aus einem einzigen Buchstaben besteht? Schreiben wir also einen Test, um die Entwicklung für diese Situation zu steuern.

c2/1/SoundexTest.cpp

Zeile 1  #include "gmock/gmock.h"      2  TEST(SoundexEncoding, RetainsSoleLetterOfOneLetterWord) {      3  Soundex soundex;      4  }

Testlisten

Mit jedem Test, den Sie in TDD schreiben und erfolgreich durchführen, haben Sie dem System ein weiteres, funktionierendes Verhalten hinzugefügt. Bestandene Tests tragen aber nicht nur dazu bei, ein weiteres Leistungsmerkmal auszuliefern, sondern stellen auch das bestmögliche Maß für den Fortschritt dar. Benennen Sie jeden Test mit einer Beschreibung des neuen Verhaltens.

Zwar legen Sie nicht alle Tests im Voraus fest, aber wahrscheinlich haben Sie schon zu Anfang einige Vorstellungen davon, was Sie alles abdecken müssen. Viele TDD-Entwickler sammeln ihre Gedanken über die kommenden Tests in einer Testliste (wie sie zuerst in »Test Driven Development: By Example« [Bec02] beschrieben wurde). Eine solche Liste kann die Namen von Tests oder Hinweise für notwendige Aufräumarbeiten enthalten.

Die Testliste können Sie auf einen Notizblock neben Ihrem Computer schreiben. (Es ist auch möglich, sie in Form von Kommentaren ganz unten in Ihre Testdatei einzufügen – allerdings dürfen Sie dann nicht vergessen, sie vor dem Einchecken zu löschen.) Diese Liste ist nur für Sie selbst gedacht, Sie können sie also so kurz oder so kryptisch halten, wie Sie wollen.

Wenn Ihnen bei der testgetriebenen Entwicklung neue Testfälle einfallen, schreiben Sie sie auf die Liste. Wenn Sie Code hinzufügen, der aufgeräumt werden muss, fügen Sie in der Liste einen entsprechenden Hinweis ein. Wenn Sie einen Test oder eine Aufgabe erledigen, streichen Sie sie von der Liste. So einfach ist das. Wenn die Liste am Ende Ihrer Programmiersitzung noch ausstehende Einträge aufweist, heben Sie sie für die nächste Sitzung auf.

Die Testliste können Sie auch als einen Bestandteil des ursprünglichen Designs ansehen. Sie macht Ihnen klar, was Sie Ihrer Meinung nach alles erstellen müssen. Außerdem kann sie Ihnen dabei helfen, auch an andere Dinge zu denken, die ebenfalls zu erledigen sind.

Lassen Sie sich durch die Testliste aber nicht in dem einschränken, was Sie tun, oder in der Reihenfolge, in der Sie es tun. TDD ist ein flexibler Vorgang. Normalerweise sollten Sie in die Richtung weiterarbeiten, die durch die Tests vorgegeben wird.

Die Verwendung von Testlisten ist besonders dann hilfreich, wenn Sie TDD erlernen. Probieren Sie es aus!

In Zeile 1 beziehen wir gmock mit ein. Damit steht uns der gesamte erforderliche Funktionsumfang zum Schreiben von Tests zur Verfügung.

Eine einfache Testdeklaration macht die Verwendung des Makros TEST erforderlich (Zeile 2). Dieses Makro nimmt zwei Parameter entgegen, nämlich den Namen des Testfalls und einen beschreibenden Namen für den Test. Ein Testfall ist laut der Dokumentation von Google eine zusammenhängende Gruppe von Tests, die »Daten und Unterroutinen gemeinsam nutzen«2. (Der Begriff ist jedoch mehrdeutig: Für manche ist ein Testfall ein einzelnes Szenario.)

Wenn Sie den Namen des Testfalls und des Tests hintereinander von links nach rechts lesen, ergibt sich ein Satz, der beschreibt, was wir überprüfen wollen: »Soundex encoding retains [the] sole letter of [a] one-letter word.« (Bei der Soundex-Codierung wird der einzige Buchstabe eines einbuchstabigen Worts beibehalten.) Bei weiteren Tests für das Codierverhalten von Soundex verwenden wir ebenfalls SoundexEncoding als Namen für den Testfall, um zusammengehörige Tests zu gruppieren.

Sehen Sie die Auswahl von Testnamen nicht als unwichtig an (siehe dazu auch den folgenden Kasten).

Die Wichtigkeit von Testnamen

Gehen Sie bei der Benennung mit Bedacht vor. Die kleine Anstrengung, sich ausführlich beschreibende Testnamen auszudenken, zahlt sich mit der Zeit aus, wenn die Tests von anderen Personen gelesen werden, die den Code pflegen müssen. Ein guter Name hilft auch Ihnen als Entwickler des Tests, genau zu verstehen, was Sie eigentlich erstellen wollen.

Für jedes neue Verhalten im System werden Sie eine Reihe von Tests schreiben. Stellen Sie sich die Gesamtmenge der Testnamen als ein Verzeichnis vor, das einem Entwickler schnell eine prägnante Zusammenfassung des Verhaltens gibt. Je verständlicher die Testnamen sind, umso schneller können Sie und andere Entwickler finden, was sie suchen.

In Zeile 3 haben wir ein Soundex-Objekt erstellt, und dann ... Aber halt! Bevor wir mit dem Testen weitermachen können, müssen wir schon feststellen, dass wir gerade Code eingeführt haben, der nicht kompiliert werden kann – denn eine Soundex-Klasse ist ja noch gar nicht definiert! Brechen wir also vorübergehend das Schreiben des Tests ab, um den Fehler zu korrigieren, und machen danach weiter. Diese Vorgehensweise ist im Einklang mit den drei Regeln von TDD, die »Uncle Bob« aufgestellt hat:

Schreiben Sie Produktionscode nur, um einen nicht bestandenen Test zu bestehen.

Gestalten Sie einen Unit Test nicht ausführlicher, als nötig ist, um ihn scheitern zu lassen. Kompilierungsfehler sind Fehler.

Schreiben Sie nur den Produktionscode, der erforderlich ist, um einen einzigen nicht bestandenen Test zu bestehen.

Uncle Bob ist übrigens Robert C. Martin. Mehr über diese Regeln erfahren Sie in Abschnitt 3.4.