-
Notifications
You must be signed in to change notification settings - Fork 0
Kapitel 5: Unit Tests
Nennung von 10 Unit-Tests und Beschreibung, was getestet wird
Unit Test | Beschreibung |
---|---|
PlayerTest # incrementScore | Testet das Inkrementieren des Scores eines Spielers |
QuestionTest # testCheckAnswer | Testet für jede Antwortoption einer Frage, ob der richtige Wahrheitswert zurückgegeben wird |
QuestionValidatorTest # validateForCorrectQuestionOptions | Testet den QuestionValidator mit einer validen Eingabe |
QuestionValidatorTest # validateForIncorrectAmountOfQuestionOptions() | Testet, ob bei einer Frage mit fünf Antwortoptionen eine InvalidQuestionFormatException zurückgegeben wird |
QuestionValidatorTest # validateForMoreThanOneCorrectAnswer() | Testet, ob bei einer Frage mit zwei richtigen Antworten eine InvalidQuestionFormatException zurückgegeben wird |
QuizGameTest # isSelectingCategory | Testet, ob vor der Wahl einer Kategorie True und danach False zurückgegeben wird |
QuizGameTest # isFinished | Testet, ob zu Beginn False und nach dem Spielen der festgelegten Anzahl an Kategorien True zurückgegeben wird |
QuizGameTest # submitRightQuestionAnswer | Testet das Wählen einer richtigen Antwort und das dementsprechende Inkrementieren des Scores |
QuizGameTest # submitWrongQuestionAnswer | Testet das Wählen einer falschen Antwort und den dabei unveränderten Score |
GameSettingsScreenProviderTest # testMenuOption | Testet, ob die Wahl des entsprechenden Menüeintrags zurück ins Hauptmenü führt |
Begründung/Erläuterung, wie ‘Automatic’ realisiert wurde
Die Ausführung der Tests erfolgt bei jedem Build. Es ist also nicht nur einfach die Tests auszuführen, sondern bei der Entwicklung auch unumgänglich. Die Tests laufen dann automatisch ab und überprüfen sich selbst. Außerdem wurde zur weiteren Automatisierung der Unit-Tests ein GitHub Workflow eingerichtet, der die Tests auch bei jedem Commit auf Main und in jeder Pull Request auf Main ausführt. So wird das Mergen von Änderungen, die fehlschlagende Tests verursachen, verhindert. Außerdem ermöglicht dieses Vorgehen die automatisierte Veröffentlichung des Test Reports.
Jeweils ein positives und negatives Beispiel zu ‘Thorough’, jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist
Im QuestionValidatorTest
werden verschiedene Testfälle berücksichtigt. Zum einen wird eine valide Eingabe getestet. Zum anderen wird auch das Ergebnis bei fünf Antwortoptionen bzw. bei zwei richtigen Antwortoptionen getestet. Damit wurden auch zwei invalide Eingaben in die Testfälle aufgenommen. Zwar wurden dennoch nicht alle möglichen Fehler berücksichtigt (z.B. weniger als vier Antwortoptionen oder drei richtige Antwortoptionen), allerdings kann an dieser Stelle davon ausgegangen werden, dass die vorhandenen Testfälle die zu testende Klasse QuestionValidator
vollständig abdecken und Fehler in der Anwendungslogik durch diese Testfälle bemerkt werden würden.
@Test
void validateForCorrectQuestionOptions() throws InvalidQuestionFormatException {
QuestionOption[] questionOptions = new QuestionOption[4];
questionOptions[0] = new QuestionOption("Test 1", true);
questionOptions[1] = new QuestionOption("Test 2", false);
questionOptions[2] = new QuestionOption("Test 3", false);
questionOptions[3] = new QuestionOption("Test 4", false);
assertEquals(QuestionValidator.validate(questionOptions), questionOptions);
}
@Test
void validateForIncorrectAmountOfQuestionOptions() {
QuestionOption[] questionOptions = new QuestionOption[5];
assertThrows(InvalidQuestionFormatException.class, () -> QuestionValidator.validate(questionOptions));
}
@Test
void validateForMoreThanOneCorrectAnswer() {
QuestionOption[] questionOptions = new QuestionOption[4];
questionOptions[0] = new QuestionOption("Test 1", true);
questionOptions[1] = new QuestionOption("Test 2", true);
questionOptions[2] = new QuestionOption("Test 3", false);
questionOptions[3] = new QuestionOption("Test 4", false);
assertThrows(InvalidQuestionFormatException.class, () -> QuestionValidator.validate(questionOptions));
}
Im ChangeSettingsScreenProviderTest
wird lediglich getestet, ob der richtige nächste ScreenProviderType
zurückgegeben wird. Dazu wird eine Eingabe von drei Kategorien getätigt. An dieser Stelle werden also nicht alle notwendigen Testfälle berücksichtigt. Besonders kritisch sind meist invalide Eingaben, die nicht durch die Tests dieser Klasse abgedeckt werden.
@Test
void getNextScreenProviderType() throws InterruptedException {
changeCategorySettingScreenProvider.execute();
assertEquals(ScreenProviderType.GAME_SETTINGS, changeCategorySettingScreenProvider.getNextScreenProviderType());
}
Jeweils ein positives und negatives Beispiel zu ‘Professional’, jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist
Für die Tests der Anwendungslogik wurden einige Hilfsfunktionen in der Klasse Helper
implementiert, um diese in verschiedenen Tests wiederverwenden zu können. So gibt es beispielsweise Methoden, die Mocks für einen TextInputScreen
(siehe Code-Beispiel), NumberInputScreen
oder OptionScreen
anlegen. Auch ein Mock für das gesamte Repository
kann mithilfe einer dieser Hilfsfunktionen erstellt werden, was die Handhabung von Test-Daten in den verschiedenen Repositories vereinfacht und duplizierten Code vermeidet, sodass auch die Unit-Tests der Anwendung einfach zu erweitern und zu warten sind.
public static TextInputScreen getMockedTextInputScreen(String returnValue) {
TextInputScreen textInputScreen = Mockito.mock(TextInputScreen.class);
Action<String> stringAction = Mockito.mock(Action.class);
Mockito.when(stringAction.getActionValue()).thenReturn(returnValue);
Mockito.when(textInputScreen.getTextInput()).thenReturn(stringAction);
return textInputScreen;
}
Die Implementierung einiger Tests, die nicht notwendig gewesen wären, kann als negatives Beispiel herangezogen werden. So wurden teilweise Getter oder Enums (siehe Code-Beispiel) getestet, was keinen wirklichen Mehrwert bietet. Der Aufwand hierfür war zwar relativ gering, hätte allerdings besser in weitere Funktionalität oder andere Tests investiert werden können. Abgesehen von den minimalen Ausführungszeiten schaden diese Tests zwar auch nicht, dennoch kann dies bei der weiteren Entwicklung vermieden werden.
@Test
void getDisplayName() {
assertEquals("Training", GameModeEnum.TRAINING.getDisplayName());
assertEquals("Local Multiplayer", GameModeEnum.LOCAL_MULTIPLAYER.getDisplayName());
assertEquals("Online Multiplayer", GameModeEnum.ONLINE_MULTIPLAYER.getDisplayName());
}
Code Coverage im Projekt analysieren und begründen
Die aktuelle Code Coverage kann im Test Report eingesehen werden. Zu diesem Zeitpunkt liegt sie bei 33%, unterscheidet sich aber je nach Schicht. Im Domain-Layer ist sie mit 98% am höchsten, da hier der Fokus beim Testen lag. Der Application-Layer ist zwar zu 43% getestet, weist aber in absoluten Zahlen dennoch die meisten ungetesteten Instructions und Branches auf. Im Gegensatz dazu wurden die Netzwerk- und Datenbank-Plugin-Schicht bisher noch gar nicht getestet, weshalb der Durchschnitt über alle Module niedriger ist, als in den zuerst genannten Modulen.
Analyse und Begründung des Einsatzes von zwei Fake/Mock-Objekten, zusätzlich jeweils UML Diagramm der Klasse
Für Fakes und Mocks wurde das Mockito Framework verwendet. Mit dessen Hilfe wurde beispielsweise das Repository
gemocked. Dies war notwendig, da die Repositories zwar Teil der Domain-Schicht sind, ihre Implementierungen allerdings in der Plugin-Schicht zu finden sind. Für die Tests im Application-Layer kann daher das gemockte Repository verwendet werden. Dieser Mock kann durch eine Hilfsfunktion der Helper
Klasse erzeugt werden, sodass dieses bereits eine Reihe notwendiger Testdaten enthält. Dies umfasst beispielsweise eine Frage in einer Kategorie mit den entsprechenden Antwortoptionen.