使用Spring Boot + Tests +文档创建REST API [第04部分]
#网络开发人员 #测试 #java #junit

你好!

我回到本教程中,教您如何使用Spring Boot开发REST API。正如我之前说的,本教程分为5个部分,如下表所示:

部分 内容
第01部分 Application Startup
第02部分 Settings and Database
第03部分 API Implementation
第04部分 测试 +覆盖范围
第05部分 Swagger Documentation

可以预期,从这第四部分,您将学习以下内容:

  • 使用junit
  • 创建单位测试
  • 创建集成测试
  • 使用Fakers创建测试数据

请记住,在本教程中使用以下工具:

  • JDK 17
  • Smart
  • Maven
  • 失眠
  • mySQL数据库

步骤01-创建测试属性

通常,当我们必须测试应用程序时,我们会创建一个专用于测试的属性文件。这样做是为了建立更好的开发和测试环境。
因此,在resources内部,您可以创建一个名为application-test.properties的新文件,该文件将包含以下内容:


spring.datasource.url=jdbc:mysql://localhost:3307/small_library_test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

前三个项目是数据库连接的原因。请注意,已经为测试创建了一个新的数据库(small_library_test),但是如果需要,可以使用与开发的相同数据库。
最后一个物品是为了避免LazyInitializationException异常。

因此,当我们创建测试时,它们将使用此新文件中定义的属性。


步骤02-异常处理程序

默认情况下,Spring通过类处理一些例外,例如EntityNotFoundExceptionEmptyResultDataAccessException

当用户尝试在数据库中搜索一个不存在的对象时,就会发生第一种情况,例如,他们想找到一本ID等于9999999999的书(考虑此ID不具有的情况存在于数据库中)。默认情况下,它返回错误500(不良请求)以及异常主体。

但是,让我们想,在这种情况下,我们不想返回错误500,而是要返回错误404(找不到)。我们如何进行此修改?

这就是为什么有RestControllerAdvice,用于处理控制器中的异常。为了创建它,您必须转到src.main.java.com.simplelibrary.simplelibrary.API软件包,并创建一个名为exception的新软件包。在此内,创建一个名为ErrorHandler的类:


package com.simplelibrary.simplelibraryAPI.exception;

import jakarta.persistence.EntityNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.simplelibrary.simplelibraryAPI.dto.ErrorValidatorDTO;

@RestControllerAdvice
public class ErrorHandler {
    @ExceptionHandler({EntityNotFoundException.class, EmptyResultDataAccessException.class})
    public ResponseEntity error404(){
        return ResponseEntity.notFound().build();
    }


    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity error400(MethodArgumentNotValidException exception){
        var erros = exception.getFieldErrors();
        return ResponseEntity.badRequest().body(erros.stream().map(ErrorValidatorDTO::new).toList());
    }



}

为了使Spring认识到该类是REST控制器建议,您必须将@RestControllerAdvice注释放在类初始化之前。

对于您要处理的每个例外,您必须将其放入@ExceptionHandler注释中,说该例外将在下一个方法中处理。
请注意,error404方法将处理通常丢弃500错误并导致他们丢弃404错误的异常。

error400方法将处理异常方法ArgumentNotValidexception。例如,当用户尝试注册书籍而没有标题时,就会触发此异常。最初,他将收到的回应如下:


{
    "timestamp": "2023-01-27T19:04:03.372+00:00",
    "status": 400,
    "error": "Bad Request",
    "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity com.simplelibrary.simplelibraryAPI.controller.BookController.store(com.simplelibrary.simplelibraryAPI.dto.BookRequestDTO,org.springframework.web.util.UriComponentsBuilder) throws java.io.IOException: [Field error in object 'bookRequestDTO' on field 'title': rejected value [null]; codes [NotBlank.bookRequestDTO.title,NotBlank.title,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [bookRequestDTO.title,title]; arguments []; default message [title]]; default message [não deve estar em branco]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:144)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:181)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:148)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1010)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:913)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:884)\r\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:833)\r\n",
    "message": "Validation failed for object='bookRequestDTO'. Error count: 1",
    "errors": [
        {
            "codes": [
                "NotBlank.bookRequestDTO.title",
                "NotBlank.title",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "bookRequestDTO.title",
                        "title"
                    ],
                    "arguments": null,
                    "defaultMessage": "title",
                    "code": "title"
                }
            ],
            "defaultMessage": "must not be blank",
            "objectName": "bookRequestDTO",
            "field": "title",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "path": "/books"
}

意识到发送给用户的信息量,实际上我们只希望他们知道哪个字段是错误的,而错误消息是什么。因此,我们将通过DTO进行您的治疗。

src.main.java.com.simplelibrary.simplelibrary.API.dto软件包中,我们将创建一个名为ErrorValidatorDTO的记录:


package com.simplelibrary.simplelibraryAPI.dto;
import org.springframework.validation.FieldError;
public record ErrorValidatorDTO(String field, String message){
    public ErrorValidatorDTO(FieldError error){
        this(error.getField(), error.getDefaultMessage());
    }
}

从那里我们将获得错误对象,并仅返回用户必要的信息:


[
    {
        "field": "title",
        "message": "must not be blank"
    }
]



步骤03-书籍工厂

对于我们的测试,有必要创建几本具有不同信息的书籍。进行此手动工作有点疲惫,因此我们可以使用社区创建的机制。

我们将使用的机制之一是Faker。从中,我们将能够为字段生成随机值并创建我们的书对象。为此,您需要将以下依赖关系导入到pom.xml文件中:


<dependency>
   <groupId>com.github.javafaker</groupId>
   <artifactId>javafaker</artifactId>
   <version>1.0.2</version>
</dependency>

通过执行此操作,我们可以创建将负责创建书籍对象的BookFactory类(DAO和DTO请求模型):

src.main.java.com.simplelibrary.simplelibrary.API.factory包中,我们将创建一个名为BookFakerFactory的类:


package com.simplelibrary.simplelibraryAPI.factory;

import com.github.javafaker.Faker;
import com.simplelibrary.simplelibraryAPI.dto.BookRequestDTO;
import com.simplelibrary.simplelibraryAPI.model.Book;

public class BookFakerFactory {

    private Faker faker;

    public BookFakerFactory(){
        this.faker = new Faker();
    }

    public BookRequestDTO createBook(){
        var book = new BookRequestDTO(faker.book().title(), faker.book().author(),
                faker.number().randomDigit(), faker.number().digits(13), faker.number().randomDouble(1,1,10),
                faker.random().nextBoolean());

        return book;
    }

    public BookRequestDTO createBook(Book bookCreated){
        var book = new BookRequestDTO(bookCreated);
        return book;
    }

    public Book createBookModel(){
        var bookDTO = new BookRequestDTO(faker.book().title(), faker.book().author(),
                faker.number().randomDigit(), faker.number().digits(13), faker.number().randomDouble(1,1,10),
                faker.random().nextBoolean());

        var book = new Book(bookDTO);
        return book;
    }



    public BookRequestDTO createBookIncomplete(){
        var book = new BookRequestDTO(faker.book().title(), "",
                faker.number().randomDigit(), faker.number().digits(13), faker.number().randomDouble(1,1,10),
                faker.random().nextBoolean());

        return book;
    }

}


请注意,我们有4种方法,所有方法的目的是创建一个对象,该对象以后将用于测试。

  • createBook方法将创建一个将用于测试请求的BookRequestDTO对象。
  • createBook方法(带有参数)将从先前创建的书籍中创建一个BookRequestDTO对象。
  • createBookModel方法将创建一个Book对象。
  • createBookIncomplete方法将创建一个没有作者信息的BookRequestDTO对象。

请注意,所有方法均使用Faker库。


步骤04-单元测试

我们准备创建测试。如果您在test包中查看,已经有一个名为SimplelibraryApiApplicationTests的类:


package com.simplelibrary.simplelibraryAPI;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SimplelibraryApiApplicationTests {


    @Test
    void contextLoads() throws Exception {


    }

}


它将使用JUnit进行测试。

注意一些重要的注释:

  • @SpringBootTest表明该类将包含应测试的方法。
  • @Test表示该方法应作为测试运行。

因此我们可以创建第一个单元测试。这将测试我们存储库的某些功能:在src.main.test.java.com.simplelibrary.simplelibrary.API软件包内部,我们将创建一个称为unit的软件包,其中一类称为BookTest


package com.simplelibrary.simplelibraryAPI.unit;

import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import com.simplelibrary.simplelibraryAPI.service.BookService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;

@SpringBootTest
@ActiveProfiles("test")
public class BookTest {

    @Autowired
    private BookRepository repository;

    @Autowired
    private BookService service;

    private BookFakerFactory bookFakerFactory;

    public BookTest() {
        this.bookFakerFactory = new BookFakerFactory();
    }



}

注意@ActiveProfiles("test")注释。它用于通知Spring,我们正在使用application-test.properties文件中定义的属性。

我们可以考虑要完成的一些测试:

  • 测试成功创建一本书。

  • 测试要验证收集给定书的评级的服务是否正常工作(验证评级值是从现有书籍中填充的,并且不填充不存在的书籍)。

所有测试的课程看起来像这样:


package com.simplelibrary.simplelibraryAPI.unit;

import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import com.simplelibrary.simplelibraryAPI.service.BookService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.Assertions.*;
import java.io.IOException;

@SpringBootTest
@ActiveProfiles("test")
public class BookTest {

    @Autowired
    private BookRepository repository;

    @Autowired
    private BookService service;

    private BookFakerFactory bookFakerFactory;

    public BookTest() {
        this.bookFakerFactory = new BookFakerFactory();
    }

    @Test
    public void itShouldCreateABookSuccessfully(){
        var book = bookFakerFactory.createBookModel();
        repository.save(book);

        var bookFinder = repository.findBookByisbn(book.getIsbn());

        Assertions.assertThat(book).isEqualTo(bookFinder);
    }

    @Test
    public void itShouldFillTheRatingValueFromExistingBook() throws IOException {
        var book = bookFakerFactory.createBookModel();
        book.setTitle("Harry Potter and the Philosopher's Stone");

        var bookCreated = service.store(bookFakerFactory.createBook(book));

        assertThat(bookCreated.getRate()).isNotNull()
                .isNotZero()
                .isGreaterThan(Double.valueOf(0));

    }

    @Test
    public void itShouldNotFillTheRatingValueFromNonexistentBook() throws IOException {
        var book = bookFakerFactory.createBookModel();
        book.setTitle("blablablablablabla");

        var bookCreated = service.store(bookFakerFactory.createBook(book));

        assertThat(bookCreated.getRate()).isZero();

    }



}


注意Junit中存在的assertThat方法的使用,该方法允许检查值。


步骤05-集成测试

我们现在可以创建集成测试,该测试将使我们能够测试系统的路线。我们还将检查请求是否正确返回,以及HTTP代码是否符合期望。

src.main.test.java.com.simplelibrary.simplelibrary.API软件包中,我们将创建一个名为integration的软件包,其中一个名为BookControllerTest的类:


package com.simplelibrary.simplelibraryAPI.integration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.assertj.core.api.Assertions.*;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

@Autowired
    private MockMvc mockMvc;

    @Autowired
    private BookRepository repository;

    private BookFakerFactory bookFakerFactory;

    public BookControllerTest() {
        this.bookFakerFactory = new BookFakerFactory();
    }


} 

请注意使用新注释的使用:@AutoConfigureMockMvc从中我们将能够使用注射的MockMvc类向我们的服务器提出请求。

我们可以想到以下测试用例:

  • 验证如果一本书成功创建
  • 验证如果我们不发送所有必要字段(HTTP代码400)
  • 时未创建书籍
  • 验证是否成功列出了所有书籍
  • 验证单书的信息是否成功出现。
  • 验证书籍的信息已成功更改
  • 验证一本书的信息未能在未提交时成功更改。
  • 验证书是否成功删除。
  • 验证一本不存在的书没有被删除。

所有实施方法的类看起来像这样:


package com.simplelibrary.simplelibraryAPI.integration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.simplelibrary.simplelibraryAPI.factory.BookFakerFactory;
import com.simplelibrary.simplelibraryAPI.repository.BookRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.assertj.core.api.Assertions.*;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private BookRepository repository;

    private BookFakerFactory bookFakerFactory;

    public BookControllerTest() {
        this.bookFakerFactory = new BookFakerFactory();
    }

    @Test
    void testIfBookIsCreatedSuccessfully() throws Exception {
        var book = this.bookFakerFactory.createBook();

        var result = this.mockMvc.perform(post("/books")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(asJsonString(book)))
                .andExpect(status().isCreated())
                .andReturn();

        String content = result.getResponse().getContentAsString();
        assertNotNull(content);

    }

    @Test
    void testIfBookIsNotCreatedWithMissingAttributes() throws Exception{
        var book = this.bookFakerFactory.createBookIncomplete();
        this.mockMvc.perform(post("/books")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(asJsonString(book)))
                .andExpect(status().isBadRequest())
                .andReturn();

    }

    @Test
    void testIfAllBooksAreListedSuccessfully() throws Exception{
        this.mockMvc.perform(get("/books"))
                .andExpect(status().isOk())
                .andReturn();
    }

    @Test
    void testIfSingleBookInformationIsShown() throws  Exception {
        this.mockMvc.perform(get("/books"))
                .andExpect(status().isOk())
                .andReturn();
    }

    @Test
    void testIfBookIsUpdatedSuccessfully() throws Exception{
        var book = this.bookFakerFactory.createBookModel();
        var bookCreated = repository.save(book);

        book.setAuthor("New Author name");

        var book2 = this.bookFakerFactory.createBook(book);

        this.mockMvc.perform(put("/books/{id}",bookCreated.getId())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(asJsonString(book2)))
                .andExpect(status().isOk())
                .andReturn();

        var bookUpdated = repository.getReferenceById(bookCreated.getId());

        assertThat(bookUpdated.getId()).isEqualTo(bookCreated.getId());
        assertThat(bookUpdated.getAuthor()).isNotEqualTo(bookCreated.getAuthor());


    }

    @Test
    void testIfBookIsNotUpdatedSuccessfully() throws Exception{
        var book = this.bookFakerFactory.createBookModel();
        var bookCreated = repository.save(book);


        this.mockMvc.perform(put("/books/{id}",bookCreated.getId()))
                .andExpect(status().isBadRequest())
                .andReturn();


    }

    @Test
    void testIfBookIsDeletedSuccessfully() throws Exception{
        var book = this.bookFakerFactory.createBookModel();
        var bookCreated = repository.save(book);
        this.mockMvc.perform(delete("/books/"+bookCreated.getId()))
                .andExpect(status().isNoContent())
                .andReturn();
    }

    @Test
    void testIfBookIsNotDeletedSuccessfully() throws Exception{
        this.mockMvc.perform(delete("/books/999999"))
                .andExpect(status().isNotFound())
                .andReturn();

    }

    public static String asJsonString(final Object obj) {
        try {
            return new ObjectMapper().writeValueAsString(obj);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


    }
}


请注意使用asJsonString方法。它用于采用对象的所有属性值并将其转换为JSON字符串。


步骤06-运行测试

要运行测试,您必须转到终端并键入以下命令:


\.mvnw test

这样做,您将通知Maven您想在应用程序中运行所有测试:

Tests being executed

如果所有测试成功进行,您将看到以下内容:

Tests run successfully


步骤07-测试覆盖范围

最后,很多时候我们进行测试时,我们想了解它的广泛性。我们希望在我们的项目中尽可能多的课程和方法,从而避免任何部分都没有注意到。

我们可以使用 jacoco 插件来检查测试覆盖范围。它的文档存在于at this link

要使用它,您需要转到pom.xml文件并添加以下plugin


<plugin>
   <groupId>org.jacoco</groupId>
   <artifactId>jacoco-maven-plugin</artifactId>
   <version>0.8.8</version>
   <executions>
      <execution>
         <goals>
            <goal>prepare-agent</goal>
         </goals>
      </execution>
      <execution>
         <id>report</id>
         <phase>test</phase>
         <goals>
            <goal>report</goal>
         </goals>
      </execution>
   </executions>
</plugin>


那就是!现在,您可以重新运行测试,完成后,将在target.site.jacoco中生成.html文件。该文件为index.html,访问它时,您的屏幕将与此类似:

JaCoCo's initial screen

您可以看到,com.simplelibrary.simplelibraryAPI.model包的覆盖率为70%。有关更多信息,您可以单击包裹,然后在所需的类上进行以下覆盖范围视图:

Book coverage

  • 绿色表示代码段被称为。

因此,您可以使用插件并添加新测试,以使给定类的覆盖范围尽可能高!


我们在本教程中学到的知识

在第三部分中,我们学习了如何使用Junit执行单元和集成测试。我们还看到了如何使用Jacoco插件检查测试覆盖范围。

next part中,您将使用Swagger看到有关项目文档的信息。

记住可以找到完整的项目HERE