Wie testet man SW mit UTE?
Jedes objektorientierte Programm besteht aus Klassen und diese aus Variablen und Methoden. Diese Methoden werden von UTE getestet: In Zukunft wird jede einzelne Methode während der Programmierarbeiten vom Programmierer der Klasse mit einem oder auch mehreren kleinen Testprogrammen versehen. Diese Testprogramme sind ihrerseits Methoden separater Klassen. Auf den Programmierer kommt also hier Mehrarbeit zu (etwa 10-20 %), dafür funktionieren später die Programme: Testen macht alle Programme gut und stabil, nämlich klein, kompakt, wohlüberlegt und genau definiert. Diese Testprogramme, die der Programmierer der Klasse jetzt auch noch schreiben muss, werden bei UTE zum Testen angemeldet mittels der UTE-Methode zum_Test() (Was bei anderen Frameworks assertEquals() heisst, das heisst bei UTE zum_Test()), und zu einem beliebigen späteren Zeitpunkt werden alle bis dato angemeldeten Tests von der Konsole aus aufgerufen durch Aufrufen des Testprogramms. Es wird empfohlen Unit Tests möglichst häufig auszuführen, idealerweise nach jedem Kompilieren (das geht unauffällig im DOS-Fenster vor sich, während man im Editor schon weiterprogrammiert). Die Tests werden in der Reihenfolge ihrer Anmeldung ausgeführt und sind voneinander unabhängig, soweit keine Seiteneffekte auftreten.
UTE automatisiert also eigentlich nur das Sammeln, Aufrufen und Dokumentieren von Tests, die der Programmierer selbst schreibt. Sonst nichts. UTE ist nicht darauf angewiesen, dass Tests oder Funktionen bestimmte Namen haben etc, denn Reflection ist bei UTE nicht im Spiel; demzufolge fehlt UTE ein gewisser Komfort bei der Benutzung und sogar eine grafische Benutzeroberfläche. Auch sind UTEs Fehlermeldungen, falls ein Test fehlschlagen sollte, nicht so elaboriert wie bei anderen Test-Frameworks. UTE sagt in so einem Fall einfach 0.
Wie gehe ich vor? Gibt es 1 Bsp.?
Folgendes Bsp:
Es soll eine Zähler-Klasse entwickelt werden. 1 Objekt z dieser Klasse besitzt 1 int-member, in dem 1 Zahlenwert aufbewahrt wird. Mittels Methodenaufruf z.plus() bzw. z.minus() wird dieser Zahlenwert manipuliert und mittels z.getzahl() wird auf den aktuellen Zahlenwert zugegriffen. Es wird also 1 Zaehler-Objekt erzeugt und in einer separaten Test-Datei werden mehrere Tests (test0, test1, ...) nebst beliebigem Erläuterungs-Text erzeugt und bei UTE angemeldet. Nach jedem dieser Anmelde-Vorgänge ruft Ute automatisch alle bis dato angemeldeten Tests zur Durchführung auf.
Die Tests selbst sind ganz einfache static Funktionen mit return-Wert bool und sagen einfach das, was Sinn und Zweck der Methoden ist:
//Datei test.cpp (Ausschnitt):
class Test{
public:
static bool test0(){
return (z.getzahl() == 13);
};
static bool test1(){
return (z.getzahl() == 14);
};
//weitere Tests hier einfügen
};
Die Anmeldung zum Test für die o.g. Klasse wird ungefähr wie folgt aussehen:
//auch noch Datei test.cpp (Teilansicht):
int main(){
Ute ute;
Time zeit; //Zeitpunkt protokollieren
ute.zum_Test(&(Test::test0),"Das ist ein Test");
z.plus();
ute.zum_Test(&(Test::test1),"Das aber auch");
//usw.
}
Dementsprechend sieht die Zähler-Klasse folgendermassen aus:
//Datei zaehler.h:
#include <iostream>
using namespace std;
class Zaehler{
int zahl;
public:
int plus();
int minus();
int getzahl();
Zaehler();
~Zaehler();
Zaehler(int i);
friend class Test;
};
//zu testende Datei zaehler.cpp:
#include "zaehler.h"
using namespace std;
int Zaehler::plus(){
return ++zahl;
}
int Zaehler::minus(){
return --zahl;
}
int Zaehler::getzahl(){return zahl;};
Zaehler::Zaehler(){};
Zaehler::Zaehler(int i){zahl = i;};
Zaehler::~Zaehler(){};
Ein Testlauf an der Konsole sieht dann folgendermassen aus, wobei 1 bedeutet: Test gelungen (Ein negatives Testergebnis wird durch die Ausgabe einer 0 angezeigt).
C:>test UTE Unit Test-Programm www.AndreasGoedel.de - Version: Prototyp 2004 Dieser Testlauf: Tue Dec 28 09:52:47 2004 Das ist ein Test 1 Das aber auch 1
Welche Dateien benötige ich zum Unit Test mit UTE?
test.cppDiese Dateien werden gemeinsam kompiliert:
C:> bcc32 test.cpp Programmdatei.cpp ute.obj time.obj
Der test wird dann aufgerufen mit
C:>test > testdoku.txt
Was für Programme kann UTE testen?
Alle Programme, die in C++ geschrieben werden. Allerdings wird UTE zu Benutzeroberflächen und Datenbankanschlüssen kaum nützliche Information liefern.
Wie reagiert UTE auf misslingende Tests?
Es kommen 2 Möglichkeiten in Betracht:
Erstens: Der Test bringt nicht das erwartete Ergebnis, also ich erwarte Returnwert 13, es kommt aber tatsächlich 14. In diesem Fall wird von UTE das Testergebnis 0 angezeigt.
Zweitens: Der Test verursacht eine Exception, z.B. bei Division durch 0 etc. Dieser Fall wird von UTE ebenfalls als falsches Testergebnis bewertet. Der Programmierer bekommt also keinen Hinweis auf 1 Exception. Vielleicht ist dieses Feature vom verwendeten Compiler abhängig. Also Vorsicht!
Kann ich mit UTE den Datenbank-Anschluss meines Programms testen?
Weiss nicht. Keine Ahnung. UTE ist 1 Prototyp.
Wie werden die Tests dokumentiert?
UTE erzeugt einen output auf den STDOUT, nämlich neben der genauen Zeitangabe des aktuellen Testlaufs genau die Texte, die man bei der Anmeldung zum Test eingegeben hat und die Testergebnisse (1: Test erfolgreich, 0: nicht erfolgreich). Diesen output kann man in eine Datei umleiten:
C:>test > testdoku.txt
Ist UTE ein XP-Tool?
Ja. Aber man kann UTE auch ohne XP verwenden
UTE ist die Abk. für Unit TEst. Unit Test heisst: Jede Methode wird sofort bei Erstellung getestet, und nicht erst das komplette Programm, wenns fertig ist. Unit Test ist also eine Massnahme zur Qualitätssicherung.
Was muss ich wissen, um die Arbeitsweise von UTE zu verstehen?
UTE beruht auf der Verwendung von Function-Pointer. Man kann UTE aber auch einsetzen, wenn man sie nicht versteht. Unbedingt erforderlich für das Nachvollziehen der hier gezeigten Beispiele ist die Kenntnis, wie man Header-Dateien verwendet, was der #include macht etc.
kann ich mit UTE die Benutzeroberfläche meines Programms testen?
Nein. UTE besitzt übrigens selbst auch keine Benutzeroberfläche, sondern ist ein ganz normales C++Programm und wird an der Konsole gestartet
Wie teste ich Methoden mit return-Datentyp void?
Ändern und Umschreiben, z.B auf return int. Das sollte immer möglich sein.
Wie teste ich private Methoden?
Die testende Klasse in der zu testenden Klasse als friend deklarieren. Das belastet zwar die zu testende Klasse mit 1 zusätzlichen Befehl (friend class Testclass; ), aber das ist schon ok.
Wie gehe ich vor, wenn die Methode, die ich testen will, jetzt noch gar nicht existiert?
Man schreibt den Test vor der Methode, die getestet werden soll. "Write Test 1st" heisst nach den Regeln des XP, dass man zuerst den Test schreibt und danach die Methode, die getestet werden soll. Dieses Vorgehen unterstreicht den Gesichtspunkt, dass wir in zielorientierter Weise Methoden schreiben, die genau definierte Schnittstellen haben, und die sich in bestimmter Weise verhalten, dazu sind sie da. Im folgenden Bsp. geht es um eine kleine Arithmetik-Klasse, welche die Methode plus() enthalten soll.
//Datei Testarithm.h
//Def der Tests für class Arithm
#include "ute.h"
#include "Arithm.h"
using namespace std;
class Test{
public:
static bool plustest0(){
return (Arithm::plus(0,0)==0);
};
static bool plustest1(){
return (Arithm::plus(3,4)==7);
};
};
Die main() kommt in die .cpp:
//Datei Testarithm.cpp
#include "Testarithm.h"
int main(){
Ute ute;
Time zeit;
ute.zum_Test(&(Test::plustest0), "Null bleibt Null");
ute.zum_Test(&(Test::plustest1), "3 und 4 ist 7");
}
Die Klasse selbst wird im Sinne des Write Test 1st erst mal leergelassen, wie folgt:
//Datei Arithm.h
class Arithm{}
D.h.: wir definieren und kompilieren den Test, obwohl die zu testende Methode noch nicht existiert, sondern bis jetzt nur die Klasse definiert wurde, zu welcher die zu testende Methode gehört. Der Compiler bringt folgende Fehlermeldung:
C:>bcc32 testarithm arithm ute.obj Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland testarithm.cpp: Error E2451 Testarithm.h 12: Undefined symbol 'plus' in function Test::plustest0 () Error E2451 Testarithm.h 15: Undefined symbol 'plus' in function Test::plustest1 () *** 2 errors in Compile ***Der Compiler sagt also ganz deutlich, was fehlt: die Methode
plus(). Diese Methode wird jetzt inline definiert:
//Datei Arithm.h
//Demo arithmetische Methode
class Arithm{
public:
static int plus(int x, int y){return x+y;}
friend class Testarithm;
};
woraufhin der Compiler zufrieden ist, und der Test anschliessend aufgerufen werden kann:
C:> testarithm UTE Unit Test-Programm www.AndreasGoedel.de - Version: Prototyp 2004 Dieser Testlauf: Tue Dec 28 09:52:47 2004 Null bleibt Null 1 3 und 4 ist 7 1Die 1 am Ende einer jeden Zeile zeigt wieder den erfolgreichen Verlauf der beiden Tests an. Da der Test sich in separaten Dateien befindet (Testarithm.h und Testarithm.cpp), braucht er nicht an den Kunden ausgeliefert zu werden; er verbleibt beim Programmierer als Teil der Dokumentation.
Ist es wahr, dass ich nach jeder Kompilierung testen soll?
Ja. Vorausgesetzt, die Kompilierung war erfolgreich. Folgendes Bsp.:
Ich beginne mein neues Programm in der Datei Progr.h wie folgt:
//Progr.h
namespace Progr{
int func(){return 13;}
}
Dazu gibt es die passende Progr.cpp:
//Datei Progr.cpp #include "Progr.h" using namespace std;
Der Test geht nun so:
#include <iostream>
#include "ute.h"
#include "Time.h"
#include "Progr.h"
using namespace std;
class Test{
public:
static bool functest0(){
return (Progr::func()==13);
};
};
int main(){
Ute ute;
Time zeit;
ute.zum_Test(&(Test::functest0), "func() return dreizehn ");
}
Der Test verläuft wie folgt:
UTE Unit Test-Programm www.AndreasGoedel.de - Version: Prototyp 2004 Dieser Testlauf: Wed Dec 29 08:01:37 2004 func() return dreizehn 1Leider ist es nicht möglich den
main() zu testen, weil mein Compiler nur 1 einzigen main() in sämtlichen Programmen eines Kompiliervorgangs akzeptiert.
Wie lösche ich einen Test? Wie ändere ich einen Test?
Wenn sich das Programm-Design während der Entwicklung ändert und Methoden entfallen oder geändert werden, dann nimmt man einfach den btr. Test aus der Testdatei heraus, so dass er bei der nächsten Kompilierung des Tests nicht mehr vorhanden ist.
Ist UTE nicht ein wenig naiv, wenn sie zulässt, dass der Programmierer selbst die Tests schreibt?
Kann sein. Und dennoch hat das Testen seinen Wert: Der Programmierer wird veranlasst, die zu testenden Programme noch einmal genau zu durchdenken: Was exakt war es eigentlich, was diese Methode tun soll? Unter welchen Umständen soll sie es tun? Woran erkennt man, dass sie es wirklich tut? etc. Es ist klar, dass die Programme bei einem solchen Vorgehen immer besser, kleiner, handlicher und effektiver werden. Ausserdem: UTE erzeugt einen Output, der einen wertvollen Beitrag zur Dokumentation leistet und dadurch zur Kommunikation im Programmier-Team beiträgt.
XP
Unit Testing mit JUnit von Frank Westphal (in deutscher Sprache)
Thinking in Patterns von Bruce Eckel
JUnit.org