In diesem Beitrag werde ich erklären, wie ein Unit-Test mit Mockito und JUnit durchgeführt wird. Das Beispiel wird von Spring Boot erstellt und verwendet die Konstruktorinjektion. Daher werden Anmerkungen wie "@ Mock" und "@ InjectMock" nicht angezeigt.
Mockito ist ein Open Source-Testframework für Java, das unter der MIT-Lizenz veröffentlicht wurde. (von Wikipedia) ~~ Als Mock Ito lesen. ~~ Als Moquito lesen. Auf diese Weise können Sie ganz einfach ein Scheinobjekt zum Testen erstellen.
Es ist schwierig, einen Test für die folgenden Tests zu schreiben.
Sie können diese Probleme lösen, indem Sie die zu testende Klasse durch eine Klasse (Mock) ersetzen, die sich genauso verhält wie die abhängige Klasse. Die Vorteile sind wie folgt.
plugins {
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.mockito'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
test {
useJUnitPlatform()
}
Mockito ist im Spring Boot Sterter-Test enthalten, sodass Sie nichts aus den Spring Boot-Standardeinstellungen hinzufügen müssen. Ich habe es hinzugefügt, weil ich nur Lombok verwenden wollte.
Nehmen Sie als Beispiel das Kassensystem, das den Gesamtpreis von Produkten berechnet. Der Gesamtbetrag wird berechnet, indem der für jeden Produkttyp festgelegte Steuersatz addiert wird. Der Steuersatz kann sich in Zukunft ändern und wird daher von der DB bezogen.
Produktobjektdefinition
package com.example.mockito;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/**
*Produkt Klassifikation
*
* @author rcftdbeu
*
*/
enum ItemType {
Food, Other
}
/**
*Produkt
*
* @author rcftdbeu
*
*/
@Getter
@Setter
@AllArgsConstructor
public class Item {
/**Name*/
String name;
/**Preis*/
int price;
/**Art*/
ItemType itemType;
}
Service zur Berechnung des Gesamtbetrags
package com.example.mockito;
import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class POS {
private final TaxRateMaster taxRateMaster;
/**
*Berechnen Sie die Gesamtmenge der Waren
*
* @param itemList
* @return
*/
public int culculateTotal(List<Item> itemList) {
//Gesamtgebühr
int sum = 0;
//Der Steuersatz wird nacheinander berechnet und addiert
for (Item item : itemList) {
sum += item.getPrice() * (1 + taxRateMaster.getTaxRate(item.getItemType()));
}
return sum;
}
}
Klasse, die den zu verspottenden DB-Zugriff enthält
package com.example.mockito;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
/**
*Holen Sie sich den Steuersatz
*
* @author rcftdbeu
*
*/
@RequiredArgsConstructor
@Component
public class TaxRateMaster {
private final TaxRepository ripository;
/**
*Heutiges Datum
*
* @return
*/
public Date getSysDate() {
return new Date();
}
/**
*Liegt es nach dem Antragsdatum für ermäßigten Steuersatz?
*
* @return
*/
private boolean isAfterApplyDate() {
String strDate = "2019/10/1 00:00:00";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
try {
Date applyDate = dateFormat.parse(strDate);
return getSysDate().after(applyDate);
} catch (Exception e) {
}
return false;
}
/**
*Holen Sie sich den Steuersatz
*
* @param type
* @return
*/
public Double getTaxRate(ItemType type) {
//Nach Anwendung des ermäßigten Steuersatzes
if (isAfterApplyDate()) {
if (type == ItemType.Food) {
return ripository.getFoodTaxRate();
} else {
return ripository.getOtherTaxRate();
}
}
//Vor Anwendung des ermäßigten Steuersatzes
return ripository.getOldTaxRate();
}
}
Repository
package com.example.mockito;
import org.springframework.stereotype.Repository;
/**
*Prozess zum Abrufen des Steuersatzes von der DB<br>
*Nicht implementiert, da DB nicht verwendet wird<br>
*Existiert nur, um Kompilierungsfehler zu vermeiden
*
* @author rcftdbeu
*
*/
@Repository
public class TaxRepository {
public Double getFoodTaxRate() {
return null;
}
public Double getOtherTaxRate() {
return null;
}
public Double getOldTaxRate() {
return null;
}
}
Unit Test
Endlich das Hauptthema.
In Mockito gibt es zwei Arten: mock
und spy
.
mock: Ein Mock, bei dem alle Methoden der Zielklasse durch "return null" ersetzt werden. Keine Initialisierung erforderlich. Spion: Muss mit demselben Objekt wie die Zielklasse initialisiert werden. Bei Bedarf können nur bestimmte Methoden geändert werden.
Mock
Wir werden einen Test mit Mock durchführen.
package com.example.mockito;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
public class MockTest {
private static POS pos;
private static TaxRateMaster master;
private static TaxRepository repository;
@Nested
Testen mit statischer Klasse Mock{
//Vor dem Testen nur einmal ausführen
@BeforeAll
static void init() {
master = mock(TaxRateMaster.class);
pos = new POS(master);
}
//Vor jedem Test ausführen
@BeforeEach
void setup() {
//Der Steuersatz für Lebensmittel beträgt 8%
when(master.getTaxRate(ItemType.Food)).thenReturn(0.08);
//Andere Steuersätze sind 10%
when(master.getTaxRate(ItemType.Other)).thenReturn(0.10);
}
@Test
void Berechneter reduzierter Steuersatz für Lebensmittel_Berechnet mit 8%() {
//Fügen Sie 100 Yen Reiskuchen hinzu
List<Item> itemList = new ArrayList<Item>();
Item onigiri = new Item("Reisbällchen", 100, ItemType.Food);
itemList.add(onigiri);
//Berechnung
int sum = pos.culculateTotal(itemList);
//Überprüfen Sie das Ergebnis
assertThat(sum, is(108));
}
@Test
nichtig Sonstige Berechnung des ermäßigten Steuersatzes_Berechnet mit 10%() {
//500 Yen Magazin hinzugefügt
List<Item> itemList = new ArrayList<Item>();
Item onigiri = new Item("Zeitschrift", 500, ItemType.Other);
itemList.add(onigiri);
//Berechnung
int sum = pos.culculateTotal(itemList);
//Überprüfen Sie das Ergebnis
assertThat(sum, is(550));
}
}
}
init()
Der Mock wird initialisiert. Der TaxRateMaster, von dem der POS abhängt, wird durch einen Mock ersetzt. Die Struktur ist wie folgt. POS-Entity-TaxRateMaster-Mock
setup()
Es definiert das Verhalten des Mocks.
when ()
definiert, welche Methode aufgerufen wird und thenReturn ()
gibt was zurück.
Wenn Sie das Verhalten bei der Ausführung mit einem bestimmten Argument definieren möchten, geben Sie diesen Wert direkt an. Geben Sie anyString () oder any (ItemType.class) an, um das Verhalten bei Ausführung mit beliebigen Argumenten zu definieren.
Es gibt zwei Methoden: Die eine besteht darin, mit "when (). ThenReturn ()" zu schreiben, und die andere darin, mit "doReturn (). When ()" zu schreiben. Wenn Sie das Setup-Beispiel neu schreiben, lautet es "doReturn (0.08) .when (master) .getTaxRate (ItemType.Food);".
Was besser ist, ist umstritten.
when (). thenReturn () Fraktion --⭕️ Einfach in der gleichen Reihenfolge wie if-Anweisungen zu lesen -❌ Kann mit Spion nicht verfügbar sein --❌ thenThrow ist nicht für Methoden verfügbar, die void zurückgeben → Wenn Sie richtig entwerfen, haben Sie keine Möglichkeit, doReturn zu verwenden.
doReturn (). When () Fraktion --⭕️ Sie können es jederzeit verwenden, ohne sich darum kümmern zu müssen -❌ Der in doReturn () beschriebene Wert wird zum Objekttyp und der Compiler kann den Fehler nicht erkennen. → Es ist einfacher, sich mit doReturn zu vereinen, als sich an subtile Unterschiede zu erinnern, und Sie können dies herausfinden, indem Sie die Typprüfung ausführen.
Persönlich gefällt mir when (). ThenReturn (), was leicht zu lesen ist, daher verwende ich diese Notation auch in diesem Beitrag.
Es gibt nichts Besonderes. Erstellen Sie einfach eine Item-Instanz zum Testen und führen Sie culculateTotal aus, das getestet werden soll.
Spy
@Nested
Testen mit der statischen Klasse Spy{
//Vor dem Testen nur einmal ausführen
@BeforeAll
static void init() {
repository = mock(TaxRepository.class);
master = spy(new TaxRateMaster(repository));
pos = new POS(master);
}
//Vor jedem Test ausführen
@BeforeEach
void setup() {
//Der Steuersatz vor Anwendung des ermäßigten Steuersatzes beträgt 8%
when(repository.getOldTaxRate()).thenReturn(0.08);
//Der Lebensmittelsteuersatz beträgt 8%
when(repository.getFoodTaxRate()).thenReturn(0.08);
//Andere Steuersätze sind 10%
when(repository.getOtherTaxRate()).thenReturn(0.10);
}
@Nested
Klasse Vor Anwendung des ermäßigten Steuersatzes{
@BeforeEach
void setup() throws ParseException {
//Legen Sie fest, dass der Tag zurückgegeben wird, bevor der reduzierte Steuersatz angewendet wird, wenn das aktuelle Datum erfasst wird
String strDate = "2019/9/30";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
Date applyDate = dateFormat.parse(strDate);
when(master.getSysDate()).thenReturn(applyDate);
}
@Test
void Berechneter reduzierter Steuersatz für Lebensmittel_Berechnet mit 8%() {
//Fügen Sie 100 Yen Reiskuchen hinzu
List<Item> itemList = new ArrayList<Item>();
Item onigiri = new Item("Reisbällchen", 100, ItemType.Food);
itemList.add(onigiri);
//Berechnung
int sum = pos.culculateTotal(itemList);
//Überprüfen Sie das Ergebnis
assertThat(sum, is(108));
}
@Test
nichtig Sonstige Berechnung des ermäßigten Steuersatzes_Berechnet mit 8%() {
//500 Yen Magazin hinzugefügt
List<Item> itemList = new ArrayList<Item>();
Item onigiri = new Item("Zeitschrift", 500, ItemType.Other);
itemList.add(onigiri);
//Berechnung
int sum = pos.culculateTotal(itemList);
//Überprüfen Sie das Ergebnis
assertThat(sum, is(540));
}
}
@Nested
Klasse Nach Anwendung des ermäßigten Steuersatzes{
@BeforeEach
void setup() throws ParseException {
//Legen Sie fest, dass der Tag zurückgegeben wird, bevor der reduzierte Steuersatz angewendet wird, wenn das aktuelle Datum erfasst wird
String strDate = "2019/10/1 00:00:01";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date applyDate = dateFormat.parse(strDate);
when(master.getSysDate()).thenReturn(applyDate);
}
@Test
void Berechneter reduzierter Steuersatz für Lebensmittel_Berechnet mit 8%() {
//Fügen Sie 100 Yen Reiskuchen hinzu
List<Item> itemList = new ArrayList<Item>();
Item onigiri = new Item("Reisbällchen", 100, ItemType.Food);
itemList.add(onigiri);
//Berechnung
int sum = pos.culculateTotal(itemList);
//Überprüfen Sie das Ergebnis
assertThat(sum, is(108));
}
@Test
nichtig Sonstige Berechnung des ermäßigten Steuersatzes_Berechnet mit 10%() {
//500 Yen Magazin hinzugefügt
List<Item> itemList = new ArrayList<Item>();
Item onigiri = new Item("Zeitschrift", 500, ItemType.Other);
itemList.add(onigiri);
//Berechnung
int sum = pos.culculateTotal(itemList);
//Überprüfen Sie das Ergebnis
assertThat(sum, is(550));
}
}
}
init()
Spion initialisieren. Setzen Sie TaxRateMaster, von dem der POS abhängt, auf Spy und setzen Sie ihn auf Spy. Die Struktur ist wie folgt. POS-Entity-TaxRateMasters Spy-TaxRepository-Mock
setup()
Nur der Teil, der das aktuelle Datum in Spy of TaxRateMaster erfasst, wird durch das definierte Datum ersetzt.
Es ist dasselbe wie im Fall von Mock, und es gibt keine besondere Erklärung.
when(master.getTaxRate(ItemType.Food)).thenReturn(0.08)
.thenReturn(0.10);
Durch Verbinden von "thenReturn" können beim ersten und zweiten Aufruf unterschiedliche Werte zurückgegeben werden. Das erste Mal kann für die Registrierung und das zweite Mal für die Aktualisierung verwendet werden.
verify(master).getTaxRate(ItemType.Food);
Sie können überprüfen, ob die Mock-Methode aufgerufen wurde, indem Sie "verify" verwenden. Sie können auch überprüfen, ob das Argument zum Zeitpunkt der Ausführung der Methode mit dem angegebenen Argument ausgeführt wird.
@BeforeEach
void setup() {
clearInvocations(master);
}
Es gibt nur eine Scheininstanz, und wenn sie aus mehreren Tests ausgeführt wird, wird die Anzahl der Aufrufe in anderen Tests bei der Überprüfung mit "verify" gezählt. Verwenden Sie daher vor der Ausführung des Tests "clearInvocations", um die Anzahl der Scheinaufrufe zu zählen. Sie müssen es zurücksetzen.
verify(master, times(2)).getTaxRate(ItemType.Food);
Sie können überprüfen, ob die Methode die angegebene Anzahl von Malen aufgerufen wurde, indem Sie dem Argument von "verify" "times" hinzufügen.
verify(master, never()).getTaxRate(ItemType.Food);
Sie können überprüfen, ob die Methode nicht aufgerufen wird, indem Sie dem Argument von "verify" "never" hinzufügen.
Gleiches gilt für times (0)
.
Wenn das Argument der Methode, die Sie überprüfen möchten, ein primitiver Typ wie String oder Boolean ist, können Sie es nach der Überprüfung mit dem Argument der Methode überprüfen, jedoch für andere Objekte. Sie können das Argument mit ArgumentCaptor abrufen.
@Test
Holen Sie sich nichtiges Argument() {
//Generieren Sie ArgumentCaptor, um Argumente abzurufen
ArgumentCaptor<ItemType> argCaptor = ArgumentCaptor.forClass(ItemType.class);
//Fügen Sie 100 Yen Reiskuchen hinzu
List<Item> itemList = new ArrayList<Item>();
itemList.add(new Item("Reisbällchen", 100, ItemType.Food));
//Berechnungsausführung
pos.culculateTotal(itemList);
//Argumente abrufen
verify(master).getTaxRate(argCaptor.capture());
ItemType executedItemType = argCaptor.getValue();
//Überprüfen Sie die Laufzeitargumente
assertThat(executedItemType, is(ItemType.Food));
}
@Test
void Ausnahme aufgetreten() {
when(master.getTaxRate(ItemType.Food)).thenThrow(new RuntimeException("DB-Verbindungsfehler"));
//Fügen Sie 100 Yen Reiskuchen hinzu
List<Item> itemList = new ArrayList<Item>();
itemList.add(new Item("Reisbällchen", 100, ItemType.Food));
//Berechnung
assertThrows(RuntimeException.class, () -> pos.culculateTotal(itemList));
//Deaktivieren Sie dann Einstellungen werfen
reset(master);
}
Sie können eine Ausnahme auslösen, indem Sie "thenThrow" anstelle von "thenReturn" setzen. Ausnahmen können nur für Ausnahmen festgelegt werden, die von der entsprechenden Methode ausgelöst werden. Da bei dieser Methode keine Ausnahme festgelegt ist, wird "RuntimeException" ausgelöst. Außerdem möchte ich in anderen Tests keine Ausnahme auslösen, daher habe ich sie mit "Zurücksetzen" gelöscht.
Standardmäßig kann es nicht verspottet werden. Die statische Methode ist nicht mit mock kompatibel. Daher sollten Sie sie nach Möglichkeit nicht statisch machen. Es scheint, dass es mit Power Mock in Mock konvertiert werden kann, aber ich wusste nicht, wie ich es mit JUnit5 bedienen soll.
Dies kann auch nicht verspottet werden. Private Methoden sollten mit öffentlichen Methoden getestet werden können. Implementieren Sie daher Tests für öffentliche Methoden. Wenn Sie wirklich wollen, müssen Sie Power Mock verwenden oder schwarze Magie wie Reflexion ausprobieren.
Recommended Posts