Anzeige
Anzeige
UX & Design

Eine Einführung in Unit Testing mit PHP: Makelloser Code durch effizientes Testen

Komplexe Applikationen, die kontinuierlich weiterentwickelt werden, sind häufig fehleranfällig. Andererseits dürfen in geschäftskritischen Anwendungen natürlich keine Fehler auftauchen. Um Probleme zu entdecken und unbeabsichtige Auswirkungen von Änderungen während der Entwicklung zu finden, muss schnell, umfassend, wiederholt und am besten automatisch getestet werden. Deshalb bedienen sich immer mehr Entwicklerteams der Funktionalität von Unit Testing.

6 Min.
Artikel merken
Anzeige
Anzeige

Die große Frage ist häufig: „Wozu brauchen wir denn Unit Tests, unsere Applikation läuft doch einwandfrei?“ Doch selbst wenn ein Programm fehlerfrei zu laufen scheint, können sich Fehler eingeschlichen haben, die auf den ersten Blick nicht ersichtlich sind. Teilweise funktionieren bestimmte Programmteile monatelang problemlos und erst durch umfangreiches Unit Testing, das alle Eventualitäten abdeckt, werden Fehler im Quellcode aufgedeckt.

Fehler aufzufinden und zu beheben ist das Ziel

Anzeige
Anzeige

Damit haben wir also bereits einen Punkt, der für Unit Testing spricht: Fehler können bereits auf der Ebene der Funktionen aufgespürt und behoben werden. Daneben gibt es aber noch mindestens einen weiteren Punkt, der für maschinelle Softwaretests spricht: Bei einfachen Funktionen ist manuelles Prüfen möglich und je nach verfügbaren Ressourcen auch praktikabel. Erreicht die Software allerdings eine bestimmte Komplexität, so sind manuelle Tests nur noch in beschränktem Rahmen möglich. Mit Unit Testing dagegen können leicht 500, 1.000 oder 10.000 Tests innerhalb von Minuten abgearbeitet werden.

Sebastian Bergmann hat mit PHPUnit [1] ein Framework entwickelt, das Unit Tests für PHP-Applikationen durchführt; gleichartige Module gibt es bereits für Java und andere Programmiersprachen. Mit PHPUnit können Entwickler auf einfache Weise eigene Tests für ihre Klassen und Funktionen schreiben und dann repetitiv durchlaufen lassen.

Anzeige
Anzeige

Innerhalb von PHPUnit existieren eine oder mehrere Testsuiten, die die einzelnen Test Cases beinhalten. Beim Aufruf von PHPUnit kann zunächst ausgewählt werden, welche Tests genau durchlaufen werden sollen.

Anzeige
Anzeige

Im Regelfall sind das alle Tests hintereinander – manchmal ist es allerdings für die effiziente Fehlersuche besser, sich auf den Durchlauf eines einzelnen Test Cases zu beschränken, bis dieser nicht mehr fehlschlägt.

Ein einfaches Beispiel zum Anfangen

Zu testende Funktion
static function addition($a, $b) {
	return $a + $b;
}

Listing 1

Diese sehr übersichtliche Funktion wird über zwei Parameter gesteuert. Der Rückgabewert ist variabel, da er vom Wert der übergebenen Parameter abhängt. Ein simpler Test für diese Funktion könnte folgendermaßen aussehen:

Anzeige
Anzeige
Einfache Testmethode
public function testAddition() {
	$this->assertEquals(
		18,
		addition(12, 6)
	);
}

Listing 2

Der Test besteht in diesem Fall aus einer einzelnen Überprüfung. Solange die Funktion addition() mit diesen Werten das richtige Resultat zurück gibt, ist der Test bestanden. Sollte ein Entwickler die Funktion addition() verändern, so schlägt der Test beim nächsten Durchlauf Alarm, falls das Resultat von dem erwarteten Wert abweicht.

Auf der grünen Wiese

Im Normalfall werden allerdings nicht einzelne Funktionen, sondern ganze Klassen mit jeweils mehreren Funktionen getestet. Je nach Aufbau der zu testenden Klasse muss diese sauber initialisiert oder gegebenenfalls instantiiert werden, damit die Funktionen sinnvoll getestet werden können.

Zudem sollte jeder Test unter gleichbleibenden, reproduzierbaren Rahmenbedingungen ablaufen. In PHPUnit gibt es zu diesem Zweck die so genannten Fixtures, die für jeden Test eine feste Testumgebung aufbauen. Die Fixtures werden durch die beiden Funktionen setUp() und tearDown() kontrolliert, die vor beziehungsweise nach jedem einzelnen Test durchlaufen werden. In diesen Funktionen wird das eigentliche Fixture je nach Bedarf initialisiert und wieder gelöscht. Zusätzlich wird jeder einzelne Test innerhalb einer neuen Instanz der Test-Runner-Klasse von PHPUnit ausgeführt.

Anzeige
Anzeige
Zu testende Klasse (stark vereinfacht)
class Article() {
	private $title;
	public function __construct($uid) {
		// Objekt wird abhängig von der übergebenen UID instantiiert
	}
	public function getTitle() {
		return $this->title;
	}
}

Listing 3

Die oben skizzierte Klasse repräsentiert einen Artikel, der beispielsweise in einem Onlineshop vorkommen kann. Auf die private Variable $title kann ausschließlich über die öffentliche Funktion getTitle() zugegriffen werden. Die folgende Testsuite ist eine eigene Klasse, die sich von der Klasse TestCase aus PHPUnit ableitet. Dadurch stehen automatisch sämtliche Hilfsfunktionen von PHPUnit zur Verfügung.

Testsuite für die Klasse „Article“
public function setUp() {
	$this->uid = createDummyArticleInDatabase(
		array('title' => 'Test Title')
	);
	$this->fixture = new Article($this->uid);
}
public function tearDown() {
	unset($this->fixture);
	$this->removeDummyArticleFromDatabase($this->uid);
}
public function testGetTitle() {
	$this->assertEquals(
		'Test Title',
		$this->fixture->getTitle()
	);
}

Listing 4

In diesem Beispiel wird in der Funktion setUp() bei jedem Testdurchlauf ein Dummy-Datensatz in die Datenbank geschrieben. Damit sich in der Datenbank keine Datenleichen ansammeln, wird der erzeugte Dummy in der Funktion tearDown() wieder gelöscht.

In der Funktion testGetTitle() wird der eigentliche Test durchgeführt. In der Variable $this->fixture ist jetzt ein sauber instantiiertes Article-Objekt abgelegt, auf das ohne Umwege zugegriffen werden kann. Der große Vorteil der Fixtures wird allerdings erst ersichtlich, wenn die Testsuite eine Vielzahl von Tests beinhaltet. Es ist dann jedes Mal sichergestellt, dass einzelne Testläufe in der Variable $this->fixture auf ein frisch instantiiertes Objekt zugreifen und somit in einer klar definierten Umgebung testen.

Anzeige
Anzeige

Sebastian Bergmann schreibt in der PHPUnit-Dokumentation, dass auf die Funktion tearDown() auch verzichtet werden kann, solange keine externen Ressourcen involviert sind. In den meisten Fällen wird es aber so sein, dass beispielsweise Datenbanken in den Tests beteiligt sind.

Wir testen doch alles, oder?

Die Testfunktionen der vorhergehenden Beispiele überprüften bislang nur, ob die Funktionen das erwartete Ergebnis für einen Standardfall liefern. Bei komplexeren Funktionen gibt es dagegen noch weitere zu überprüfende Aspekte:

Aspekte Grund
Kontrolle der Funktionalität Sicherstellen, dass alle Anforderungen erfüllt werden.
Exceptions abfangen Funktionen liefern, z. B. bei illegalen Parametern, Exceptions zurück. Fehlen diese Exceptions, handelt es sich um einen Fehler.
Edge-Cases überprüfen Was passiert, wenn zum Beispiel eine viel größere Zahl übergeben wird als im
„Normalfall“? Verhält sich die Funktion genau so wie erwartet?
Regression Gefixte Bugs sollen durch Unit Tests abgedeckt werden. Sollte der Bug
erneut auftreten, wird durch den fehlgeschlagenen Test automatisch
darauf aufmerksam gemacht.

Wenn ein Team beginnt, in seinen Projekten Unit Tests einzusetzen, wird anfangs sicherlich die Überprüfung der Anforderungen im Mittelpunkt stehen. Ist genug Zeit vorhanden, können auch noch die Exceptions/Edge Cases geprüft werden. Diese Tests bringen aber häufig sehr viel Aufwand mit sich.

Anzeige
Anzeige

Neben all den Vorteilen, die sauberer Code und die Tests mit sich bringen, gibt es auch einige negative Punkte: Neben dem enormen Aufwand zu Beginn ist mit jeder Änderung am Programmcode meist auch Arbeit an den Tests nötig. Zudem kann die ganze Testerei leicht süchtig machen, was zu noch mehr Tests führt. Die größte Gefahr lauert jedoch in den Tests selbst: Man sollte immer daran denken, dass die Tests von Menschen entwickelt wurden und deshalb ebenfalls Fehler enthalten können.

Und jetzt alles umgekehrt, bitte!

Bis jetzt wurde in allen Beispielen davon ausgegangen, dass zuerst der eigentliche Programmcode entwickelt wird, um daraufhin mit Unit Testing die einwandfreie Funktionalität abzusichern. Test Driven Development verfolgt einen umgekehrten Ansatz: Zuerst werden anhand von Spezifikationen die Tests für jedes zu entwickelnde Modul geschrieben. Anfangs werden erwartungsgemäß alle diese Tests fehlschlagen, da der Code noch gar nicht existiert.

Nach und nach wird dann der eigentliche Programmcode entwickelt und die Tests werden fehlerfrei durchlaufen. Ziel ist es, am Schluss alle Tests zu bestehen – denn dann funktioniert der Programmcode genau so, wie er laut den Spezifikationen soll. Ein großer Vorteil dieser Vorgehensweise ist die detailliertere Auseinandersetzung mit dem strukturellen Aufbau einer Applikation vor dem effektiven Entwicklungsprozess.

Anzeige
Anzeige

Wer häufig testet

Martin Fowler beschreibt auf seiner Website [2] die Vorgehensweise der Continuous Integration. Diese basiert auf dem Konzept, dass möglichst der gesamte Sourcecode durch Unit Tests abgedeckt sein sollte. Entwickler werden dazu angehalten, vor jeder Änderung alle Tests durchlaufen zu lassen. Zudem wird nach dem Einchecken einer Änderungen im Repository erneut ein kompletter Durchlauf aller Unit Tests gestartet. Durch das häufige Einchecken selbst kleinster Änderungen ist die Fehlersuche und Fehlerbehebung mit viel weniger Aufwand verbunden.

Fazit

Zusammenfassend kann man sagen, dass die Einführung von Unit Testing in einem Softwareprojekt einen nicht zu vernachlässigenden Aufwand bedeutet. Die kurzfristige Investition schlägt sich aber merklich in besseren Prozessen und saubererem Quellcode nieder und zahlt sich mittel- bis langfristig auf jeden Fall aus. Aus der Sicht eines Entwicklers ist der Einsatz von Unit Testing jedem Entwicklungsteam wärmstens zu empfehlen.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Ein Kommentar
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Webelutions

Sehr guter Artikel!
Ich habe auch einen Artikel über Unit Testing mit PHPUnit geschrieben, dort sind noch weiter Informationen zu diesem Thema vorhanden: http://webelutions.de/blog/unit-testing-mit-php/

Antworten

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige