Java >> Java Tutorial >  >> Tag >> Spring

Integrationstests in der Spring Boot-Anwendung

In diesem Beitrag werde ich zeigen, wie wir Integrationstests zu einer Spring Boot-Anwendung hinzufügen können.

Integrationstests spielen eine Schlüsselrolle, um die Qualität der Anwendung sicherzustellen. Mit einem Framework wie Spring Boot lassen sich solche Tests noch einfacher integrieren. Dennoch ist es wichtig, Anwendungen mit Integrationstests zu testen, ohne sie auf dem Anwendungsserver bereitzustellen.

Integrationstests können helfen, die Datenzugriffsschicht Ihrer Anwendung zu testen. Integrationstests helfen auch, mehrere Units zu testen. Für die Spring Boot-Anwendung müssen wir eine Anwendung in ApplicationContext ausführen Tests durchführen zu können. Integrationstests können beim Testen der Ausnahmebehandlung helfen.

Spring Boot-Anwendung

Für diese Demo erstellen wir eine einfache Spring Boot-Anwendung mit REST-APIs. Wir werden die H2 In-Memory-Datenbank zum Speichern der Daten verwenden. Schließlich werde ich zeigen, wie man einen Integrationstest schreibt. Diese Anwendung liest eine JSON-Datei mit Schwachstellen aus der National Vulnerability Database und speichert sie in der H2-Datenbank. REST-APIs ermöglichen es einem Benutzer, diese Daten in einem besser lesbaren Format abzurufen.

Abhängigkeiten

Zuerst möchten wir Integrationstests in dieser Anwendung erstellen, also müssen wir die Abhängigkeit spring-boot-starter-test einschließen .


dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'junit:junit:4.13.1'
	runtimeOnly 'com.h2database:h2:1.4.200'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Diese Abhängigkeit von spring-boot-starter-test Erlauben Sie uns, testbezogene Anmerkungen hinzuzufügen, die wir bald sehen werden.

REST-API

Wie ich bereits sagte, werden wir eine REST-API haben, um Daten aus nationalen Schwachstellendatenbanken abzurufen. Wir werden einen REST-Controller mit zwei APIs erstellen, eine zum Abrufen einer Liste von Schwachstellen und eine zum Abrufen einer Schwachstelle nach CVE-ID.


@RestController
@RequestMapping("/v1/beacon23/vulnerabilities")
public class CveController
{

    @Autowired
    private CveService cveService;

    @GetMapping("/list")
    public List getAllCveItems(@RequestParam(required = false, name="fromDate") String fromDate, @RequestParam(required = false, name=
            "toDate") String toDate)
    {
        List cveDTOList = cveService.getCveItems(fromDate, toDate);

        if(cveDTOList == null || cveDTOList.isEmpty())
        {
            return new ArrayList<>();
        }
        else
        {
            return cveDTOList;
        }
    }

    @GetMapping
    public ResponseEntity getCveItemById(@RequestParam("cveId") String cveId)
    {
        CveDTO cveDTO = cveService.getCveItemByCveId(cveId);

        if(cveDTO != null)
        {
            return new ResponseEntity<>(cveDTO, HttpStatus.OK);
        }
        else
        {
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        }
    }

}

Also haben wir

  • /v1/beacon23/vulnerabilities/list – zum Abrufen einer Liste von Schwachstellen
  • /v1/beacon23/vulnerabilities?cveId=value – zum Abrufen der Schwachstelle nach CVE-ID.

Dienst

Jetzt findet der Großteil der Geschäftslogik und -validierung in der Serviceklasse statt. Wie wir in unserer API gesehen haben, verwenden wir CVEService um die erforderlichen Daten abzurufen.

    @Autowired
    public CveDataDao cveDataDao;

    public List getCveItems(String from, String to)
    {
        LOGGER.debug("The date range values are from = {} and to = {}", from, to);
        List cveDataList = cveDataDao.findAll();
        List cveDTOList = new ArrayList<>();

        for(CveData cveData : cveDataList)
        {
            List cveList = cveData.getCveItems();
            for(CveItem cveItem: cveList)
            {
                Date fromDate;
                Date toDate;

                if(!isNullOrEmpty(from) && !isNullOrEmpty(to))
                {
                    fromDate = DateUtil.formatDate(from);
                    toDate = DateUtil.formatDate(to);

                    Date publishedDate = DateUtil.formatDate(cveItem.getPublishedDate());

                    if(publishedDate.after(toDate) || publishedDate.before(fromDate))
                    {
                        continue;
                    }
                }
                CveDTO cveDTO = convertCveItemToCveDTO(cveItem);
                cveDTOList.add(cveDTO);
            }
        }
        return cveDTOList;
    }

    private boolean isNullOrEmpty (String str)
    {
        return (str == null || str.isEmpty());
    }

    private String buildDescription (List descriptionDataList)
    {
        if(descriptionDataList == null || descriptionDataList.isEmpty())
        {
            return EMPTY_STRING;
        }
        else
        {
            return descriptionDataList.get(0).getValue();
        }
    }

    private List buildReferenceUrls (List referenceDataList)
    {
        return referenceDataList.stream().map(it -> it.getUrl()).collect(Collectors.toList());
    }

    public CveDTO getCveItemByCveId(String cveId)
    {
        List cveDataList = cveDataDao.findAll();
        CveDTO cveDTO = null;

        for(CveData cveData : cveDataList)
        {
            List cveItems = cveData.getCveItems();

            Optional optionalCveItem =
                    cveItems.stream().filter(ci -> ci.getCve().getCveMetadata().getCveId().equals(cveId)).findAny();
            CveItem cveItem = null;
            if(optionalCveItem.isPresent())
            {
                cveItem = optionalCveItem.get();
            }
            else
            {
                return cveDTO;
            }
            cveDTO = convertCveItemToCveDTO(cveItem);
        }

        return cveDTO;
    }

Verwendung von @SpringBootTest

Spring Boot stellt eine Anmerkung @SpringBootTest bereit die wir in Integrationstests verwenden können. Mit dieser Anmerkung können die Tests den Anwendungskontext starten, der alle Objekte enthalten kann, die wir zum Ausführen der Anwendung benötigen.

Integrationstests bieten ein fast produktionsähnliches Szenario zum Testen unseres Codes. Die Tests sind mit @SpringBootTest annotiert Erstellen Sie den in unseren Tests verwendeten Anwendungskontext durch die mit @SpringBootConfiguration kommentierte Anwendungsklasse .

Diese Tests starten einen eingebetteten Server, erstellen eine Webumgebung und führen dann @Test aus Methoden zur Durchführung von Integrationstests. Wir müssen einige Attribute hinzufügen, um sicherzustellen, dass wir die Webumgebung starten können, während wir @SpringBootTest verwenden .

  • Attribut webEnvironment – So erstellen Sie eine Webumgebung mit einem Standardport oder einem zufälligen Port.

Wir können auch Eigenschaften übergeben, die für Tests mit einem aktiven Profil verwendet werden. Normalerweise verwenden wir diese Profile für verschiedene Umgebungen, aber wir können auch ein spezielles Profil nur für Tests verwenden. Wir erstellen application-dev.yml , application-prod.yml Profile. Auf ähnliche Weise können wir application-test.yml erstellen und verwenden Sie die Anmerkung @ActiveProfiles('test') in unseren Tests.

Beispiel für Integrationstest

Für unsere REST-API werden wir einen Integrationstest erstellen, der unseren Controller testet. Wir werden auch TestRestTemplate verwenden Daten zu holen. Dieser Integrationstest sieht wie folgt aus:


package com.betterjavacode.beacon23.tests;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertNotNull;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CveControllerTest
{
    @LocalServerPort
    private int port;

    TestRestTemplate testRestTemplate = new TestRestTemplate();

    HttpHeaders headers = new HttpHeaders();

    @Test
    public void testGetAllCveItems()
    {
        HttpEntity entity = new HttpEntity<>(null, headers);

        ResponseEntity responseEntity = testRestTemplate.exchange(createURLWithPort(
                "/v1/beacon23/vulnerabilities/list"),HttpMethod.GET, entity, String.class);

        assertNotNull(responseEntity);

    }


    private String createURLWithPort(String uri)
    {
        return "http://localhost:" + port + uri;
    }
}

Wir verwenden @SpringBootTest Anmerkung für unsere Testklasse und richten den Anwendungskontext mit webEnvironment ein mit einem RANDOM_PORT. Wir mocken auch den lokalen Webserver, indem wir einen Scheinport mit @LocalServerPort einrichten .

TestRestTemplate ermöglicht es uns, einen Client zu simulieren, der unsere API aufruft. Nachdem wir diesen Test ausgeführt haben (entweder durch gradle build ODER über IntelliJ), sehen wir, wie das Spring Boot-Anwendungskontext-Setup ausgeführt wird und die Anwendung an einem zufälligen Port ausgeführt wird.

Ein Nachteil beim Erstellen von Integrationstests mit @SpringBootTest ist, dass es die Erstellung Ihrer Anwendung verlangsamt. In den meisten Unternehmensumgebungen wird dies durch Continuous Integration und Continuous Deployment eingerichtet. In solchen Szenarien verlangsamt es den Integrations- und Bereitstellungsprozess, wenn Sie viele Integrationstests durchführen.

Schlussfolgerung

Schließlich sollten Sie Integrationstests in der Spring Boot-Anwendung verwenden oder nicht, dies hängt von Ihrer Anwendung ab. Aber trotz des Nachteils ist es immer nützlich, Integrationstests zu haben, die es ermöglichen, mehrere Einheiten gleichzeitig zu testen. @SpringBootTest ist eine praktische Anmerkung, die zum Einrichten eines Anwendungskontexts verwendet werden kann, sodass wir Tests in der Nähe einer Produktionsumgebung ausführen können.

Referenzen

  1. Integrationstests mit Spring Boot – Integrationstests

Java-Tag