Holen Sie sich die UMFANGREICHSTE Abdeckung für die Einhaltung von MISRA C! Erfahren Sie mehr >>

Lieben Sie Frühlingstests noch mehr mit Mocking und Unit Test Assistant

Von Brian McGlauflin

12. Dezember 2017

7  min lesen

Dieses Frühling Rahmen (zusammen mit Spring Boot) bietet ein hilfreiches Testframework zum Schreiben von JUnit-Tests für Ihre Spring Controller.

In meinem previous postWir haben darüber gesprochen, wie diese Tests mit Parasoft Jtests effizient erstellt und verbessert werden können Unit-Test-Assistent. In diesem Beitrag werde ich mich weiterhin mit einer der größten Herausforderungen beim Testen komplexer Anwendungen befassen: Abhängigkeitsmanagement.

Warum muss ich mich verspotten?

Lass uns ehrlich sein. Komplexe Anwendungen werden nicht von Grund auf neu erstellt. Sie verwenden Bibliotheken, APIs und Kernprojekte oder -dienste, die von einer anderen Person erstellt und verwaltet werden. Als Spring-Entwickler nutzen wir vorhandene Funktionen so weit wie möglich, damit wir unsere Zeit und Mühe auf das verwenden können, was uns wichtig ist: die Geschäftslogik unserer Anwendung. Wir überlassen die Details den Bibliotheken, sodass unsere Anwendungen viele Abhängigkeiten aufweisen, die unten in Orange dargestellt sind:

Abb. 1. Ein Spring-Service mit mehreren Abhängigkeiten

Wie konzentriere ich Unit-Tests auf meine Anwendung (Controller und Service), wenn der größte Teil ihrer Funktionalität vom Verhalten dieser Abhängigkeiten abhängt? Führe ich am Ende nicht immer Integrationstests statt Unit-Tests durch? Was ist, wenn ich eine bessere Kontrolle über das Verhalten dieser Abhängigkeiten benötige oder die Abhängigkeiten während des Komponententests nicht verfügbar sind?

Verbessern Sie Unit-Tests für Java mit Automatisierung: Best Practices für Java-Entwickler

Was ich brauche, ist eine Möglichkeit, meine Anwendung von diesen Abhängigkeiten zu isolieren, damit ich meine Komponententests auf meinen Anwendungscode konzentrieren kann. In einigen Fällen könnten wir spezielle Testversionen dieser Abhängigkeiten erstellen. Die Verwendung einer standardisierten Bibliothek wie Mockito bietet jedoch aus mehreren Gründen Vorteile gegenüber diesem Ansatz:

  • Sie müssen den speziellen Testcode nicht selbst schreiben und pflegen
  • Mocking-Bibliotheken können Aufrufe anhand von Mocks verfolgen und bieten so eine zusätzliche Validierungsebene
  • Standardbibliotheken wie PowerMock bieten zusätzliche Funktionen wie das Verspotten statischer Methoden, privater Methoden oder Konstruktoren
  • Das Wissen über eine Spottbibliothek wie Mockito kann projektübergreifend wiederverwendet werden, während das Wissen über benutzerdefinierten Testcode nicht wiederverwendet werden kann

Abb. 2. Ein verspotteter Dienst ersetzt mehrere Abhängigkeiten

Abhängigkeiten im Frühjahr

Im Allgemeinen teilen Spring-Anwendungen die Funktionalität in Beans auf. Ein Controller hängt möglicherweise von einer Service Bean ab, und die Service Bean hängt möglicherweise von einem EntityManager, einer JDBC-Verbindung oder einer anderen Bean ab. In den meisten Fällen handelt es sich bei den Abhängigkeiten, von denen der zu testende Code isoliert werden muss, um Beans. In einem Integrationstest ist es sinnvoll, dass alle Ebenen real sein sollten - aber für Unit-Tests müssen wir entscheiden, welche Abhängigkeiten real und welche verspottet sein sollen.

Mit Spring können Entwickler Beans entweder mit XML, Java oder einer Kombination aus beiden definieren und konfigurieren, um eine Mischung aus verspotteten und echten Beans in Ihrer Konfiguration bereitzustellen. Da Mock-Objekte in Java definiert werden müssen, sollte eine Konfigurationsklasse verwendet werden, um die Mock-Beans zu definieren und zu konfigurieren.

Abhängigkeiten verspotten

Wenn UTA einen Spring-Test generiert, werden alle Abhängigkeiten für Ihren Controller als Mocks eingerichtet, sodass jeder Test die Kontrolle über die Abhängigkeit erlangt. Wenn der Test ausgeführt wird, erkennt UTA Methodenaufrufe, die an einem Scheinobjekt für Methoden ausgeführt wurden, für die noch keine Methodenverspottung konfiguriert ist, und empfiehlt, diese Methoden zu verspotten. Wir können dann eine Schnellkorrektur verwenden, um jede Methode automatisch zu verspotten.

Hier ist ein Beispiel-Controller, der von a abhängt PersonenService:

@Controller
@RequestMapping("/people")
public class PeopleController {
 
    @Autowired
    protected PersonService personService;
    @GetMapping
    public ModelAndView people(Model model){
   
        for (Person person : personService.getAllPeople()) {
            model.addAttribute(person.getName(), person.getAge());
        }
        return new ModelAndView("people.jsp", model.asMap());
    }
}

 

Und ein Beispieltest, der vom Unit Test Assistant von Parasoft Jtest erstellt wurde:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class PeopleControllerTest {
 
    @Autowired
    PersonService personService;
 
    // Other fields and setup
 
    @Configuration
    static class Config {
 
        // Other beans
 
        @Bean
        public PersonService getPersonService() {
            return mock(PersonService.class);
        }
    }
 
    @Test
    public void testPeople() throws Exception {
        // When
        ResultActions actions = mockMvc.perform(get("/people"));
    }
}

 

Hier verwendet der Test eine innere Klasse, die mit annotiert ist @AufbauHiermit werden Bean-Abhängigkeiten für den zu testenden Controller mithilfe der Java-Konfiguration bereitgestellt. Dies ermöglicht es uns, das zu verspotten PersonenService in der Bohnenmethode. Es sind noch keine Methoden verspottet. Wenn ich den Test durchführe, wird folgende Empfehlung angezeigt:

Dies bedeutet, dass die getAllPeople () Methode wurde auf meine verspottete aufgerufen PersonenService, aber der Test konfiguriert noch keine Verspottung für diese Methode. Wenn ich die Quickfix-Option "Mock it" wähle, wird der Test aktualisiert:

    @Test
    public void testPeople() throws Exception {
        Collection<Person> getAllPeopleResult = new ArrayList<Person>();
        doReturn(getAllPeopleResult).when(personService).getAllPeople();
        // When
        ResultActions actions = mockMvc.perform(get("/people"));

Wenn ich den Test erneut durchführe, besteht er. Ich sollte immer noch die bevölkern Kollektion das wird zurückgegeben von getAllPeople (), aber die Herausforderung, meine verspotteten Abhängigkeiten einzurichten, ist gelöst.

Beachten Sie, dass ich die generierte Verspottungsmethode von der Testmethode in die Bean-Methode der Konfigurationsklasse verschieben könnte. Wenn ich das mache, bedeutet das, dass jeder Test in der Klasse dieselbe Methode auf dieselbe Weise verspottet. Wenn Sie die Methode in der Testmethode verspotten lassen, kann die Methode zwischen verschiedenen Tests unterschiedlich verspottet werden.

Frühlingsstiefel

Spring Boot macht das Verspotten von Bohnen noch einfacher. Anstatt eine zu verwenden @Autowired Feld für die Bean im Test und eine Konfigurationsklasse, die sie definiert. Sie können einfach ein Feld für die Bean verwenden und mit Anmerkungen versehen @ MockBean. Spring Boot erstellt mithilfe des im Klassenpfad gefundenen Verspottungsframeworks ein Mock für die Bean und injiziert es auf dieselbe Weise, wie jede andere Bean im Container injiziert werden kann.

Beschleunigen Sie den Komponententest von Federanwendungen mit Parasoft Jtest und seinem Unit Test Assistant

Beim Generieren von Spring Boot-Tests mit dem Unit Test Assistant wird der @ MockBean Die Funktionalität wird anstelle der Konfigurationsklasse verwendet.

@SpringBootTest
@AutoConfigureMockMvc
public class PeopleControllerTest {
    // Other fields and setup – no Configuration class needed!
 
    @MockBean
    PersonService personService;
 
    @Test
    public void testPeople() throws Exception {
        ...
    }
}

XML vs Java-Konfiguration

Im ersten Beispiel oben hat die Konfigurationsklasse alle Beans für den Spring-Container bereitgestellt. Alternativ können Sie anstelle der Konfigurationsklasse die XML-Konfiguration für den Test verwenden. oder Sie können die beiden kombinieren. Zum Beispiel:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:/**/testContext.xml" })
public class PeopleControllerTest {
 
    @Autowired
    PersonService personService;
 
    // Other fields and setup
 
    @Configuration
    static class Config {
        @Bean
        @Primary
        public PersonService getPersonService() {
            return mock(PersonService.class);
        }
    }
 
    // Tests
}

 

Hier verweist die Klasse auf eine XML-Konfigurationsdatei in der @ContextConfiguration Anmerkung (hier nicht gezeigt), um die meisten Bohnen bereitzustellen, bei denen es sich um echte Bohnen oder testspezifische Bohnen handeln kann. Wir bieten auch eine @Aufbau Klasse, wo PersonenService wird verspottet. Das @Primär Anmerkung gibt an, dass auch wenn a PersonenService Bean befindet sich in der XML-Konfiguration. Bei diesem Test wird die verspottete Bean aus dem verwendet @Aufbau Klasse stattdessen. Diese Art der Konfiguration kann das Testen von Code verkleinern und die Verwaltung vereinfachen.

Sie können UTA so konfigurieren, dass Tests mit einer bestimmten Methode generiert werden @ContextConfiguration Attribute, die Sie benötigen.

Verspotten statischer Methoden

Manchmal wird statisch auf Abhängigkeiten zugegriffen. Beispielsweise kann eine Anwendung auf eine 3 zugreifenrd-Party-Service durch einen statischen Methodenaufruf:

public class ExternalPersonService {
    public static Person getPerson(int id) {
       RestTemplate restTemplate = new RestTemplate();
       try {
           Rückkehr restTemplate.getForObject("http://domain.com/people/" + id, Person.class);
        } catch (RestClientException e) {
            return null;
        }
    }
}

 

In unserem Controller:

    @GetMapping
    public ResponseEntity<Person> getPerson(@PathVariable("id") int id, Model Modell)
    {
        Person person = ExternalPersonService.getPerson(id);
        if (person != null) {
            return new ResponseEntity<Person>(person, HttpStatus.OK);
        }
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }

 

In diesem Beispiel verwendet unsere Handlermethode einen statischen Methodenaufruf, um ein Personenobjekt von einer 3 abzurufenrd-Partei-Service. Wenn wir einen JUnit-Test für diese Handler-Methode erstellen, wird bei jeder Testausführung ein echter HTTP-Aufruf an den Dienst gesendet.

Verspotten wir stattdessen die Statik ExternalPersonService.getPerson () Methode. Dies verhindert den HTTP-Aufruf und ermöglicht es uns, eine Person Objektantwort, die unseren Testanforderungen entspricht. Der Unit Test Assistant kann es einfacher machen, statische Methoden mit PowerMockito zu verspotten.

UTA generiert einen Test für die oben beschriebene Handler-Methode, der folgendermaßen aussieht:

    @Test
    public void testGetPerson() throws Throwable {
        // When
        long id = 1L;
        ResultActions actions = mockMvc.perform(get("/people/" + id));
 
        // Then
        actions.andExpect(status().isOk());
    }

 

Wenn wir den Test ausführen, wird der HTTP-Aufruf im UTA-Flow-Baum angezeigt. Lassen Sie uns den Anruf zu finden ExternalPersonService.getPerson () und verspotten Sie es stattdessen:

 

 

Der Test wird aktualisiert, um die statische Methode für diesen Test mit PowerMock zu verspotten:

    @Test
    public void testGetPerson() throws Throwable {
        spy(ExternalPersonService.class);
 
        Person getPersonResult = null; // UTA: default value
        doReturn(getPersonResult).when(ExternalPersonService.class, "getPerson", anyInt());
 
        // When
        int id = 0;
        ResultActions actions = mockMvc.perform(get("/people/" + id));
 
        // Then
        actions.andExpect(status().isOk());
    }

 

Mit UTA können wir nun die auswählen getPersonErgebnis variiere und instanziiere es, damit der verspottete Methodenaufruf nicht zurückkehrt null:

    String name = ""; // UTA: default value
    int age = 0; // UTA: default value
    Person getPersonResult = new Person(name, age);

 

Wenn wir den Test erneut ausführen, getPersonErgebnis wird von der verspotteten zurückgegebenExternalPersonService.getPerson () Methode, und der Test besteht.

Hinweis: Im Flow-Baum können Sie auch "Mockable Method Pattern hinzufügen" für statische Methodenaufrufe auswählen. Dadurch wird Unit Unit Assistant so konfiguriert, dass diese statischen Methodenaufrufe beim Generieren neuer Tests immer verspottet werden.

Fazit

Komplexe Anwendungen haben oft funktionale Abhängigkeiten, die die Fähigkeit eines Entwicklers, seinen Code zu testen, erschweren und einschränken. Die Verwendung eines Mocking-Frameworks wie Mockito kann Entwicklern dabei helfen, den zu testenden Code von diesen Abhängigkeiten zu isolieren, sodass sie schneller bessere Unit-Tests schreiben können. Unit-Testing-Tool von Parasoft Vereinfacht das Abhängigkeitsmanagement, indem neue Tests für die Verwendung von Mocks konfiguriert werden und fehlende Methoden-Mocks zur Laufzeit gefunden werden und Entwickler dabei unterstützt werden, Mocks für sie zu generieren.

Automatisieren Sie die Erstellung von Junit-Tests mit Parasoft Jtest und lieben Sie Unit-Tests.
Fordern Sie jetzt eine Demo an

Von Brian McGlauflin

Brian McGlauflin ist Softwareentwickler bei Parasoft und verfügt über Erfahrung in der Full-Stack-Entwicklung mit Spring und Android, API-Tests und Service-Virtualisierung. Derzeit konzentriert er sich auf automatisierte Softwaretests für Java-Anwendungen mit Parasoft Jtest.

Erhalten Sie die neuesten Nachrichten und Ressourcen zum Testen von Software sofort.