Skip to content

Kapitel 5: Unit Tests

Kevin Wiesner edited this page Jun 7, 2024 · 9 revisions

10 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

ATRIP: Automatic

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.

ATRIP: Thorough

Jeweils ein positives und negatives Beispiel zu ‘Thorough’, jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist

Positiv Beispiel

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));
}

Negativ Beispiel

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());
}

ATRIP: Professional

Jeweils ein positives und negatives Beispiel zu ‘Professional’, jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist

Positiv Beispiel

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;
}

Negativ Beispiel

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

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.

Fakes und Mocks

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.