Testen von Softwaresystemen - Exam
Aufgabe 1)
Du bist beauftragt, eine Software-Teststrategie für ein neues Online-Banking-System zu entwickeln. Dein Ziel ist es, sicherzustellen, dass das System fehlerfrei und sicher funktioniert. Dabei sollst Du verschiedene Testarten wie Unit-Tests, Integrationstests und Systemtests berücksichtigen.
a)
Erkläre, wie Unit-Tests für das Online-Banking-System implementiert werden könnten. In Deiner Erklärung sollst Du folgende Punkte berücksichtigen:
- Ziel der Unit-Tests.
- Welche Komponenten des Systems getestet werden könnten.
- Ein Python-Beispiel für einen Unit-Test, der prüft, ob die Funktion zur Erstellung eines neuen Bankkontos korrekt funktioniert.
Lösung:
- Ziel der Unit-Tests: Das Hauptziel der Unit-Tests ist es, sicherzustellen, dass jede einzelne Komponente des Online-Banking-Systems in Isolation einwandfrei funktioniert. Durch das Testen einzelner Funktionen und Module kann frühzeitig festgestellt werden, ob es Fehler im Code gibt. Dies hilft auch dabei, Herauszufinden, ob Änderungen an einer Funktion die Funktionalität anderer Teile des Systems beeinträchtigen.
- Welche Komponenten des Systems getestet werden könnten: Da Unit-Tests einzelne Funktionen und Methoden testen, könnten folgende Komponenten des Online-Banking-Systems getestet werden:
- Authentifizierungs- und Autorisierungsfunktionen
- Kontoverwaltungsfunktionen, z.B. Kontoerstellung, Kontoschließung
- Zahlungs- und Überweisungsfunktionen
- Funktionen zur Abfrage des Kontostands
- Verwaltung historischer Transaktionen
- Benachrichtigungsfunktionen
- Währungsumrechnungsfunktionen (falls vorhanden)
- Ein Python-Beispiel für einen Unit-Test, der prüft, ob die Funktion zur Erstellung eines neuen Bankkontos korrekt funktioniert:
import unittestclass BankAccount: def __init__(self, account_id, owner, initial_balance=0.0): self.account_id = account_id self.owner = owner self.balance = initial_balance def get_account_details(self): return { 'account_id': self.account_id, 'owner': self.owner, 'balance': self.balance }class TestBankAccountCreation(unittest.TestCase): def test_create_new_account(self): # Arrange account_id = '1234567890' owner = 'John Doe' initial_balance = 1000.0 # Act account = BankAccount(account_id, owner, initial_balance) account_details = account.get_account_details() # Assert self.assertEqual(account_details['account_id'], account_id) self.assertEqual(account_details['owner'], owner) self.assertEqual(account_details['balance'], initial_balance)if __name__ == '__main__': unittest.main()
In diesem Beispiel enthält die Klasse BankAccount
eine Methode zur Kontoerstellung sowie eine Methode zur Abfrage der Kontodetails. Der Unit-Test in der Klasse TestBankAccountCreation
überprüft, ob bei der Erstellung eines neuen Bankkontos die übergebenen Parameter korrekt gesetzt wurden und ob die Funktion zur Abfrage der Kontodetails die erwarteten Werte zurückgibt.
b)
Beschreibe den Ansatz für Integrationstests und Systemtests für das Online-Banking-System:
- Worin besteht der Unterschied zwischen Integrationstests und Systemtests?
- Erstelle ein Szenario für einen Integrationstest, bei dem die Interaktion zwischen dem Benutzer-Authentifizierungsmodul und dem Konto-Transaktionsmodul getestet wird.
- Beschreibe ein Systemtest-Szenario, das die Gesamtheit der Hauptfunktionen des Online-Banking-Systems überprüft.
Lösung:
- Unterschied zwischen Integrationstests und Systemtests:Integrationstests und Systemtests haben unterschiedliche Ziele und Bereiche:
- Integrationstests: Diese Tests überprüfen die Interaktion und das Zusammenspiel zwischen mehreren Komponenten oder Modulen im System. Das Ziel ist es, sicherzustellen, dass die einzelnen Module korrekt zusammenarbeiten, Daten austauschen und die gewünschte Funktionalität erreichen.
- Systemtests: Diese Tests testen das gesamte System als eine Einheit. Dabei wird überprüft, ob das System als Ganzes den Anforderungen entspricht und wie es sich unter realen Einsatzbedingungen verhält. Systemtests beinhalten oft End-to-End-Szenarien, die alle wichtigen Funktionalitäten und Abläufe im System abdecken.
- Szenario für einen Integrationstest:Ein Szenario für einen Integrationstest könnte folgendermaßen aussehen:Titel: Interaktion zwischen Benutzer-Authentifizierungsmodul und Konto-TransaktionsmodulZiel: Sicherstellen, dass ein authentifizierter Benutzer eine Transaktion erfolgreich durchführen kannVoraussetzungen:
- Benutzerkonto ist im System registriert
- Benutzer kennt seine Zugangsdaten (Benutzername und Passwort)
Testschritte:- Der Benutzer navigiert zur Login-Seite des Online-Banking-Systems.
- Der Benutzer gibt seinen Benutzernamen und sein Passwort ein und klickt auf „Einloggen“.
- Das System überprüft die Zugangsdaten und authentifiziert den Benutzer.
- Nach erfolgreicher Authentifizierung wird der Benutzer zur Kontenübersicht weitergeleitet.
- Der Benutzer wählt sein Konto aus und initiiert eine neue Transaktion (z.B. Überweisung).
- Der Benutzer gibt die Transaktionsdetails ein (Empfänger, Betrag, Verwendungszweck) und bestätigt die Transaktion.
- Das System verarbeitet die Transaktion und zeigt eine Bestätigung an.
- Der Benutzer sieht die aktualisierte Kontostandsübersicht mit der vorgenommenen Transaktion.
Erwartetes Ergebnis:- Der Benutzer kann sich erfolgreich einloggen.
- Der Benutzer kann eine neue Transaktion initiieren und abschließen.
- Die Transaktion wird korrekt im Konto des Benutzers verbucht und angezeigt.
- Systemtest-Szenario:Ein Systemtest-Szenario könnte folgendermaßen aussehen:Titel: Überprüfung der Hauptfunktionen des Online-Banking-SystemsZiel: Sicherstellen, dass das gesamte System korrekt funktioniert und alle Hauptfunktionen reibungslos ablaufenVoraussetzungen:
- System ist voll funktionsfähig und betriebsbereit
- Testbenutzerkonten sind eingerichtet
Testschritte:- Der Benutzer navigiert zur Homepage des Online-Banking-Systems.
- Der Benutzer loggt sich in sein Konto ein.
- Der Benutzer überprüft seinen Kontostand und die letzten Transaktionen.
- Der Benutzer überweist einen Betrag von seinem Konto auf ein anderes Konto.
- Der Benutzer erstellt ein neues Sparkonto.
- Der Benutzer richtet Benachrichtigungen für bestimmte Transaktionen ein.
- Der Benutzer loggt sich aus dem System aus.
Erwartetes Ergebnis:- Der Benutzer kann sich erfolgreich einloggen und wird korrekt authentifiziert.
- Der Benutzer sieht eine korrekte Übersicht über seinen Kontostand und die letzten Transaktionen.
- Die Überweisung wird erfolgreich durchgeführt und auf beiden betroffenen Konten korrekt angezeigt.
- Das neue Sparkonto wird korrekt erstellt und angezeigt.
- Die Benachrichtigungen werden korrekt eingerichtet und funktionieren wie erwartet.
- Der Benutzer kann sich erfolgreich ausloggen.
Aufgabe 2)
Sie sind der Qualitätssicherungsingenieur für ein Softwaresystem, das zur Berechnung der monatlichen Raten für ein Darlehen verwendet wird. Das System nimmt drei Eingaben an: den Darlehensbetrag (in Euro), die Laufzeit des Darlehens (in Monaten) und den jährlichen Zinssatz (in Prozent). Sie müssen sicherstellen, dass das System die Anforderungen korrekt erfüllt, indem Sie Äquivalenzklassen- und Grenzwertanalysen anwenden.
a)
1. Betrachten Sie den Darlehensbetrag, der Werte im Bereich 1.000 Euro bis 1.000.000 Euro annehmen kann, die Laufzeit des Darlehens, die zwischen 12 und 360 Monaten liegt, und den Zinssatz, der zwischen 1% und 25% variiert. Teilen Sie den Eingabebereich für jede dieser drei Eingaben in Äquivalenzklassen auf und identifizieren Sie mindestens eine Äquivalenzklasse pro Eingabezustand.
Lösung:
- Darlehensbetrag: Der Darlehensbetrag kann Werte zwischen 1.000 Euro und 1.000.000 Euro annehmen. Die Äquivalenzklassen für den Darlehensbetrag könnten wie folgt definiert werden:
- Äquivalenzklasse 1: 1.000 Euro bis 100.000 Euro
- Äquivalenzklasse 2: 100.001 Euro bis 500.000 Euro
- Äquivalenzklasse 3: 500.001 Euro bis 1.000.000 Euro
- Laufzeit des Darlehens: Die Laufzeit des Darlehens kann zwischen 12 und 360 Monaten liegen. Die Äquivalenzklassen könnten wie folgt definiert werden:
- Äquivalenzklasse 1: 12 bis 60 Monate
- Äquivalenzklasse 2: 61 bis 180 Monate
- Äquivalenzklasse 3: 181 bis 360 Monate
- Zinssatz: Der jährliche Zinssatz kann zwischen 1% und 25% variieren. Die Äquivalenzklassen könnten wie folgt definiert werden:
- Äquivalenzklasse 1: 1% bis 5%
- Äquivalenzklasse 2: 6% bis 15%
- Äquivalenzklasse 3: 16% bis 25%
b)
2. Für jede Ihrer identifizierten Äquivalenzklassen führen Sie eine Grenzwertanalyse durch. Geben Sie mindestens zwei Testfälle pro Äquivalenzklasse an (einen am unteren und einen am oberen Rand). Berücksichtigen Sie dabei auch die Rand- und Extremwerte jeder Klasse.
Lösung:
- Darlehensbetrag:
- Äquivalenzklasse 1 (1.000 Euro bis 100.000 Euro):
- Testfall 1: 1.000 Euro (unterer Rand)
- Testfall 2: 100.000 Euro (oberer Rand)
- Äquivalenzklasse 2 (100.001 Euro bis 500.000 Euro):
- Testfall 1: 100.001 Euro (unterer Rand)
- Testfall 2: 500.000 Euro (oberer Rand)
- Äquivalenzklasse 3 (500.001 Euro bis 1.000.000 Euro):
- Testfall 1: 500.001 Euro (unterer Rand)
- Testfall 2: 1.000.000 Euro (oberer Rand)
- Laufzeit des Darlehens:
- Äquivalenzklasse 1 (12 bis 60 Monate):
- Testfall 1: 12 Monate (unterer Rand)
- Testfall 2: 60 Monate (oberer Rand)
- Äquivalenzklasse 2 (61 bis 180 Monate):
- Testfall 1: 61 Monate (unterer Rand)
- Testfall 2: 180 Monate (oberer Rand)
- Äquivalenzklasse 3 (181 bis 360 Monate):
- Testfall 1: 181 Monate (unterer Rand)
- Testfall 2: 360 Monate (oberer Rand)
- Zinssatz:
- Äquivalenzklasse 1 (1% bis 5%):
- Testfall 1: 1% (unterer Rand)
- Testfall 2: 5% (oberer Rand)
- Äquivalenzklasse 2 (6% bis 15%):
- Testfall 1: 6% (unterer Rand)
- Testfall 2: 15% (oberer Rand)
- Äquivalenzklasse 3 (16% bis 25%):
- Testfall 1: 16% (unterer Rand)
- Testfall 2: 25% (oberer Rand)
Aufgabe 3)
Du arbeitest als Software-Tester und bist beauftragt, sowohl Black-Box- als auch White-Box-Testing-Methoden auf eine neu entwickelte Anwendung anzuwenden. Diese Anwendung berechnet den Nettopreis basierend auf der Anzahl der gekauften Artikel und einem festgelegten Preis pro Artikel. Es wird auch ein Discount gewährt, wenn die Anzahl der gekauften Artikel eine bestimmte Schwelle überschreitet.
a)
Nutze die Äquivalenzklassenbildung und die Grenzwertanalyse, um geeignete Black-Box-Testfälle für die oben beschriebene Anwendung zu entwerfen. Erkläre jeweils, warum du jeden Testfall ausgewählt hast.
Lösung:
Um geeignete Black-Box-Testfälle für die Anwendung zu entwerfen, die den Nettopreis basierend auf der Anzahl der gekauften Artikel und einem festgelegten Preis pro Artikel berechnet und gegebenenfalls einen Rabatt gewährt, nutzen wir die Äquivalenzklassenbildung und die Grenzwertanalyse. Mithilfe dieser Methoden lassen sich systematische und vollständige Testfälle entwickeln.
- Äquivalenzklassenbildung: Diese Technik teilt Eingabedaten in Äquivalenzklassen ein, wobei davon ausgegangen wird, dass alle Werte innerhalb einer Klasse die gleiche Testwirkung haben.
- Klasse 1: Kleine Anzahl gekaufter Artikel, unter der Rabatt-Schwelle.
- Testfall 1: 1 Artikel
- Testfall 2: Ein Wert knapp unter der Rabatt-Schwelle (zum Beispiel 9 Artikel, wenn die Schwelle 10 ist)
- Klasse 2: Genaue Anzahl der Artikel auf der Rabatt-Schwelle.
- Testfall 3: Genau 10 Artikel, wenn dies die Rabatt-Schwelle ist
- Klasse 3: Große Anzahl gekaufter Artikel, über der Rabatt-Schwelle.
- Testfall 4: Ein Wert genau einen über der Rabatt-Schwelle (zum Beispiel 11 Artikel, wenn die Schwelle 10 ist)
- Testfall 5: Eine deutlich größere Anzahl, etwa 50 Artikel
- Grenzwertanalyse: Dieser Ansatz konzentriert sich auf die Ränder der Äquivalenzklassen, da dort häufig Fehler auftreten.
- Testfall 6: 0 Artikel (Grenzwert unterhalb des Minimums)
- Testfall 7: 1 Artikel (Grenzwert Minimum)
- Testfall 8: Rabatt-Schwelle – 1 (zum Beispiel 9 Artikel, wenn die Schwelle 10 ist)
- Testfall 9: Rabatt-Schwelle (zum Beispiel 10 Artikel)
- Testfall 10: Rabatt-Schwelle + 1 (zum Beispiel 11 Artikel)
- Testfall 11: Eine sehr große Anzahl (zum Beispiel 1000 Artikel, um das obere Grenzwertverhalten zu testen)
Durch diese Kombination von Äquivalenzklassenbildung und Grenzwertanalyse können wir sicherstellen, dass die Anwendung unter verschiedenen Eingabebedingungen korrekt funktioniert. Jeder Testfall repräsentiert eine spezifische Bedingung, die in der Praxis auftreten könnte, und soll sicherstellen, dass sowohl normale als auch extreme Nutzungsszenarien abgedeckt werden.
b)
Analysiere die Kontrollflüsse der Anwendung und entwirf einen White-Box-Testfall, der die Pfadabdeckung maximiert. Erläutere, welche Pfade du identifiziert hast und wie dein Testfall diese abdeckt.
Lösung:
Um einen White-Box-Testfall zu entwerfen, der die Pfadabdeckung maximiert, müssen wir die Kontrollflüsse der Anwendung analysieren, die den Nettopreis basierend auf der Anzahl der gekauften Artikel und einem festgelegten Preis pro Artikel berechnet und gegebenenfalls einen Rabatt gewährt. Folgende Schritte erläutern die Analyse und die daraus abgeleiteten Testfälle:
- Analyse der Kontrollflüsse:
Die Anwendung folgt einer bestimmten logischen Struktur, die wir durch ein Flussdiagramm oder Pseudocode repräsentieren können. Hier ist eine vereinfachte Darstellung der Anwendung:
if (quantity < discount_threshold) { \t net_price = quantity * price_per_item; } else { \t total_price = quantity * price_per_item; \t discount = total_price * discount_rate; \t net_price = total_price - discount; }
- Identifikation der Pfade:
- Pfad 1: quantity < discount_threshold
Dies ist der Pfad, wenn die Anzahl der gekauften Artikel unter der Rabatt-Schwelle liegt, und daher kein Rabatt gewährt wird.
- Pfad 2: quantity ≥ discount_threshold
Dies ist der Pfad, wenn die Anzahl der gekauften Artikel die Rabatt-Schwelle erreicht oder überschreitet, und daher ein Rabatt gewährt wird.
- White-Box-Testfälle:
- Testfall 1:
- Parameter:
- quantity = 5
- price_per_item = 10
- discount_threshold = 10
- Erwartetes Ergebnis:
- Überdeckung:
- Dieser Testfall deckt Pfad 1 ab, bei dem die Anzahl der gekauften Artikel unter der Rabatt-Schwelle liegt und kein Rabatt gewährt wird.
- Testfall 2:
- Parameter:
- quantity = 10
- price_per_item = 10
- discount_threshold = 10
- discount_rate = 0.10
- Erwartetes Ergebnis:
- total_price = 10 * 10 = 100
- discount = 100 * 0.10 = 10
- net_price = 100 - 10 = 90
- Überdeckung:
- Dieser Testfall deckt Pfad 2 ab, bei dem die Anzahl der gekauften Artikel die Rabatt-Schwelle erreicht und ein Rabatt gewährt wird.
- Testfall 3:
- Parameter:
- quantity = 15
- price_per_item = 10
- discount_threshold = 10
- discount_rate = 0.10
- Erwartetes Ergebnis:
- total_price = 15 * 10 = 150
- discount = 150 * 0.10 = 15
- net_price = 150 - 15 = 135
- Überdeckung:
- Dieser Testfall deckt ebenfalls Pfad 2 ab, aber mit einer höheren Anzahl von Artikeln, um zu gewährleisten, dass die Logik auch bei größeren Mengen korrekt funktioniert.
Durch diese Testfälle stellen wir sicher, dass alle möglichen Pfade durch die Anwendung abgedeckt sind. Dabei überprüfen wir die korrekte Berechnung des Nettopreises sowohl für Szenarien ohne Rabatt als auch für Szenarien mit Rabatt.
c)
Angenommen, die Discount-Regel ist, dass ein Discount von 10% gewährt wird, wenn die Anzahl der gekauften Artikel größer oder gleich 100 ist. Schreibe eine Funktion in Python, die diesen Discount berechnet und einen entsprechenden White-Box-Test dafür durchführt. Teste, ob die Funktion den Discount korrekt anwendet, indem du mindestens drei verschiedene Pfade ausführst.
Lösung:
Um die Aufgabe zu lösen, schreiben wir zuerst eine Funktion in Python, die den Nettopreis basierend auf den Anforderungen berechnet. Danach führen wir White-Box-Tests durch, um sicherzustellen, dass die Funktion korrekt arbeitet und alle Pfade abgedeckt sind.
Funktion zur Berechnung des Nettopreises
def calculate_net_price(quantity, price_per_item): \t discount_threshold = 100 \t discount_rate = 0.10 \t if quantity >= discount_threshold: \t \t total_price = quantity * price_per_item \t \t discount = total_price * discount_rate \t \t net_price = total_price - discount \t else: \t \t net_price = quantity * price_per_item \t return net_price
White-Box-Testfälle
Wir führen drei Testfälle durch, um alle wesentlichen Pfade abzudecken:
- Testfall 1: Menge unterhalb der Rabatt-Schwelle (quantity < 100)
def test_case_1(): \t quantity = 50 \t price_per_item = 10 \t expected_net_price = 50 * 10 = 500 \t result = calculate_net_price(quantity, price_per_item) \t assert result == expected_net_price, f'Expected {expected_net_price}, but got {result}'
- Testfall 2: Menge genau auf der Rabatt-Schwelle (quantity = 100)
def test_case_2(): \t quantity = 100 \t price_per_item = 10 \t total_price = 100 * 10 = 1000 \t discount = total_price * 0.10 = 100 \t expected_net_price = total_price - discount = 900 \t result = calculate_net_price(quantity, price_per_item) \t assert result == expected_net_price, f'Expected {expected_net_price}, but got {result}'
- Testfall 3: Menge über der Rabatt-Schwelle (quantity > 100)
def test_case_3(): \t quantity = 150 \t price_per_item = 10 \t total_price = 150 * 10 = 1500 \t discount = total_price * 0.10 = 150 \t expected_net_price = total_price - discount = 1350 \t result = calculate_net_price(quantity, price_per_item) \t assert result == expected_net_price, f'Expected {expected_net_price}, but got {result}'
Ausführung der White-Box-Tests
def run_tests(): \t test_case_1() \t test_case_2() \t test_case_3() \t print('All test cases passed!') run_tests()
Durch die Ausführung dieser Testfälle stellen wir sicher, dass die Funktion alle relevanten Pfade abdeckt und korrekt funktioniert, indem sie den entsprechenden Discount nur bei einer Menge von 100 oder mehr Artikeln anwendet.
Aufgabe 4)
Du bist für die Qualitätssicherung eines komplexen Softwareprojekts verantwortlich. Im Rahmen deines Studiums hast du die Best Practices für die Implementierung von Testskripten kennengelernt. Du sollst nun diese Best Practices in die Praxis umsetzen und verschiedene Aspekte der Testskripte analysieren und bewerten.
a)
Modularität und Isolierung: Du hast zwei Testskripte, testLogin und testRegistration. Beide Skripte prüfen das Anmelde- und Registrierungsmodul deiner Software. Aktuell wird in beiden Skripten die Datenbank initialisiert und am Ende wird die Verbindung wieder geschlossen.
- Analysiere diese Vorgehensweise in Bezug auf Modularität und Isolierung.
- Schlage Änderungen vor, um die Testskripte modularer und isolierter zu gestalten.
Lösung:
Die Modularität und Isolierung von Testskripten sind entscheidende Faktoren für die Wartbarkeit und Zuverlässigkeit von Softwaretests. Lass uns die genannten Testskripte testLogin und testRegistration analysieren und Verbesserungsmöglichkeiten finden.
- Analyse der aktuellen Vorgehensweise:
Derzeit initialisieren beide Testskripte die Datenbank und schließen die Verbindung am Ende. Diese Vorgehensweise führt zu einigen Problemen hinsichtlich Modularität und Isolierung:
- Redundanz: Die Initialisierung und das Schließen der Datenbankverbindung wird in beiden Skripten wiederholt. Dies führt zu doppeltem Code, der die Wartung erschwert.
- Kopplung: Beide Skripte sind eng mit der Datenbankinitialisierung verknüpft. Änderungen an der Datenbankkonfiguration erfordern Anpassungen in beiden Skripten, was zu einem höheren Wartungsaufwand führt.
- Isolierung: Wenn beide Skripte dieselbe Datenbankinstanz verwenden, können sie sich gegenseitig beeinflussen. Beispielsweise könnten Testdaten aus einem Skript das Ergebnis des anderen Skripts beeinflussen.
- Vorgeschlagene Änderungen:
- Datenbankinitialisierung auslagern: Extrahiere die Initialisierung und das Schließen der Datenbankverbindung in separate Funktionen oder Set-up/Teardown-Methoden. Diese Methoden können in einer gemeinsamen Basisklasse oder einem Hilfsmodul platziert werden.
def setup_database_connection(): # Code zur Initialisierung der Datenbankverbindung passdef teardown_database_connection(): # Code zum Schließen der Datenbankverbindung pass# Basisklasse für die Testsclass BaseTest: @staticmethod def setup_method(): setup_database_connection() @staticmethod def teardown_method(): teardown_database_connection()
- Isolation durch Testdaten: Verwende für jeden Test eigene Testdaten, um sicherzustellen, dass Tests unabhängig voneinander sind. Dies kann durch Zurücksetzen der Datenbank auf einen bekannten Zustand vor jedem Testlauf erreicht werden.
class TestLogin(BaseTest): def test_login_success(self): # Bereite Testdaten vor # Führe Login-Test durch pass def test_login_failure(self): # Bereite Testdaten vor # Führe Login-Test durch pass
- Parallelisierung ermöglichen: Durch eine klar definierte Set-up/Teardown-Struktur und isolierte Testdaten können Tests parallel ausgeführt werden, um die Testdauer zu verkürzen.
Durch diese Änderungen werden die Testskripte modularer, leichter wartbar und besser isoliert. Dies führt zu zuverlässigerem und effizienterem Testing.
b)
Automatisierung und Fehlerbehandlung: Du implementierst Testskripte mit JUnit. Ein bestimmter Testfall testPaymentProcessing schlägt häufig aufgrund von Netzwerkproblemen fehl.
- Beschreibe, wie du diese Tests mithilfe von JUnit automatisieren und robust gegen Netzwerkfehler gestalten könntest.
- Schreibe ein Pseudo-Testskript in JUnit, das die Behandlung von Netzwerkfehlern zeigt.
Lösung:
Um Testskripte robust gegen temporäre Netzwerkfehler zu gestalten, können Wiederholungsmechanismen und eine sinnvolle Fehlerbehandlung implementiert werden. JUnit bietet Möglichkeiten, solche Mechanismen einzubauen, um sicherzustellen, dass Tests nicht sofort wegen flüchtiger Probleme fehlschlagen.
- Automatisierung und Fehlerbehandlung:
Beschreibung:
Um die testPaymentProcessing-Tests zu automatisieren und robust gegen Netzwerkfehler zu gestalten, könnten die folgenden Schritte unternommen werden:
- Retry-Mechanismus einbauen: Implementiere einen Mechanismus, der den Test mehrfach ausführt, bevor er endgültig als fehlgeschlagen markiert wird. Dies hilft, temporäre Netzwerkprobleme abzufangen.
- Timeouts und Pausen: Verwende Timeouts und Pausen zwischen den Wiederholungen, um dem Netzwerk oder dem Server Zeit zur Erholung zu geben.
- Umgebungsüberwachungen: Überwache die Netzwerkverbindung und protokolliere Fehler, um wiederkehrende Probleme identifizieren zu können.
- Pseudo-Testskript in JUnit:
import static org.junit.jupiter.api.Assertions.*;import org.junit.jupiter.api.RepeatedTest;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.junit.jupiter.api.extension.ExtensionContext;import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;public class PaymentProcessingTest { private static final int MAX_RETRIES = 3; @RepeatedTest(value = MAX_RETRIES) void testPaymentProcessing() { try { // Simuliere Zahlungsprozess processPayment(); } catch (NetworkException e) { // Werfe Ausnahme nur, wenn es sich um den letzten Versuch handelt if (ExtensionContext.getRequiredTestMethod().getParameterCount() == MAX_RETRIES) { throw e; } } } void processPayment() throws NetworkException { // Hier wird der eigentliche Zahlungsprozess implementiert // Zum Beispiel: HTTP-Anfrage an Zahlungs-Gateway senden // Wirf NetworkException bei Netzwerkfehlern } // Benutzerdefinierte Ausnahme für Netzwerkfehler class NetworkException extends Exception { public NetworkException(String message) { super(message); } }}// Erweiterung für das Wiederholen von Tests bei Ausnahmenclass RetryTestExtension implements TestExecutionExceptionHandler { @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { if (context.getExecutionCount() < MAX_RETRIES && throwable instanceof NetworkException) { // Wartezeit einführen, bevor der Test erneut versucht wird Thread.sleep(1000); // Test erneut ausführen context.getTestInstance().get().testPaymentProcessing(); } else { // Ausnahme weiterwerfen, wenn maximale Anzahl an Versuchen erreicht ist throw throwable; } }}
Durch diese Implementierung werden temporäre Netzwerkfehler abgefangen, und der Test wird mehrere Male wiederholt, bevor er als fehlgeschlagen markiert wird. Dies erhöht die Zuverlässigkeit des Tests und reduziert die Wahrscheinlichkeit von Flaky Tests, die durch temporäre Netzwerkprobleme verursacht werden.
c)
Dokumentation und Wiederholbarkeit: Im Testprojekt gibt es oft Inkonsistenzen in den Testergebnissen aufgrund unterschiedlicher Testumgebungen. Dies führt zu Problemen bei der Reproduzierbarkeit von Fehlern und ihrer Behebung.
- Erkläre, welche Maßnahmen zur Dokumentation und Sicherstellung der Wiederholbarkeit der Tests ergriffen werden sollten.
- Entwickle eine Strategie zum Umgang mit umgebungsbedingten Inkonsistenzen.
Lösung:
Um die Konsistenz und Wiederholbarkeit von Testergebnissen sicherzustellen, sind eine sorgfältige Dokumentation und eine Strategie zur Handhabung von umgebungsbedingten Inkonsistenzen erforderlich. Hier sind einige Maßnahmen und Strategien, die Du ergreifen kannst:
- Maßnahmen zur Dokumentation und Sicherstellung der Wiederholbarkeit:
- Detaillierte Testdokumentation: Stelle sicher, dass alle Testfälle, Testbedingungen und erwarteten Ergebnisse ausführlich dokumentiert sind. Dies umfasst:
- Eine klare Beschreibung des Testziels.
- Angaben zu den verwendeten Daten und Ressourcen.
- Beschreibungen der Schritte zur Reproduktion der Tests.
- Erwartete Ergebnisse und deren Abweichungen.
- Umgebungskonfiguration: Dokumentiere die Testumgebung detailliert:
- Betriebssystemversionen und -einstellungen.
- Software-Abhängigkeiten und deren Versionen.
- Datenbanken und deren Versionen.
- Netzwerkeinstellungen und Zugangsrechte.
Dies hilft, die Umgebung konsistent zu halten und Testergebnisse zu reproduzieren. - Automatisierte Set-up-Skripte: Verwende automatisierte Skripte zur Einrichtung der Testumgebung. Tools wie Docker oder Vagrant können verwendet werden, um sicherzustellen, dass die Testumgebung immer in einem bekannten Zustand beginnt.
- Versionskontrolle: Alle Testskripte und Konfigurationsdateien sollten unter Versionskontrolle stehen (zum Beispiel mit Git). Dies ermöglicht eine Nachverfolgbarkeit von Änderungen und stellt sicher, dass jeder Test mit einer bestimmten Version des Codes reproduziert werden kann.
- Kontinuierliche Integration (CI): Verwende CI/CD-Pipelines (zum Beispiel Jenkins, GitLab CI), um Tests regelmäßig und automatisch in einer standardisierten Umgebung auszuführen. Dies stellt sicher, dass Tests in einer kontrollierten Umgebung wiederholt werden.
- Strategie zur Handhabung von umgebungsbedingten Inkonsistenzen:
- Virtualisierung und Containerisierung: Verwende Virtualisierungstechnologien wie Docker, um Testumgebungen in Containern zu isolieren. Dies stellt sicher, dass alle Tests in einer identischen Umgebung ausgeführt werden, unabhängig von der zugrunde liegenden Hardware.
- Verwendung von Mocking und Stubbing: Wenn echte Abhängigkeiten unzuverlässig oder schwer zu kontrollieren sind (zum Beispiel externe APIs), verwende Mocking-Frameworks (zum Beispiel Mockito in Java) oder Stub-Implementierungen, um diese Abhängigkeiten zu simulieren.
- Umgebungs-Snapshots: Erstelle Snapshots der Testumgebung, die leicht wiederhergestellt werden können. Dies kann durch Virtualisierungssoftware wie VMware oder VirtualBox erreicht werden.
- Fehlerprotokollierung und Berichtswesen: Implementiere ein detailliertes Fehlerprotokollierungssystem, das Umgebungsinformationen bei Auftreten von Fehlern erfasst. Diese Informationen sollten in die Fehlerberichte aufgenommen werden, um die Ursache leichter zu identifizieren.
- Regelmäßige Überprüfung der Umgebung: Überprüfe regelmäßig die Konsistenz der Testumgebungen. Dies kann automatisiert werden, indem Skripte die Konfigurationen der Testumgebungen gegen eine bekannte gute Konfiguration prüfen.
Durch diese Maßnahmen stellst Du sicher, dass Testergebnisse konsistent und reproduzierbar sind. Dies erleichtert die Fehlersuche und -behebung erheblich und erhöht die Zuverlässigkeit der Qualitätssicherung.