Will ein Entwickler seine Software mit automatisierten Tests prüfen, muss er komplexe Randbedingungen wiederholbar machen. Dabei helfen ihm Mock-Objekte, die das Verhalten von Produktionscode simulieren, ob es sich nun um ein Netzwerk oder eine Datenbankanbindung handelt.
Software-Entwickler sehen sich immer wieder mit Innovationen konfrontiert, die ihre Arbeit beschleunigen und vereinfachen sollen. Aktuelle Techniken wie agile oder gar extreme Programmierung klingen zwar nach schweißtreibender Arbeit, führen aber einige praktische Techniken ein: Test-getriebene Entwicklung und Unit-Tests.
Komponenten testen
Unit-Tests setzen auf einer sehr niedrigen Ebene an, testen also möglichst einfache Programmkomponenten einzeln. Idealerweise testen sie genau eine Methode. So bleiben die Tests selbst einfach und frei von Seiteneffekten. Ein Fehler ist meist leicht lokalisierbar.
Allerdings ist diese Ebene nicht immer erreichbar. Oft hängen Klassen voneinander ab oder beeinflussen sich gegenseitig während des Programmablaufs. In solchen Fällen simulieren Mock-Objekte die zeitliche Umgebung (zum Namen siehe den Kasten “Was bedeutet Mock?”). Sie ersetzen während des Testablaufs den Produktionscode des zu testenden Objekts.
Entwickeln mit Tests
Software wird üblicherweise zuerst als Ganzes geplant, danach programmiert und abschließend getestet. Diese Zyklen können sich bei Bedarf wiederholen. Das Test-getriebene Programmieren stellt diese Vorgehensweise auf den Kopf: Das Testen steht am Anfang. Da zu diesem Zeitpunkt aber noch kein passender Programmcode existiert, schlägt der erste Test fehl.
In einem zweiten Schritt behebt der Entwickler diesen Mangel und programmiert den Produktionscode in spe so weit, bis der Test erfolgreich verläuft. Er erstellt also seine Software schrittweise, indem er zuerst eine Erwartung (Expectation) an das künftige Programm formuliert und danach genau diese Erwartung erfüllt. Auf diese Weise entsteht häppchenweise eine Applikation, die der geforderten Aufgabe entspricht.
Soweit die Theorie. Natürlich sind derartige Tests noch keine Garantie für korrekte Programme. Aber durch das Schreiben von Tests setzt sich der Programmierer intensiver mit seinem Code auseinander und kommt daher dem Ziel zumindest näher. Fast noch wichtiger: Er kann es bereits während der Entwicklung erreichen. Es ist nicht mehr nötig, Teile der Software wie Bananen beim Kunden reifen zu lassen.
Dynamische Umgebung simulieren
Die ständige Wiederholung der Tests stellt auch bei späteren Wartungsarbeiten die Zuverlässigkeit der Software sicher. Wichtig ist dabei, dass der Produktionscode auf die Tests abgestimmt ist. Darin besteht das größte Hindernis für die Übernahme von bestehendem Code in Unit-Tests: Oft lassen sich Testdaten nur schwer erzeugen, denn die internen Abhängigkeiten sind zu groß.
In solchen Fällen empfiehlt es sich, den Programmcode nur schrittweise zu verändern, zum Beispiel bei sowieso anstehenden Wartungsarbeiten, wenn ausreichend Zeit zur Verfügung steht.
|
Installation |
|---|
|
Die beiden Bibliotheken Cpp-Unit und Mockpp lassen sich ohne große Klimmzüge installieren, denn sie benutzen beide Autoconf, sie bringen ein Configure-Skript mit [3]. Sind sie auf dem System installiert, genügt es, die beiden Bibliotheken beim Linken anzugeben: c++ -c consumer.cpp c++ -o basicmock basicmock.cpp consumer.o -lmockpp -lcppunit Die Ausführung der Programme setzt voraus, dass der dynamische Linker die Bibliotheken findet. Je nach Installationsort braucht er dazu einen passenden Eintrag in der »/etc/ld.so.conf«, zum Beispiel die Zeile »/usr/local/lib«. Der Befehl »/sbin/ldconfig« aktualisiert anschließend den Linker-Cache. Die Beispielprogramme für diesen Artikel lassen sich einfach mit dem mitgelieferten Makefile kompilieren. |
Test-getriebene Entwicklung ist nur mit kurzen Turnaround-Zeiten praktikabel, also jener Zeit, die benötigt wird, den Programmcode zu übersetzen und zu starten. Leider eignet sich C++ hierfür aufgrund ihrer langen Kompilier- und Link-Zeiten nur bedingt.

Abbildung 1: Cpp-Unit-Tests mit wechselndem Erfolg: Der erste Test ist erfolg-reich. Der zweite schlug fehl und zeigt erwartete sowie tatsächliche Ergebnisse.
Programmiertools
Ein Kompromiss besteht darin, Test- und zugehörigen Produktionscode vor dem eigentlichen Testen zu schreiben. In geeigneten Zeiträumen wird dann übersetzt und der Erfolg kontrolliert. Um dies nicht während des täglichen Trubels zu vergessen, verwendet der Autor dieses Artikels dafür eine einfache Umgebung in Form eines Editors und eines Terminalfensters. Mit dem Editor im Vordergrund programmiert er wie gewohnt. Im Terminalfenster im Hintergrund startet ein Skript periodisch die Übersetzung. Wenn dabei oder im Testlauf Probleme auftreten, färbt sich das Fenster rot. Auf diese Weise genügt es, einen kleinen Ausschnitt des Terminalfensters zu sehen, um den aktuellen Teststatus leicht zu erkennen.
|
Was bedeutet Mock? |
|---|
|
Das Maskottchen einiger Mock-Objekbibliotheken ist eine etwas seltsam aussehende Schildkröte. Ihre Ahnherrin stammt aus dem Buch “Alice im Wunderland” des Mathematikers Lewis Carroll. Dort trifft Alice irgendwann auf die falsche Suppenschildkröte, eben die Mock Turtle. Das englische to mock übersetzt man am besten mit vortäuschen oder nachmachen. |
Entwicklungsumgebungen, die den Output eines Testlaufs auswerten und den Cursor automatisch entsprechend positionieren, bieten aber mehr Komfort. Für die Entwicklungsumgebung KDevelop entsteht derzeit unter[1] ein Plugin, das diese Funktionen zur Verfügung stellen soll.
Lösung für C++
Mittlerweile gibt es mehrere Frameworks für Unit-Tests unter C++. Das bekannteste ist wohl Cpp-Unit[2]. Es läuft auf vielen Plattformen und stellt recht geringe Ansprüche an den Compiler. Einige Hinweise zum Gebrauch gibt der Kasten “Installation”.
In Abbildung 1 ist der Ablauf des Demoprogramms »cppunit.cpp« zu sehen: Der erste Lauf führt die zugehörigen zwei Unit-Tests erfolgreich aus. Der darauf folgende Aufruf wird durch den Parameter »bad« gestört. Cpp-Unit gibt daraufhin eine Liste der Stellen im Quelltext samt Fehlerbeschreibung aus. Das vollständigen Listing »cppunit.cpp« ist wie die anderen hier vorgestellten Programme unter[3] verfügbar.
|
Listing 1: |
|---|
01 class PoorMock : public Interface
02 {
03 unsigned counter;
04 unsigned opener;
05 unsigned reader;
06
07 void open(const string & /**/)
08 {
09 opener = ++counter;
10 }
11
12 string read()
13 {
14 reader = ++counter;
15 return "dummy";
16 }
17 };
18
19 // Test starten
20 PoorMock mock;
21 Consumer consumer(&mock);
22 consumer.load();
23
24 // Zählerstände prüfen
25 ASSERTER(mock.opener == 1);
26 ASSERTER(mock.reader == 2);
|
Um ein reales Objekt zu simulieren, stellt dessen Mock verschiedene Funktionen zur Verfügung. Es muss definierte Rückgabewerte für Methoden vorbereiten, C++-Ausnahmen angeben, Übergabeparameter prüfen und die Reihenfolge der Aufrufe sicherstellen.
Für Sprachen wie Java[4] oder Ruby (siehe Artikel in diesem Heft) gibt es schon seit einiger Zeit mehrere Lösungsansätze. Mockpp[5] stellt solche Funktionalität auch unter C++ bereit. Diese Bibliothek setzt verschiedene Lösungen aus der Java-Welt in C++ um, siehe[6],[7],[8]. Da die Bibliothek intensiven Gebrauch von Templates macht, ist ein aktueller Compiler nötig.
Beispiel: Dateizugriff
Ein einfaches Beispiel, das sich am Online-Tutorial orientiert, zeigt die Anwendung der Mockpp-Bibliothek: Eine zu testende Klasse »Consumer« (»consumer.cpp«, »consumer.h«) liest eine Konfigurationsdatei ein, verarbeitet sie und schreibt die geänderten Daten zurück. Dieses File enthält in jeder Zeile einen Datensatz. Dem Zugriff auf die Daten dient im Produktionscode eine Helferklasse »ConfigFile«. Sie stellt Operationen zum Öffnen und Schließen der Datei sowie zum Lesen und Schreiben eines Datensatzes zur Verfügung.
Für die Tests ersetzt man die Helferklasse des Produktionscode durch ein geeignetes Mock-Objekt, das die Zugriffe verfolgt und überprüft. Eine virtuelle Basisklasse »Interface« stellt die Schnittstelle für Produktion und Test bereit:
class Interface
{
public:
virtual void open(const string &n) = 0;
virtual string read() = 0;
virtual void write(const string &s) = 0;
virtual void close() = 0;
};
Denkbar ist es auch, das Mock-Objekt von der Helferklasse abzuleiten. Doch sind hier Seiteneffekte möglich, die den Testablauf negativ beeinflussen.
Lösung für Arme
Oft genügen bereits sehr einfache Mock-Objekte, die sicherstellen, dass das getestete Programm eine Methode überhaupt aufruft. In diesem Fall reicht es, wenn die Mock-Methode immer den gleichen Wert zurückliefert. Dann bietet es sich an, selbst gebaute Mock-Objekte zu verwenden. Sie erfüllen die Anforderungen mit möglichst einfachen Mitteln. Dadurch erhöht sich die Ausführungsgeschwindigkeit der Tests, die bei größeren Projekten durchaus relevant sein kann. Auf der anderen Seite leidet aber die Verständlichkeit, wenn die Programmierer eines Projekts unterschiedliche Lösungswege verwenden.
|
Listing 2: Mock-Objekt mit |
|---|
01 class BasicMock : public Interface
02 , public MockObject
03 {
04 virtual void open(const string &name)
05 {
06 open_name.addActual(name);
07 }
08
09 virtual string read()
10 {
11 return read_data.nextReturnObject();
12 }
13
14 virtual void close()
15 {
16 closer.inc();
17 }
18
19 ExpectationList<string> open_name;
20 ReturnObjectList<string> read_data;
21 ExpectationCounter closer;
22 };
23 // Vorbereiten der Mock-Daten
24 mock.open_name.addExpected("datei1.lst");
25 mock.read_data.addObjectToReturn("Daten-1");
26 mock.closer.setExpected(2);
27
28 // Ablauf des Tests
29 Consumer consumer(&mock);
30 consumer.load();
31 consumer.process();
32 consumer.save();
33 mock.verify();
|
|
Listing 3: Erweitertes |
|---|
01 class VisitMock : public Interface
02 , public VisitableMockObject
03 {
04 VisitMock()
05 : VisitableMockObject("VisitMock", 0)
06 , MOCKPP_CONSTRUCT_MEMBERS_FOR_VOID_VISITABLE_EXT1(open, ext)
07 , MOCKPP_CONSTRUCT_MEMBERS_FOR_VISITABLE0(read)
08 , MOCKPP_CONSTRUCT_MEMBERS_FOR_VOID_VISITABLE0(close)
09 {}
10
11 MOCKPP_VOID_VISITABLE_EXT1(VisitMock,
12 open, const string &,
13 ext, string);
14
15 MOCKPP_VISITABLE0(VisitMock, string, read);
16
17 MOCKPP_VOID_VISITABLE0(VisitMock, close);
18 };
|
Listing 1 zeigt einen Ausschnitt aus einer sehr einfachen Lösung für das Testproblem. Der Zähler »counter« protokolliert die Aufrufe. Bei jedem Aufruf speichern Merker für die jeweilige Methode den Zählerstand. Der Rückgabewert der Methode »read()« ist immer der gleiche String «dummy«. Am Ende prüft der Test die Zählerstände der Methoden.
Grundlegende Mock-Objekte
Um nur zu testen, ob Funktionen aufgerufen werden, genügen solche einfachen Mock-Objekte. Für komplexere Testanwendungen muss man auf den umfangreichen Werkzeugkasten von Mockpp zurückgreifen.
Die Bibliothek teilt sich in zwei Gruppen von Klassen: grundlegende Mock-Klassen, die verschiedene Arten einfacher Erwartungen (Expectations) bereitstellen, und erweiterte Klassen mit höherer Funktionalität. Allen grundlegenden Klassen ist gemein, dass sich über Methoden wie »setExpected()« Erwartungen formulieren lassen. Später verifiziert der Tester mit Aufrufen wie »setActual()« diese Erwartungen. Der Abgleich kann sofort erfolgen oder erst beim Aufruf von »verify()«.
Listing 2 zeigt eine Anwendung, die einige der einfachen Klassen zu einem Mock-Objekt für die Lösung der Beispielaufgabe organisiert. »ExpectationList« (Zeile 19) bereitet die Vergleichswerte der Parameter für Funktionsaufrufe in der gewünschten Reihenfolge vor.
Genauso stellt »ReturnObjectList« (Zeile 20) eine bestimmte Menge an Rückgabewerten bereit. »ExpectationCounter« (Zeile 21) schließlich zählt die Methodenaufrufe, die nicht über Parameter oder Rückgabewerte verifizierbar sind. Um nicht jedes einzelne Objekt dieser Gruppe getrennt überprüfen zu müssen, registriert man sie beim übergeordneten Mock-Objekt.
Makros und Erweiterungen
Komplexere Aufgaben lassen sich mit einfachen Mock-Objekten nur umständlich lösen: zum Beispiel die Reihenfolge mehrerer Methodenaufrufe und deren Parameter testen. Hier helfen erweiterte Mock-Objekte weiter. Damit lassen sich auch definierte Rückgabewerte einstellen oder C++-Ausnahmen werfen. Dies kann entweder anhand der übergebenen Parameter geschehen oder unabhängig von ihnen.
Erweiterte Mock-Objekte lassen sich mit Hilfe von C-Makros erstellen. Da es schwierig ist, die richtigen zu finden, erläutert der Kasten “Welches Makro soll es sein?” die Vorgehensweise näher. Die Makros implementieren den Code für interne Variablen und Konstruktoren sowie die emulierten Methoden selbst. Vor den eigentlichen Tests stellt ein Helferobjekt das Verhalten des Mock-Objekts ein. Listing 3 implementiert den Beispieltest in der Klasse »VisitableMockObjekt«. In den Zeilen 6 bis 8 übernehmen Makros wie beschrieben die Konstruktion der Member-Funktionen.
Test-Besuch
Der Name »VisitableMockObjekt« rührt daher, dass das Mock-Objekt entsprechend dem späteren Testablauf aufgerufen oder besucht wird (to visit). Das Testprogramm speichert also den Ablauf des Produktionscode ab und reproduziert ihn beim Testen. Ein sinnvoller Einsatz ist daher nur möglich, wenn die Methodenparameter exakt verglichen werden dürfen. Eine Toleranz ist nicht einstellbar, auch nicht eine variable Kombination der Parameter.
|
Welches Makro soll es |
|---|
|
Einer der wirklich schwierigen Aspekte der Mockpp-Bibliothek ist, die Makro-Namen für die Implementation der simulierten Methoden herauszufinden. Die große Anzahl verfügbarer Makros (ungefähr 140 gibt es) erscheint zunächst schwer überschaubar. Aber da sich diese Anzahl aus der Kombination von »const« und »void« sowie der Parameteranzahl ergibt, steckt ein letztlich einfaches System dahinter. Als Besonderheit gelten die Datentypen für die Methodenparameter indirekt auch für die zugehörigen internen Hilfsvariablen. Daraus folgt, dass bei einigen Datentypen der Typ für die Methode und der interne Typ getrennt anzugeben sind. Die einfachen Makros setzen implizit voraus, dass alle Methodenparameter »const &« sind, also eine konstante Referenz. Falls dies nicht zutrifft, existiert noch eine erweiterte Form des Makros, die beide Typen getrennt aufführt. Die erweiterte Form ist auch bei überladenen Methoden erforderlich, da sich die Namen der internen Hilfsvariablen vom Methodennamen ableiten. Es empfiehlt sich deshalb ein schrittweises Vorgehen: eine Methode nach der anderen umsetzen und kompilieren. Fehlermeldungen, die sich auf Makros beziehen, erhalten alle dieselbe Zeilennummer. Das gestaltet die Fehlersuche mitunter ziemlich mühsam. Das Makro für die Initialisierungsliste im Konstruktor setzt sich so zusammen: Das Makro für die Implementation der Methode ergibt sich durch folgende Schritte: Das Makro für das Helferobjekt ergibt sich durch zwei einfache Schritte: entweder »MOCKPP_CHAINER_FOR« für Chainable-Objekte oder »MOCKPP_CONTROLLER_FOR« für die Variante Visitable. Bei erweiterten Makros kommt noch »_EXT« hinzu. |
Weitere Einflussmöglichkeiten für diese Mock-Objekte stehen mit Helferobjekten zur Verfügung. Sie legen die Reaktion der getesteten Methode den übergebenen Parametern entsprechend fest. Die Methode »addResponseValue()« legt einen Rückgabewert für eine bestimmte Kombination an Parametern fest, »addResponseThrowable()« wirft eine Ausnahme. Zusätzlich kann man festlegen, dass die Ausnahme erst nach einer bestimmten Menge an Rückgabewerten zu werfen ist. Damit lassen sich zum Beispiel Übertragungsfehler bei einer Netzwerkverbindung simulieren.
Listing 4 konfiguriert das Mock-Objekt für die Beispielaufgabe. So legt in Zeile 10 die Methode »addResponseValue()« den Rückgabewert für den Aufruf von »read()« fest. Das Programm ruft in den Zeilen 5 bis 7 die erwarteten Methoden mit den erwarteten Parametern in der richtigen Reihenfolge auf und hält damit den Ablauf fest, dem später die Tests folgen. In Zeile 13 aktiviert es das Mock-Objekt und startet damit den Testlauf.
Einen anderen Ansatz verfolgen so genannte Chainable Mock Objects. Sie verdanken ihren Namen der Tatsache, dass ein Test Erwartungen in Form verketteter Methodenaufrufe eines temporären Objekts formuliert.
Mock-Objekte verketten
Mit Chainable Mock Objects lassen sich auch komplexe Abläufe im Test modellieren. Die Kette von Methodenaufrufen ähnelt dabei der Formulierung in englischer Sprache. In C++ sieht eine solche Verkettung so aus:
chainer.expects( once() )
.after("anderer")
.with(new IsEqual<int>(321))
.will(new ReturnStub(123))
.id("dieser");
- Das Objekt erwartet (»expects«) einen Aufruf genau
ein Mal (»once«). - Dies muss nach (»after«) einem anderen Aufruf von
»anderer« geschehen. - Die Methode muss mit (»with«) dem Wert
»321« aufgerufen werden. - Dann wird (»will«) der Rückgabewert
»123« sein. - Der Aufruf hat den eindeutigen Bezeichner (»id«)
»dieser«
Verkettete Erwartungen enthalten immer eine oder mehrere Teil-Erwartungen. Meist spezifizieren sie, wie oft die Kette zutreffen soll. Die häufigsten Anwendungen (Mindest- oder Maximalwert sowie exakte Anzahl) bringt Mockpp schon mit.
Verkettete Mock-Objekte erlauben das Modellieren des zeitlichen Ablaufs von Methodenaufrufen. Damit lässt sich festlegen, dass der Testaufruf vor oder nach einem anderen erfolgen soll. Für den Vergleich vergibt der Programmierer eindeutige Bezeichner.
|
Listing 4: |
|---|
01 VisitMock mock;
02 MOCKPP_CONTROLLER_FOR(VisitMock, read) read_contr (&mock);
03
04 // Ablauf beim Einlesen "aufzeichnen"
05 mock.open("datei1.lst");
06 mock.read();
07 mock.close();
08
09 // Rückgabewerte für read()
10 read_contr.addResponseValue("Daten-1");
11
12 // Mock-Objekt aktivieren
13 mock.activate();
|

Abbildung 2: Ausgabe der implementierten Mock-Objekte beim Testen: Je nach Komplexität liefern die Tests mehr oder weniger gute Hinweise auf den Fehler.
Die letzte wichtige Teil-Erwartung betrifft die Methodenparameter. Auch hier kennt Mockpp die üblichen Anwendungsfälle. Sie umfassen die exakte oder ungefähre Übereinstimmung der Parameter und ihre Größe im Vergleich zu einem Referenzwert sowie logische Verknüpfungen der Ergebnisse solcher Teil-Erwartungen. Die Reaktion der Methode, also welchen Wert sie zurückliefert oder ob sie stattdessen eine Ausnahme wirft, lässt sich auf gleiche Weise einstellen.
Erwartungen und Abfragen
Alle beschriebenen Schritte sind für spezielle Problemfälle durch eigene Klassen erweiterbar. Vorhandene Funktionen, die bestimmte Objekte erzeugen, erhöhen die Lesbarkeit. Der Aufruf von »once()« zum Beispiel ersetzt die wesentlich längere Sequenz »new InvokeOnceMatcher <T>()«. Genauso ersetzt »eq()« das längere »new IsEqual<T>()«.
Beim Erstellen der Ketten sollte man zwischen den tatsächlichen Erwartungen und den Abfragen an das Mock-Objekt unterscheiden. Die Erwartungen lassen sich mit »expects()« angeben, die Abfragen mit »stubs()«.
Der oberflächliche Unterschied ist syntaktischer Natur und soll nur die dahinter stehende Absicht anzeigen. Der interne Unterschied besteht darin, dass »stubs()« keine Parameter braucht und das Mock-Objekt dann beliebig oft aufgerufen werden darf.
Tests durchlaufen die aufgestellten Erwartungsketten in der Reihenfolge des Quelltextes. Sobald die vorbestimmte Menge an Aufrufen für eine Kette erreicht ist, gliedert der Test sie aus. Am Ende müssen schließlich alle Erwartungen erfüllt sein. Listing 5 zeigt eine weitere Lösung der Beispielaufgabe mit Hilfe der Chainable-Mock-Objekte. Für jede Methode legt der Test ein Hilfsobjekt an (Zeilen 2 bis 5).
Abbildung 2 zeigt die Ausgabe der vier beschriebenen Lösungsvarianten, wenn das Beispielprogramm einen häufig auftretenden Fehler enthält: Die geöffnete Datei wird nicht mehr durch »close()« geschlossen. Den zur Verfügung stehenden Möglichkeiten entsprechend weisen die Tests mehr oder weniger exakt auf die Fehlerursache hin.
|
Listing 5: |
|---|
01 //Hilfsobjekte vorbereiten
02 ChainMock mock;
03 MOCKPP_CHAINER_FOR_EXT(ChainMock, open, ext) opener (&mock);
04 MOCKPP_CHAINER_FOR(ChainMock, read) reader (&mock);
05 MOCKPP_CHAINER_FOR(ChainMock, close) close_chainer (&mock);
06
07 // Erwartungen für das Einlesen der Datei
08 opener.expects(once())
09 .with(eq(string("datei1.lst")))
10 .before("reader");
11
12 reader.stubs()
13 .will(onConsecutiveCalls(
14 new ReturnStub<string>("Daten-1"),
15 new ReturnStub<string>("Daten-2"),
16 new ReturnStub<string>("Daten-3")))
17 .id("reader");
18
19 closer.expects(once())
20 .after("reader");
|
Das Beispiel zeigt, wie wichtig es ist, am Ende jedes Testabschnitts »verify()« aufzurufen. Andernfalls ist der Ablauf fehlerfrei, weil die Mock-Objekte den fehlenden Aufruf am Ende nicht erkennen. Aus diesem Grund sollte man das Ergebnis zum Schluss noch explizit abgleichen, zum Beispiel mit der Klasse »VerifyingTestCase«.
Tests statt Debugger
Unit-Tests, ob mit oder ohne Mock-Objekte, helfen bei der täglichen Suche nach Softwarefehlern. Wer noch nicht ausreichend damit vertraut ist, beginnt mit wenigen einfachen Tests und baut diese nach Bedarf aus. Wer länger damit arbeitet, stellt vielleicht seine Arbeitsweise komplett um.
Der bisher wichtigste virtuelle Helfer, der Debugger, tritt damit in den Hintergrund. Während der Entwicklung übernehmen Unit-Tests die Aufgabe der Fehlersuche. Bei späteren Wartungsarbeiten und Erweiterungen sichern sie die korrekte Funktion. (ofr)
|
Infos |
|---|
|
[1] Cpp-Unit-Plugin für KDevelop: [http://kdevcppunit.sf.net] [2] Cpp-Unit:[http://cppunit.sf.net] [3] Listings zum Artikel: [https://www.linux-magazin.de/Service/Listings/2005/04/MockCpp/] [4] Bernhard Bablok, “Unit-Tests mit Java”: Linux-Magazin 03/02, S. 104 [5] Mockpp: [http://mockpp.sf.net] [6] JMock: [http://www.jmock.org] [7] Mock Objects for Java: [http://www.mockobjects.org] [8] Easy Mock: [http://www.easymock.org] |
|
Der Autor |
|---|
|
Ewald Arnold ist Autor von Mockpp und bekennender Feierabend-Programmierer, seit er vor rund 20 Jahren seinem ersten Computer in Form eines Sinclar ZX81 begegnet ist. Wenn er nicht gerade programmiert, dann liest er Fantasy und Science-Fiction oder kümmert sich um die beste aller Ehefrauen. |





