Tests ablaufen lassen
Die so genannten Testrunner führen die Tests aus und bereiten zudem die Ergebnisse übersichtlich auf. Standardmäßig verwendet »Test::Unit« den Testrunner der Kommandozeile (siehe Abbildung 2).
Für eine grafische Ausgabe ist einfach der Testrunner für das gewünschte Userinterface zu wählen, zum Beispiel der TK-Testrunner für eine Darstellung mit TK beziehungs-weise der GTK-2-Testrunner für die Anzeige mit dem GTK-2-Toolkit (siehe Abbildung 3). Einen solchen grafischen Testrunner kann natürlich nur verwenden, wer die Ruby-Module für das entsprechende Toolkit installiert hat.
Der folgende Code bindet erst den Testrunner ein und führt danach dessen »run«-Methode aus:
require 'test/unit/ui/gtk2/testrunner'
Test::Unit::UI::GTK2::TestRunner.run(Test)
Der übergebene Parameter (»Test«) darf ein einzelner Test oder eine ganze Testsuite sein. Es ist wichtig, den Testrunner nur auf der obersten Ebene anzugeben, da die einzelnen Tests sonst mehrfach durchlaufen.
Testsuites fassen mehrere Unit-Tests sowie bereits bestehende Testsuites zu einem Paket zusammen. Damit bleibt auch in einem größeren Projekt der Überblick über alle verwendeten Tests erhalten. Die einfachste Methode zur Erstellung einer Testsuite ist, in einer eigenen Datei alle benötigten Tests und das »Test::Unit«-Framework einzubinden.
require 'test/unit'
require 'test_case_1'
require 'test_suite_1'
Das Diagramm in Abbildung 1 veranschaulicht, wie eine solche Sammlung von Tests zusammenhängt.
01 # test_factorial.rb
02 require 'test/unit'
03 require 'factorial'
04 class TestFactorial < Test::Unit::TestCase
05 def test_calc
06 f = Factorial.new
07 assert_equal(120, f.calc(5) )
08 assert_equal(1, f.calc(1) )
09 assert_equal(1, f.calc(0) )
10 assert_nil(f.calc(1.3) )
11 assert_nil(f.calc(-1) )
12 assert_nil(f.calc("text"))
13 end
14 end
15
16 # factorial.rb
17 class Factorial
18 def calc number
19 return nil if !number.is_a?(Integer) || number < 0
20 @result = 1
21 number.downto(1) { |i|
22 @result *= i
23 }
24 return @result
25 end
26 end
|
01 require 'net/http'
02
03 class Article
04 def initialize
05 @host = 'www.linux-magazin.de'
06 @baseurl = '/Artikel/ausgabe/'
07 end
08
09 def parse_articles(text)
10 ...
11 end
12
13 def load_article_page(year, month)
14 ...
15 end
16
17 def read_articles(year, month)
18 ...
19 end
20
21 private
22 def createConnection
23 ...
24 end
25
26 def get_address(year, month, suffix=nil)
27 ...
28 end
29
30 def extract_article_text(text)
31 ...
32 end
33
34 def extract_article_link(text, base)
35 ...
36 end
37 end
38
39 ...
|
Zugriff auf private Methoden durch Vererbung
Das Testen der öffentlichen Methoden einer Klasse reicht in den meisten Fällen aus, denn es stellt dabei die korrekte Funktion der privaten Methoden sicher. Es gibt aber dennoch einen Weg, eine als »private« oder »protected« markierte Methode direkt zu testen: Man leitet von der zu testenden Klasse eine Subklasse ab, die geschützte Methoden als öffentlich definiert. Im folgenden Beispiel besitzt die »Klasse« eine geschützte (»protected«) Methode »geheim«. Die Unterklasse »Subklasse« besitzt eine gleichnamige öffentliche Methode, die mit »super« die geschützte Methode der Elternklasse aufruft:
class Klasse
protected
def geheim; end
end
class Subklasse < Klasse
def geheim; super; end
end
Eine noch einfachere Möglichkeit bietet die Methode »send«, die auch geschützte Methoden aufrufen kann. Das Beispiel im obigen Code ist auch mit » Instanz .send :geheim« umsetzbar. Diese Fähigkeit der »send«-Methode ist zwar nicht dokumentiert, wird aber auch in Beispielen der Ruby-Dokumentation für ähnliche Zwecke verwendet. Optional lässt sich die Testsuite danach explizit als Klasse anlegen, bei der die Klassenmethode »suite« alle gewünschten Tests einbindet. Diese Klasse bekommt der Testrunner übergeben:
class Suite
def self.suite
suite = Test::Unit::TestSuite.new
suite << TestCase1.suite
suite << TestSuite1.suite
return suite
end
end
require 'test/unit/ui/gtk2/testrunner'
Test::Unit::UI::GTK2::TestRunner.run(Suite)
Listing 3 zeigt Beispiele zum Testen privater Methoden: So testet »test_get_address« die private Methode »get_address« direkt. Die erste Assertion der Testmethode »test_read_articles« überprüft dagegen, ob im weiteren Verlauf das Programm die richtige Adresse verwendet. Damit prüft sie indirekt gleichzeitig die korrekte Funktion der »get_address«-Methode.
|
Aus welchen Gründen sollte ein Programmierer zusätzlich Zeit aufwenden, um für Tests ein zweites Programm ohne eigene Funktionalität parallel zum eigentlichen Programm zu schreiben?
Absicherung bei Änderungen am Programm
Programmierer verbringen oft einen überraschend kleinen Teil ihrer Zeit mit dem Schreiben des Programms, einen deutlich größeren mit der Fehlerbehebung. Dabei sind die meisten Fehler schnell ausgebessert - den Fehler finden kostet die meiste Zeit. Zu allem Überfluss können durch jede Änderung neue Fehler entstehen, gegen die sich Entwickler mit guten Unit-Tests absichern können.
Bei regelmäßiger Ausführung der Tests ist der Fehler sehr schnell gefunden, weil nur der Code, der seit dem letzten Testdurchlauf hinzugekommen ist, überhaupt zur Debatte steht. Das ist besonders für das Refactoring wichtig, also für Änderungen am Programm, um dessen Struktur (nicht dessen Funktionalität) zu verbessern.
Unit-Tests sind zwar kein Allheilmittel gegen Programmfehler, können aber dabei helfen, deutlich weniger Arbeitszeit mit der Fehlerbehebung zu verbringen.
Ausnahmesituationen und Grenzfälle
Unit-Tests eignen sich dazu, alle Arten von Ausnahmesituationen und Grenzfällen zu kontrollieren. In diesen Bereichen verstecken sich sehr häufig Fehler, die in einem nicht getesteten Programm selten auftreten und dadurch schwierig zu eliminieren sind.
Dokumentation des Programms
Unit-Tests dokumentieren ein Programm, indem sie mit dem Interface der getesteten Klasse kommunizieren und die erwarteten Rückgabewerte festlegen.
Test Driven Development
Beim Test Driven Development (TDD) werden die Tests vor der Implementation des Programms erstellt. Das Programm gilt als fertig gestellt, sobald alle Tests ohne Fehler absolviert werden. Beim Schreiben eines Tests muss sich der Programmierer Gedanken über die Funktionsweise einer Klasse machen. Der Fokus liegt dabei automatisch auf dem Interface und nicht auf der Implementation - eine der wichtigsten Richtlinien für objektorientiertes Programmieren. Das führt meist zu übersichtlicherem Code. Entwickler, die zuerst die Tests schreiben, versetzen sich in den Klassenanwender und werden das Interface (hoffentlich) verständlicher aufbauen.
Dokumentation der Fehler
Tritt ein Fehler auf, der nicht durch einen Test abgedeckt ist, schreibt der Entwickler einen Test, der diesen Fehler dokumentiert. Sobald der Test fehlerfrei durchgeführt wird, ist der Bug behoben. So ist auch sichergestellt, dass derselbe Fehler kein zweites Mal unbemerkt durch die Tests rutscht.
|
| Whitepaper |
|
Open Source Datenintegration in der Praxis: Fallstudien und Anwendungsbeispiele (Folge 2)
Der zweite Teil des Open Source Datenintegration in der Praxis: Fallstudien und Anwendungsbeispiele White Papers beleuchtet anhand weiterer ausgewählter Case Studies die Implementierung von Open Source Datenintegration in der Praxis und benennt die daraus resultierenden Vorteile.
Download PDF (Registrierung erforderlich)
|
|
Usage Landscape Enterprise Open Source Data Integration
Die Nachfrage nach Datenintegrationslösungen für Unternehmen ist zunehmend gestiegen und vor allem das Interesse an Open Source Technologien wird immer größer. Doch wie und von wem werden Open Source Datenintegrationslösungen genutzt und welches Nutzungsverhalten lässt sich daraus ableiten? Das vorliegende White Paper präsentiert die Erfahrungswerte von über 1000 Open Source Nutzern und liefert fundierte Antworten auf diese Fragen.
Download PDF (Registrierung erforderlich)
|
Dieser Online-Artikel kann Links enthalten, die auf nicht mehr vorhandene Seiten verweisen. Wir ändern solche "broken links"
nur in wenigen Ausnahmefällen. Der Online-Artikel soll möglichst unverändert der gedrucken Fassung entsprechen.
|