it-swarm.com.de

Spring Data: Testen der Service-Layer-Einheit

In meinem Projekt habe ich Probleme beim Komponententest. Ein Problem ist, dass das Durchführen eines Integrationstests viel schneller zu schreiben ist als auch, dass die Komponenten tatsächlich zusammenarbeiten. Unit-Testing neuartiger "Algorithmen" scheint viel einfacher zu sein. Unit Testing Service-Klassen fühlt sich einfach falsch und nutzlos an. 

Ich verwende Mockito, um das Frühjahrsdaten-Repository (und damit den DB-Zugriff) zu simulieren. Die Sache ist, wenn ich dem verspotteten Repository sage, Entität A mit dem Methodenaufruf getById zurückzugeben, wird dies offensichtlich zurückgegeben und der Dienst wird es ebenfalls zurückgeben. Ja, der Dienst leistet einige Extras, aber sehr kleine Dinge, wie das Laden von Lazy-Collections (aus dem Ruhezustand). Natürlich habe ich keine faulen Sammlungen (Proxies) in einem Unit-Test.

Beispiel:

@Test
public void testGetById() {
    System.out.println("getById");
    TestCompound expResult = new TestCompound(id, "Test Compound", "9999-99-9", null, null, null);

    TestCompoundRepository mockedRepository = mock(TestCompoundRepository.class);
    when(mockedRepository.findOne(id)).thenReturn(expResult);

    ReflectionTestUtils.setField(testCompoundService, "testCompoundRepository",
            mockedRepository, TestCompoundRepository.class);

    TestCompound result = testCompoundService.getById(id);
    assertEquals(expResult, result);
}

hurra, der Rest gelingt. Was für eine Überraschung! Nicht wirklich, nein. 

Kann mir jemand erklären, was ich falsch mache? Oder was ist der Sinn eines solchen Tests? Ich meine, ich sage, expResult zurückzugeben und dann wird es zurückgegeben. Beeindruckend. Was für eine Überraschung! Fühlt sich an, als würde ich testen, ob Mockito funktioniert, und nicht mein Service.

BEARBEITEN:

Der einzige Vorteil, den ich sehe, wenn einige dumme Fehler waren, passiert, als wenn Sie eine unerwünschte Zeile dort belassen, die den Rückgabewert auf null oder ähnliches dumm setzt. Solche Fälle würden vom Komponententest erfasst. Trotzdem scheint das Verhältnis zwischen Belohnung und Aufwand schlecht zu sein.

20
beginner_

Die Frage ist vielleicht etwas alt, aber ich werde eine Antwort geben, falls jemand herüberkommt.

  • Ich benutze Mockito und JUnit.
  • AccountRepository ist ein reines Frühjahrsdaten-Repository, das JPARepository erweitert.
  • Konto ist eine reine JPA-Entität.

Zum Testen Ihrer Dienste und der Nachahmung von Spring Data-Repositorys benötigen Sie Folgendes.

package foo.bar.service.impl;

import foo.bar.data.entity.Account;
import foo.bar.data.repository.AccountRepository;
import foo.bar.service.AccountService;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class AccountServiceImplTest {

    @Mock
    private static AccountRepository accountRepository;

    @InjectMocks
    private static AccountService accountService = new AccountServiceImpl();

    private Account account;

    @Test
    public void testFindAccount() {

        Integer accountId = new Integer(1);

        account = new Account();
        account.setId(accountId);
        account.setName("Account name");
        account.setCode("Accont code");
        account.setDescription("Account description");

        Mockito.when(accountRepository.findOne(accountId)).thenReturn(account);

        Account retrivedAccount = accountService.findAccount(accountId);

        Assert.assertEquals(account, retrivedAccount);

    }

}
11
Desorder

Einer der Gründe, warum ich meine Spring Data-Repositories gerne teste, ist zu testen, dass ich meine JPA-Zuordnungen richtig definiert habe. Ich verwende kein spöttisches Framework für diese Tests. Ich verwende das Spring Test-Framework, das den Container tatsächlich bootstraps. Dadurch kann ich das eigentliche Repository automatisch in den Junit-Test einspeisen, damit ich Tests dagegen ausführen kann.

Ich stimme mit Ihren Gedanken überein, dass das Verspotten des Repositorys ziemlich nutzlos ist. Da Sie Spring verwenden, würde ich vorschlagen, das Spring Test-Framework zu nutzen, um echte Tests mit Ihren Repositorys durchzuführen, die für eine eingebettete Datenbank wie H2 auf einer mehr Unit-Test-basierten Art oder Ihre tatsächliche Datenbankimplementierung wie Oracle oder MySql ausgeführt werden können führen Sie einen Integrationstest durch. (Führen Sie diese für eine Kopie einer Entwicklungsdatenbank aus.) Bei diesen Tests werden Abweichungen in Ihren JPA-Zuordnungen und andere Elemente, z. B. falsche Kaskadeneinstellungen in der Datenbank, aufgedeckt.

Hier ist ein Beispiel eines meiner Tests auf GitHub. Beachten Sie, wie das Framework das Repository tatsächlich für den Test verwendet. Das Repository enthält auch ein Beispiel für die Konfiguration des Spring Test-Frameworks, das ich auch in diesem blog post demonstriert habe.

Zusammenfassend glaube ich nicht, dass Sie einen der Vorteile des Testens eines Repositorys, das ich besprochen habe, durch die Verwendung eines Mock des Repositorys erhalten.

Eine zusätzliche Anmerkung, die ich hinzufügen wollte, ist, dass Mocks nicht wirklich für die Verwendung in der eigentlichen Klasse gedacht sind. Sie dienen dazu, einer zu testenden Klasse die erforderlichen Abhängigkeiten zu geben.

8
Kevin Bowersox

Du bist genau richtig. Es ist ein klarer Komponententest. Und es wird niemals scheitern (also ist es nutzlos) Ich denke, Sie benötigen beim Integrationstest einen Test, um das real JPA-Repository mit real database (beispielsweise H2 im Speicher) zu testen (wie immer). 

Und es ist besser, Ihre Dienste (ihre Schnittstellen) zu testen. Wenn Sie nach einiger Zeit Ihren Speicher ändern (z. B. in Mongo), können Sie Ihre Servicetests verwenden, um sicherzustellen, dass alle Vorgänge wie zuvor ausgeführt werden.

Nach einiger Zeit werden Sie überrascht sein, wie viele Probleme mit DB\JPA (Einschränkungen, optimistische Sperren, verzögertes Laden, doppelte ID, einige Probleme mit dem Ruhezustand usw.) auftauchen.

Versuchen Sie auch, über Tests zu entwickeln - nicht nur einen Test nach der Implementierung. Anstatt vor der Erstellung einer neuen Methode im Service - erstellen Sie einen Test für sie, implementieren Sie die Service-Methode und erst nach einer erneuten Überprüfung in der realen Anwendung. Zumindest ist der Start des Tests viel schneller als ein Server.

Erstellen Sie also keine Tests, um viele davon zu haben. Finden Sie heraus, wie sie Ihnen helfen können.

Die Verwendung von Vorlagen für Repositorys ist keine gute Idee. Testen Sie, wie Ihre Dienste mit Hibernate\JPA\Database zusammenarbeiten. Der größte Teil der Probleme liegt in zwischen zwei Schichten .

1

Sie können diese Bibliothek verwenden: https://github.com/agileapes/spring-data-mock

Dadurch wird Ihr Repository simuliert, und Sie können benutzerdefinierte Funktionen für jede Methode sowie Ihre eigenen Abfragemethoden implementieren.

1
Milad Naseri

Angenommen, wir haben die untenstehende Klasse Service

@Service 
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

Test Klasse:

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {

       @Bean
       public EmployeeService employeeService() {
           return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

// write test cases here
}

Um die Service-Klasse zu überprüfen, muss eine Instanz der Service-Klasse erstellt und als @Bean verfügbar sein, damit @Autowire in unserer Testklasse verfügbar ist. Diese Konfiguration wird mit der Annotation @TestConfiguration erreicht.

Beim Scannen von Komponenten werden möglicherweise Komponenten oder Konfigurationen gefunden, die nur für bestimmte Tests erstellt wurden, die versehentlich überall abgerufen werden. Um dies zu verhindern, bietet Spring Boot die Annotation @TestConfiguration, die für Klassen in src/test/Java verwendet werden kann, um anzuzeigen, dass sie nicht durch Scannen erfasst werden sollen.

Interessant ist hier auch die Verwendung von @MockBean. Es erstellt ein Mock für das EmployeeRepository, mit dem der Aufruf an das eigentliche EmployeeRepository umgangen werden kann:

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

Nach dem Setup können wir unseren Service ganz einfach testen:

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);

    assertThat(found.getName())isEqualTo(name);
}

Weitere Informationen finden Sie unter: https://www.baeldung.com/spring-boot-testing

0

Sie können das Repository simulieren und dem Dienst hinzufügen. Dies ist der Weg. Wenn Sie den Dienst jedoch nur mit @Mock von Repositorys instanziieren, wäre es besser, wenn Sie die Repositorys als private final-Felder im Service definieren und einen Konstruktor aller Repositorys verwenden. Wenn Sie dem Service ein weiteres Repository hinzufügen, schlägt der Test fehl und Sie müssen ihn ändern. Dies ist der Zweck.

Stellen Sie sich diesen Service vor:

class OrderService {
    private final UserRepository userRepos;

    public OrderService(UserRepository userRepos) {
        this.userRepos = userRepos;
    }

    ...

}

Und dieser Test:

class OrderServiceTests {
    @Mock
    private UserRepository userRepos;

    private OrderService service;

    private OrderServiceTests() {
        this.service = new OrderService(this.userRepos);
    }
}

Wenn wir jetzt eine weitere Abhängigkeit zum Dienst hinzufügen:

class OrderService {
    private final UserRepository userRepos;
    private final AddressRepository addRepos;

    public OrderService(UserRepository userRepos, AddressRepository addRepos) {
        this.userRepos = userRepos;
        this.addRepos = addRepos;

    ...

}

Der vorherige Test schlägt fehl, da der Konstruktor geändert wurde. Wenn Sie @InjectMocks verwenden, wird dies nicht passieren. Die Injektion geschieht hinter dem Vorhang und wir wissen nicht, was passiert. Dies ist möglicherweise nicht wünschenswert.

Eine andere Sache ist, ich stimme nicht zu, dass der Integrationstest alle Fälle abdeckt, die von Unit-Tests abgedeckt werden. es kann aber nicht immer der fall sein. Sogar der Controller kann mit Mock-Einheiten getestet werden. Schließlich sollen alle Tests den gesamten Code abdecken, den wir geschrieben haben, also müssen sie feinkörnig sein. Stellen Sie sich vor, wenn wir TTD folgen und nur die Controller- und Service-Ebene abschließen: Wie gehen wir ohne Controller Unit-Tests vor?

0
WesternGun