- Integrationstest einer Datenbank mit MariaDB als Ersatz für MySql
- Implementierung einer mehrsprachigen Anwendung
- Speichern von Dateien in der Anwendung und Daten darüber in der Datenbank
Arten von Tests
Was ist ein Test? Wie Wiki sagt: „ Ein Test oder Versuch ist eine Möglichkeit, die zugrunde liegenden Prozesse eines Systems zu untersuchen, indem das System in verschiedene Situationen versetzt und beobachtbare Änderungen darin verfolgt werden.“ Mit anderen Worten handelt es sich hierbei um einen Test der korrekten Funktion unseres Systems in bestimmten Situationen. Schauen wir uns mal an, welche Arten von Tests es gibt:-
Unit-Tests sind Tests, deren Aufgabe es ist, jedes Modul des Systems einzeln zu testen. Es ist wünschenswert, dass es sich um minimal teilbare Teile des Systems handelt, beispielsweise um Module.
-
Beim Systemtest handelt es sich um einen High-Level-Test zum Testen des Betriebs eines größeren Teils einer Anwendung oder des Systems als Ganzes.
-
Unter Regressionstests versteht man Tests, mit denen überprüft wird, ob neue Funktionen oder Fehlerbehebungen die bestehende Funktionalität der Anwendung beeinträchtigen und ob alte Fehler erneut auftreten.
-
Beim Funktionstest wird die Übereinstimmung eines Teils der Anwendung mit den in Spezifikationen, User Stories usw. genannten Anforderungen überprüft.
Arten von Funktionstests:
- „White-Box“-Test zur Übereinstimmung eines Teils der Anwendung mit den Anforderungen mit Kenntnis der internen Implementierung des Systems;
- „Black-Box“-Test zur Übereinstimmung eines Teils der Anwendung mit den Anforderungen ohne Kenntnis der internen Implementierung des Systems.
- Leistungstests sind eine Art von Tests, die geschrieben werden, um die Geschwindigkeit zu bestimmen, mit der ein System oder ein Teil davon unter einer bestimmten Last läuft.
- Lasttests – Tests, die dazu dienen, die Stabilität des Systems unter Standardlasten zu überprüfen und den maximal möglichen Spitzenwert zu ermitteln, bei dem die Anwendung ordnungsgemäß funktioniert.
- Bei Stresstests handelt es sich um eine Testart, mit der die Funktionalität einer Anwendung unter nicht standardmäßigen Belastungen überprüft und der maximal mögliche Spitzenwert ermittelt werden soll, bei dem das System nicht abstürzt.
- Sicherheitstests – Tests zur Überprüfung der Sicherheit eines Systems (vor Angriffen durch Hacker, Viren, unbefugtem Zugriff auf vertrauliche Daten und anderen Lebensfreuden).
- Beim Lokalisierungstest handelt es sich um einen Lokalisierungstest für eine Anwendung.
- Bei Usability-Tests handelt es sich um eine Art von Tests, die darauf abzielen, die Benutzerfreundlichkeit, Verständlichkeit, Attraktivität und Erlernbarkeit für Benutzer zu überprüfen. Das klingt alles gut, aber wie funktioniert es in der Praxis? Es ist ganz einfach: Es wird die Testpyramide von Mike Cohn verwendet: Dies ist eine vereinfachte Version der Pyramide: Jetzt ist sie in kleinere Teile unterteilt. Aber heute werden wir nicht pervertieren und die einfachste Option in Betracht ziehen.
-
Unit – Unit-Tests, die in verschiedenen Schichten der Anwendung verwendet werden und die kleinste teilbare Logik der Anwendung testen: zum Beispiel eine Klasse, meistens jedoch eine Methode. Diese Tests versuchen normalerweise, so viel wie möglich von der externen Logik zu isolieren, d. h. den Eindruck zu erwecken, dass der Rest der Anwendung im Standardmodus arbeitet.
Von diesen Tests sollte es immer viele geben (mehr als bei anderen Typen), da sie kleine Teile testen, sehr leichtgewichtig sind und nicht viele Ressourcen verbrauchen (mit Ressourcen meine ich RAM und Zeit).
-
Integration – Integrationstests. Es prüft größere Teile des Systems, das heißt entweder die Kombination mehrerer Logikteile (mehrere Methoden oder Klassen) oder die Korrektheit der Arbeit mit einer externen Komponente. Normalerweise gibt es weniger dieser Tests als Unit-Tests, da sie schwerer sind.
Als Beispiel für Integrationstests können Sie in Betracht ziehen, eine Verbindung zu einer Datenbank herzustellen und zu überprüfen, ob die damit arbeitenden Methoden ordnungsgemäß funktionieren .
-
UI – Tests, die die Funktion der Benutzeroberfläche überprüfen. Sie beeinflussen die Logik auf allen Ebenen der Anwendung, weshalb sie auch End-to-End genannt werden. Davon gibt es in der Regel deutlich weniger, da sie am schwersten sind und die notwendigsten (benutzten) Wege prüfen müssen.
In der Abbildung oben sehen wir das Verhältnis der Flächen verschiedener Teile des Dreiecks: Bei der Anzahl dieser Tests in der realen Arbeit bleibt ungefähr das gleiche Verhältnis erhalten.
Heute werfen wir einen genaueren Blick auf die am häufigsten verwendeten Tests – Unit-Tests, da alle Java-Entwickler mit etwas Selbstachtung in der Lage sein sollten, sie auf einem grundlegenden Niveau zu verwenden.
- Material über Code Coverage auf JavaRush und auf Habré ;
- Grundlegende Testtheorie .
- Wir schreiben unseren Test.
- Wir führen den Test durch, unabhängig davon, ob er bestanden wurde oder nicht (wir sehen, dass alles rot ist – keine Panik: So sollte es sein).
- Wir fügen den Code hinzu, der diesen Test erfüllen soll (führen Sie den Test aus).
- Wir überarbeiten den Code.
- Angabe der zu testenden Daten (Fixtures).
- Verwendung des zu testenden Codes (Aufruf der zu testenden Methode).
- Überprüfen Sie die Ergebnisse und vergleichen Sie sie mit den erwarteten.
assertEquals(Object expecteds, Object actuals)
— prüft, ob die übertragenen Objekte gleich sind.assertTrue(boolean flag)
— prüft, ob der übergebene Wert „true“ zurückgibt.assertFalse(boolean flag)
– prüft, ob der übergebene Wert „false“ zurückgibt.assertNull(Object object)
– prüft, ob das Objekt null ist.assertSame(Object firstObject, Object secondObject)
— prüft, ob sich die übergebenen Werte auf dasselbe Objekt beziehen.assertThat(T t, Matcher<T> matcher)
— prüft, ob t die im Matcher angegebene Bedingung erfüllt.
Schlüsselkonzepte des Unit-Tests
Die Testabdeckung (Code Coverage) ist eine der wichtigsten Beurteilungen der Qualität von Anwendungstests. Dies ist der Prozentsatz des Codes, der von Tests abgedeckt wurde (0–100 %). In der Praxis verfolgen viele Leute diesen Prozentsatz, mit dem ich nicht einverstanden bin, da sie anfangen, Tests dort hinzuzufügen, wo sie nicht benötigt werden. Unser Dienst verfügt beispielsweise über Standard-CRUD-Operationen (Erstellen/Abrufen/Aktualisieren/Löschen) ohne zusätzliche Logik. Diese Methoden sind reine Vermittler, die Arbeit an die Ebene delegieren, die mit dem Repository arbeitet. In dieser Situation haben wir nichts zu testen: Vielleicht ruft diese Methode eine Methode aus dem Tao auf, aber das ist nicht ernst. Zur Beurteilung der Testabdeckung werden in der Regel zusätzliche Tools verwendet: JaCoCo, Cobertura, Clover, Emma usw. Für eine detailliertere Untersuchung dieses Problems halten Sie einige geeignete Artikel bereit:Testphasen
Der Test besteht aus drei Stufen:Testumgebungen
Kommen wir nun zur Sache. Für Java stehen mehrere Testumgebungen (Frameworks) zur Verfügung. Die beliebtesten davon sind JUnit und TestNG. Für unsere Überprüfung verwenden wir: Ein JUnit-Test ist eine in einer Klasse enthaltene Methode, die nur zum Testen verwendet wird. Eine Klasse wird normalerweise genauso benannt wie die Klasse, die sie testet, mit +Test am Ende. Zum Beispiel CarService→ CarServiceTest. Das Maven-Build-System fügt solche Klassen automatisch in den Testbereich ein. Tatsächlich wird diese Klasse als Testklasse bezeichnet. Lassen Sie uns die grundlegenden Anmerkungen ein wenig durchgehen: @Test – Definition dieser Methode als Testmethode (tatsächlich ist die mit dieser Anmerkung gekennzeichnete Methode ein Komponententest). @Before – markiert die Methode, die vor jedem Test ausgeführt wird. Zum Beispiel Füllen von Klassentestdaten, Lesen von Eingabedaten usw. @After – wird über der Methode platziert, die nach jedem Test aufgerufen wird (Daten bereinigen, Standardwerte wiederherstellen). @BeforeClass – über der Methode platziert – analog zu @Before. Diese Methode wird jedoch nur einmal vor allen Tests für eine bestimmte Klasse aufgerufen und muss daher statisch sein. Es wird verwendet, um anspruchsvollere Vorgänge durchzuführen, beispielsweise das Hochladen einer Testdatenbank. @AfterClass ist das Gegenteil von @BeforeClass: wird einmal für eine bestimmte Klasse ausgeführt, aber nach allen Tests ausgeführt. Wird beispielsweise verwendet, um persistente Ressourcen zu bereinigen oder die Verbindung zur Datenbank zu trennen. @Ignore – weist darauf hin, dass die folgende Methode deaktiviert ist und beim Ausführen von Tests insgesamt ignoriert wird. Es wird in verschiedenen Fällen verwendet, beispielsweise wenn die Basismethode geändert wurde und keine Zeit blieb, den Test dafür zu wiederholen. In solchen Fällen empfiehlt es sich auch, eine Beschreibung hinzuzufügen – @Ignore("Some description"). @Test (expected = Exception.class) – wird für negative Tests verwendet. Hierbei handelt es sich um Tests, die prüfen, wie sich eine Methode im Fehlerfall verhält. Das heißt, der Test erwartet, dass die Methode eine Ausnahme auslöst. Eine solche Methode wird durch die Annotation @Test gekennzeichnet, weist jedoch einen abzufangenden Fehler auf. @Test(timeout=100) – prüft, ob die Methode in nicht mehr als 100 Millisekunden ausgeführt wird. @Mock – eine Klasse wird über einem Feld verwendet, um ein bestimmtes Objekt als Mock festzulegen (dies ist nicht aus der Junit-Bibliothek, sondern von Mockito), und wenn wir es brauchen, legen wir das Verhalten des Mocks in einer bestimmten Situation fest , direkt in der Testmethode. @RunWith(MockitoJUnitRunner.class) – die Methode wird über der Klasse platziert. Dies ist die Schaltfläche zum Ausführen von Tests. Läufer können unterschiedlich sein: Es gibt beispielsweise die folgenden: MockitoJUnitRunner, JUnitPlatform, SpringRunner usw.). In JUnit 5 wurde die Annotation @RunWith durch die leistungsfähigere Annotation @ExtendWith ersetzt. Werfen wir einen Blick auf einige Methoden zum Vergleichen von Ergebnissen:assertThat(firstObject).isEqualTo(secondObject)
hier habe ich über die grundlegenden Methoden gesprochen, da der Rest verschiedene Variationen der oben genannten sind.
Testpraxis
Schauen wir uns nun das obige Material anhand eines konkreten Beispiels an. Wir werden die Methode für den Dienst testen – Update. Wir werden die Dao-Schicht nicht berücksichtigen, da sie unsere Standardeinstellung ist. Fügen wir einen Starter für Tests hinzu:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
Also die Serviceklasse:
@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
private final RobotDAO robotDAO;
@Override
public Robot update(Long id, Robot robot) {
Robot found = robotDAO.findById(id);
return robotDAO.update(Robot.builder()
.id(id)
.name(robot.getName() != null ? robot.getName() : found.getName())
.cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
.producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
.build());
}
}
8 - Ziehen Sie das aktualisierte Objekt aus der Datenbank. 9-14 - Erstellen Sie das Objekt über den Builder. Wenn das eingehende Objekt ein Feld hat, legen Sie es fest. Wenn nicht, belassen Sie das, was in der Datenbank ist. Schauen Sie sich unseren Test an:
@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
@Mock
private RobotDAO robotDAO;
private RobotServiceImpl robotService;
private static Robot testRobot;
@BeforeClass
public static void prepareTestData() {
testRobot = Robot
.builder()
.id(123L)
.name("testRobotMolly")
.cpu("Intel Core i7-9700K")
.producer("China")
.build();
}
@Before
public void init() {
robotService = new RobotServiceImpl(robotDAO);
}
1 – unser Runner 4 – isolieren Sie den Dienst von der Dao-Schicht durch Ersetzen eines Scheins. 11 – legen Sie eine Testentität für die Klasse fest (diejenige, die wir als Testhamster verwenden werden). 22 – legen Sie ein Dienstobjekt fest, das wir testen werden
@Test
public void updateTest() {
when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
Robot robotForUpdate = Robot
.builder()
.name("Vally")
.cpu("AMD Ryzen 7 2700X")
.build();
Robot resultRobot = robotService.update(123L, robotForUpdate);
assertNotNull(resultRobot);
assertSame(resultRobot.getId(),testRobot.getId());
assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
Hier sehen wir eine klare Aufteilung des Tests in drei Teile: 3-9 – Fixtures einstellen 11 – Ausführen des getesteten Teils 13-17 – Überprüfen der Ergebnisse Weitere Details: 3-4 – Festlegen des Verhaltens für Moka Dao 5 – Festlegen einer Instanz dass wir zusätzlich zu unserem Standard aktualisieren werden 11 - Verwenden Sie die Methode und nehmen Sie die resultierende Instanz 13 - Überprüfen Sie, ob sie nicht Null ist 14 - Überprüfen Sie die Ergebnis-ID und die angegebenen Methodenargumente 15 - Überprüfen Sie, ob der Name aktualisiert wurde 16 - Schauen Sie beim Ergebnis von CPU 17 – da wir dies nicht im Feld „Update-Instanz“ eingestellt haben, sollte es gleich bleiben, überprüfen wir es. Lassen Sie uns starten: Der Test ist grün, Sie können ausatmen)) Fassen wir also zusammen: Tests verbessern die Qualität des Codes und machen den Entwicklungsprozess flexibler und zuverlässiger. Stellen Sie sich vor, wie viel Aufwand wir aufwenden müssten, wenn wir Software mit Hunderten von Klassendateien neu entwerfen würden. Sobald wir für alle diese Klassen Komponententests geschrieben haben, können wir mit Zuversicht umgestalten. Und was am wichtigsten ist: Es hilft uns, Fehler während der Entwicklung leichter zu finden. Leute, das ist alles für mich heute: Likes abgeben, Kommentare schreiben)))
GO TO FULL VERSION