- Modultest
-
In der Softwareentwicklung wird ein Computerprogramm üblicherweise in einzelne Teile mit klar definierten Schnittstellen, sogenannte Module, unterteilt. Der Modultest (auch Komponententest oder oft vom engl. unit test als Unittest bezeichnet) ist der Softwaretest dieser Programmteile, die zu einem späteren Zeitpunkt zusammengefügt (integriert) werden (vgl. Integrationstest). Ziel des Modultests ist es, frühzeitig Programmfehler in den Modulen einer Software (z. B. von einzelnen Klassen) zu finden. Die Funktionalität der Module kann so meist einfacher getestet werden, als wenn die Module bereits integriert sind, da in diesem Fall die Abhängigkeit der Einzelmodule mit in Betracht gezogen werden muss.
Inhaltsverzeichnis
Automatisierte Modultests
Mit der Verbreitung von agilen Softwareentwicklungsmethoden und insbesondere testgetriebener Entwicklung hat sich die Ansicht verbreitet, Modultests ausschließlich automatisiert durchzuführen. Dazu werden üblicherweise mit Hilfe sogenannter Test Frameworks wie beispielsweise JUnit Testprogramme geschrieben, welche die zu testenden Module, in der Regel etwa eine Methode oder eine Funktion, aufrufen und auf korrekte Funktionsweise testen.
Die automatisierten Modultests haben den Vorteil, dass sie rasch und von jedermann ausgeführt werden können. Somit besteht die Möglichkeit, nach jeder Programmänderung durch Ablauf aller Modultests nach Programmfehlern zu suchen. Dies wird üblicherweise empfohlen, da damit etwaige neu entstandene Fehler schnell entdeckt und somit kostengünstig behoben werden können. Dies setzt allerdings voraus, dass die Modultests vor Programmänderungen 100% durchlaufen, also Entwickler nur dann ihren Sourcecode einchecken, wenn alle Modultests erfolgreich durchlaufen.
Eigenschaften von Modultests
- Isoliert
- Modultests testen die Module isoliert, d. h. ohne die Interaktion der Module mit anderen. Ist das nicht der Fall spricht man nicht von Modultests, sondern von Integrationstests. Dazu müssen andere Module beziehungsweise externe Komponenten wie etwa eine Datenbank, Dateien oder Backendsysteme, die von dem Modul verwendet werden, simuliert (engl. to mock) werden. Diese Module nennt man Mock-Objekte. Auch dafür verwendet man üblicherweise Frameworks wie beispielsweise Easymock.
- Test von Fehlverhalten
- Modultests testen ausdrücklich nicht nur das Verhalten des Moduls im Gutfall, beispielsweise bei korrekten Eingabewerten, sondern auch im Fehlerfall, beispielsweise bei unkorrekten Eingabewerten oder von anderen Modulen gelieferten Fehlermeldungen. Auch dafür ist das Mocken anderer Module hilfreich, da sich ein Fehlzustand anderer Module, wie beispielsweise der Verbindungsabbruch bei Netzwerkverbindungen, durch Mock-Objekte leichter simulieren als in der Realität nachstellen lässt.
- Laufende Ausführung
- Modultests sollten im Laufe der Entwicklung regelmäßig durchgeführt werden, um zu verifizieren, dass Änderungen keine unerwünschten Nebeneffekte haben. Modultests sollten daher von jedem Entwickler vor dem Einchecken durchgeführt werden, automatisierte Modultests auch bei Kontinuierlicher Integration auf sogenannten Continuous Integration Servern wie beispielsweise Jenkins.
- Test des Vertrages und nicht der Algorithmen
- Modultests sollen gemäß dem Design-by-contract-Prinzip möglichst nicht die Interna einer Methode testen, sondern nur ihre externen Auswirkungen (Rückgabewerte, Ausgaben, Zustandsänderungen, Zusicherungen). Werden auch interne Details der Methode geprüft (dies wird als White-Box-Testing bezeichnet), droht der Test fragil zu werden, er könnte auch fehlschlagen, obwohl sich die externen Auswirkungen nicht geändert haben. Daher wird in der Regel das sogenannte Black-Box-Testing empfohlen, bei dem man sich auf das Prüfen der externen Auswirkungen beschränkt.
- Testgetriebene Entwicklung
- Bei der Testgetriebenen Entwicklung (TDD von Test driven development) wird jeweils ein Modultest vor dem Erstellen bzw. Ändern des eigentlichen Programmcodes erstellt und gepflegt. Dies hat verschiedene Vorteile: Es wird verifiziert, dass der Test ohne die Änderung wirklich fehlschlägt, es reduziert die Gefahr des übermäßigen White-Box-Testings, es verbessert das Design, da sich der Entwickler so besser über das benötigte Verhalten der Methode klar werden kann, bevor er mit der Entwicklung beginnt. Darum ist es in der Praxis meist weniger aufwendig einen Test vor der Implementierung zu schreiben als umgekehrt.
- Pro Bug ein Test
- Die Erstellung eines Modultests vor Behebung eines Fehlers bietet einige Möglichkeiten. Einerseits stellt der Test sicher, dass der Bug wirklich in dem Programmcode vorhanden ist, wo man ihn auszubessern gedenkt, andererseits dass der Bug auch durch die Programmänderung tatsächlich behoben wurde und auch in Zukunft nicht mehr auftreten kann. Einige Open-Source-Projekte fordern daher, dass bei Fehlern des Programms nicht nur ein Fix, sondern auch ein Test mitgeliefert wird, der ohne den Fix fehlschlägt, aber mit dem Fix korrekt abläuft.
Modultests sind die Vorstufe zu Integrationstests, die wiederum zum Testen mehrerer voneinander abhängiger Komponenten im Zusammenspiel geeignet sind. Im Gegensatz zu Modultests werden Integrationstests meist manuell ausgeführt.
Anwendungsbeispiele
Modultests werden auch im Automotive-Bereich an programmierbaren Steuereinheiten verwendet. Damit wird die Steuereinheit verifiziert (ihre Übereinstimmung mit der Absicht des Entwicklers geprüft). Hier haben die Modultests auch rechtliche Bedeutung innerhalb des Vertragsdokumentes. Falls eine programmierbare Steuerung versagt, kann es zu Personenschäden kommen. Bei einem solchen Test wird die Durchführung einschließlich aufgetretener Fehler protokollartig festgehalten. Dabei wird dann zwischen Unit-Test (kann der Test einer einzelnen C-Funktion sein) und Modultest (Test des gesamten Moduls, dazu gehören Tests der Units und Tests der Funktionsschnittstellen zwischen den Units) unterschieden. Im Automotive-Bereich stehen bei diesen Tests weniger textuelle Daten als vielmehr Variablen physikalischer Werte und damit Grenzwerte im Vordergrund. So muss z. B. geprüft werden, ob das Ergebnis einer Addition von Ganzzahlen sich in jedem Fall innerhalb des Wertebereiches des Ganzzahl-Datentyps befindet. Man erhält dabei über die Code-Abdeckung hinaus große Mengen an Zahlenlisten, die zu testen sind.
Bei Fluxtests werden die Datenflüsse der Schnittstellen integrierter Systeme abgehört und dem Regressionset für die Unittests beigefügt, da ja sowohl Input als auch Output bekannt ist. Die eigentliche Fluxtestphase erfolgt dabei erst beim nächsten Zyklus der Softwareentwicklung, in der den Units dann die bekannten Aufrufparameter übergeben werden beziehungsweise anhand der bekannten gewünschten Ausgabedaten die Units auf ihre Richtigkeit überprüft werden. Fluxtests können nur aus funktionierenden (teil-)integrierten Systemen gewonnen werden. Damit wird im nächsten Zyklus der Integration vorgegriffen. Bereits fertiggestellte Units werden früher getestet, der Release- oder Entwicklungszyklus verkürzt sich. Besonders bei "hardest-first" Integrationsstrategien zahlen sich Fluxtests somit aus.
Beispieltest
Das folgende Beispiel ist eine Testsuite für eine Model-Klasse einer Ruby on Rails-Anwendung mit dem Shoulda-Test-Framework. Zunächst wird ein einfacher Test für Verknüpfungen zu anderen Objekten (hier Übersetzungen) geprüft. Mit einer setup-Methode wird ein Ort in die Datenbank eingetragen, anhand dessen dann zwei Methoden der Klasse mit unterschiedlichen Werten geprüft werden. Anhand der verschachtelten
context
- undshould
-Blöcke generiert Shoulda Methoden mit Namen wieGiven I have a place name should return "Schweiz" for Switzerland in German
oderGiven I have a place name_with_local should return "Switzerland (Schweiz)" or "Switzerland (Suisse)" for Switzerland in English
.I18n.locale
setzt dabei die Spracheinstellung, währendassert_equal
undassert_contains
die Methode aufrufen und den Rückgabewert mit den erwarteten Werten vergleichen.class PlaceTest < ActiveRecord::TestCase should_have_many :translations context "Given I have a place" do setup do @Switzerland = Place.create! @Switzerland.translations.create! :name => "Schweiz", :locale => "de", :is_local => true @Switzerland.translations.create! :name => "Switzerland", :locale => "en" @Switzerland.translations.create! :name => "Suisse", :locale => "fr", :is_local => true end context "name" do should "return \"Schweiz\" for Switzerland in German" do I18n.locale = :de assert_equal "Schweiz", @Switzerland.name end should "return \"Switzerland\" for Switzerland in English" do I18n.locale = :en assert_equal "Switzerland", @Switzerland.name end should "return \"Switzerland\" for Switzerland in Korean" do I18n.locale = :ko assert_equal "Switzerland", @Switzerland.name end end context "name_with_local" do should "return \"Schweiz\" for Switzerland in German" do I18n.locale = :de assert_equal "Schweiz", @Switzerland.name_with_local end should "return \"Switzerland (Schweiz)\" or \"Switzerland (Suisse)\" for Switzerland in English" do I18n.locale = :en assert_contains ["Switzerland (Schweiz)", "Switzerland (Suisse)"], @Switzerland.name_with_local end end end end
Siehe auch
Literatur
- Gerard Meszaros: xUnit Test Patterns: Refactoring Test Code. Addison-Wesley, Amsterdam 31. Mai 2007, ISBN 0-1314-9505-0, S. 833. (Englisch)
- Paul Hamill: Unit Test Frameworks. O'Reilly Media, 19. November 2004, ISBN 0-5960-0689-6, S. 304. (Englisch)
Weblinks
Kategorie:- Testen (Software)
Wikimedia Foundation.