Eine Komponente wird getestet, indem Eingaben an ihre Schnittstelle gesendet werden. Dann wird gewartet, dass die
Komponente die Eingaben verarbeitet, und anschließend werden die Ergebnisse geprüft. Im Verlauf der Verarbeitung
verwendet eine Komponente in der Regel andere Komponenten, indem sie Eingaben an diese sendet und die zurückgegebenen
Ergebnisse verwendet:
Abb1: Implementierte Komponente testen
Diese anderen Komponenten können aus den folgenden Gründen Probleme beim Testen verursachen:
-
Sie sind möglicherweise noch nicht implementiert.
-
Sie weisen unter Umständen Mängel auf, die die Durchführung Ihrer Tests behindern oder dafür verantwortlich sind,
dass Sie viel Zeit damit verbringen herauszufinden, dass der Testfehler nicht von Ihrer Testkomponente verursacht
wurde.
-
Sie können die Ausführung von Tests erschweren. Wenn eine Komponente eine kommerzielle Datenbank ist, hat Ihr
Unternehmen möglicherweise nicht genügend Floating-Lizenzen für jedermann. Oder eine Komponente könnte Hardware
sein, die nur zu bestimmten Zeiten in einem separaten Labor verfügbar ist.
-
Sie können die Tests so verlangsamen, dass die Tests nicht häufig genug ausgeführt werden. Das Initialisieren der
Datenbank kann beispielsweise fünf Minuten pro Test dauern.
-
Es kann schwierig sein, die Komponenten dazu zu bringen, bestimmte Ergebnisse zu erzeugen. Beispiel: Sie möchten,
dass jede Ihrer Methoden, die auf die Platte schreibt, Fehler des Typs "Platte voll" behandelt. Wie stellen Sie
sicher, dass die Platte gerade zu dem Zeitpunkt voll ist, wenn eine dieser Methoden aufgerufen wird?
Solche Probleme können Sie mit Stub-Komponenten (oder Attrappenobjekten) vermeiden. Stub-Komponenten
verhalten sich wie die echten Komponenten, zumindest was die Werte anbelangt, die Ihre Komponente an diese Komponenten
sendet, während sie auf ihre Tests reagiert. Sie können sogar noch mehr als das: Sie können vielseitig einsetzbare
Emulatoren sein, die versuchen, das meiste oder das gesamte Verhalten der Komponente zu simulieren. Häufig
empfiehlt es sich beispielsweise, Softwareemulatoren für Hardware zu erstellen. Sie verhalten sich wie die Hardware,
nur langsamer. Sie sind hilfreich, weil sie ein besseres Debugging ermöglichen, weil mehr Exemplare von ihnen verfügbar
sind und weil sie verwendet werden können, bevor die Hardware zusammengestellt ist.
Abb2: Implementierte Komponente durch Darstellung einer abhängigen Komponente als Stub testen
Stubs haben zwei Nachteile.
-
Die Kosten für ihre Erstellung können hoch sein. (Dies gilt insbesondere für Emulatoren.) Da sie selbst Software
sind, müssen sie auch gepflegt werden.
-
Sie verdecken Fehler. Angenommen, Ihre Komponente verwendet trigonometrische Funktionen, aber es ist noch keine
Bibliothek verfügbar. Ihre drei Testfälle fragen den Sinus von drei Winkeln ab: 10 Grad, 45 Grad und 90 Grad. Sie
verwenden Ihren Taschenrechner, um die richtigen Werte zu berechnen und erstellen dann einen Sinus-Stub, der die
Werte 0,173648178, 0,707106781 und 1,0 zurückgibt. Alles ist in Ordnung, bis Sie Ihre Komponente mit der echten
trigonometrischen Bibliothek integrieren, deren Sinusfunktion Argumente in Radianten erwartet und deshalb
-0,544021111, 0,850903525 bzw. 0,893996664 zurückgibt. Dies ist ein Mangel in Ihrem Code, der erst später,
möglicherweise später als Ihnen lieb ist, erkannt wird.
Sofern die Stubs nicht erstellt wurden, weil die echte Komponente noch nicht verfügbar ist, sollten Sie damit rechnen,
dass die Stubs auch nach dem Deployment beibehalten werden. Die Tests, die sie unterstützen, sind wahrscheinlich für
die Produktpflege sehr wichtig. Bei der Codierung von Stubs muss deshalb ein höherer Standard angesetzt werden als bei
"Wegwerfcode". Obwohl sie nicht dem Standard von Produktcode entsprechen müssen (beispielsweise benötigen die meisten
keine eigene Testsuite), müssen die Entwickler sie später als Komponenten der Produktänderung verwalten. Wenn diese
Verwaltung zu streng ist, werden die Stubs verworfen, und die Investition gehen damit verloren.
Wenn Stubs beibehalten werden sollen, haben sie Einfluss auf das Komponentendesign. Angenommen, Ihre Komponente
verwendet eine Datenbank, um Schlüssel/Wert-Paare persistent zu speichern. Schauen Sie sich die folgenden beiden
Designszenarios an:
Szenario 1: Die Datenbank wird für Tests und für den normalen Betrieb verwendet. Die Existenz der Datenbank muss
vor der Komponente nicht verdeckt werden. Sie können sie mit dem Namen der Datenbank initialisieren:
public Component(
String DatenbankURL) { try { databaseConnection = DriverManager.getConnection(DatenbankURL); ... } catch (SQLException e) {...} }
Obwohl Sie sicherlich nicht möchten, dass jede Position, die einen Wert liest oder schreibt, eine SQL-Anweisung erstellt,
so haben Sie doch einige Methoden, die SQL enthalten. Der Komponentencode, der einen Wert benötigt, könnte beispielsweise
die folgende Komponentenmethode aufrufen:
public String get(String key) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery(
"SELECT value FROM Table1 WHERE key=" + key); ... } catch (SQLException e) {...} }
Szenario 2: Für die Tests wird die Datenbank durch einen Stub ersetzt. Der Komponentencode sollte bei der Verwendung
eines Stub genauso aussehen wie bei der Verwendung einer echten Datenbank. Deshalb muss er so codiert werden, dass Methoden
einer abstrakten Schnittstelle verwendet werden:
interface SchluesselWertPaare { String
get(String Schluessel); void
put(String Schluessel, String Wert); }
Tests implementieren SchluesselWertPaare mit etwas Einfachem wie einer Hash-Tabelle:
class FakeDatabase implements SchluesselWertPaare { Hashtable table = new Hashtable(); public String
get(String Schluessel) { return (String) table.get(Schluessel); } public void
put(String Schluessel, String Wert) { table.put(key, Wert); } }
Wenn die Komponente nicht in einem Test verwendet wird, verwendet sie ein
Adapterobjekt, das Aufrufe an die Schnittstelle SchluesselWertPaare in SQL-Anforderungen
konvertiert:
class DatabaseAdapter implements SchluesselWertPaare { private Connection databaseConnection; public DatabaseAdapter(String DatenbankURL) { try { databaseConnection = DriverManager.getConnection(DatenbankURL); ... } catch (SQLException e) {...} } public String
get(String Schluessel) { try { Statement stmt = databaseConnection.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT value FROM Table1 WHERE Schluessel=" + Schluessel); ... } catch (SQLException e) {...} } public void
put(String Schluessel, String Wert) { ... } }
Ihre Komponente kann einen einfachen Konstruktor für Tests und andere Clients haben. Dieser Konstruktor erwartet ein
Objekt, das SchluesselWertPaare implementiert. Er könnte diese Schnittstelle aber auch nur Tests
bereitstellen und voraussetzen, das gewöhnliche Clients der Komponente den Namen einer Datenbank übergeben:
class Component {
public Component(String DatenbankURL) { this.valueStash = new DatabaseAdapter(DatenbankURL); } // Für Tests.
Geschützt Component(SchluesselWertPaare valueStash) { this.valueStash = valueStash; } }
Vom Standpunkt des Clientprogrammierers aus wird in beiden Szenarios dieselbe API verwendet, aber eine ist schneller
testfähig. (Beachten Sie, dass einige Tests die echte Datenbank verwenden können und andere die Stub-Datenbank.)
Weitere Informationen zu Stubs finden Sie in den folgenden Veröffentlichungen:
|