Skip to content

31如何利用单元测试和集成测试让你开发效率翻倍?

在实际工作中,我发现有些开发人员不喜欢写测试用例,感觉是在浪费时间,但是要知道,如果我们测试用例非常完备,是可以提升团队体效率的。那么这一讲我们就针对这一问题,来看看如何使用单元测试,以及如何快速地写单元测试。

由于工作中常见的有 junit 4、junit 5 两个版本,我们使用的是 Spring boot 2.1,里面默认集成了 junit5,所以今天我们以 junit 5 进行讲解。我们先从数据库层开始。

Spring Data JPA 单元测试的最佳实践

实际工作中我们免不了要和 Repository 打交道,那么这层的测试用例应该怎么写呢?怎么才能提高开发效率呢?关于 JPA 的 Repository,下面我们分成两个部分来介绍:了解基本语法;分析最佳实践。

Spring Data JPA Repository 的测试用例

测试用例写法步骤如下。

第一步:引入 test 的依赖,gradle 的语法如下所示。

java
testImplementation 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

第二步:利用项目里面的实体和 Repository,假设我们项目里面有 Address 和 AddressRepository,代码如下所示。

java
@Entity
@Table
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class Address extends BaseEntity {
   private String city;
   private String address;
}
//Repository的DAO层
public interface AddressRepository extends JpaRepository<Address, Long>{
  
}

第三步:新建 RepsitoryTest,@DataJpaTest 即可,代码如下所示。

java
@DataJpaTest
public class AddressRepositoryTest {
    @Autowired
    private AddressRepository addressRepository;
    //测试一下保存和查询
    @Test
    public  void testSave() {
        Address address = Address.builder().city("shanghai").build();
        addressRepository.save(address);
        List<Address> address1 = addressRepository.findAll();
        address1.stream().forEach(address2 -> System.out.println(address2));
    }
}

通过上面的测试用例可以看到,我们直接添加了 @DataJpaTest 注解,然后利用 Spring 的注解 @Autowired,引入了 spring context 里面管理的 AddressRepository 实例。换句话说,我们在这里面使用了集成测试,即直接连接的数据库来完成操作。

第四步:直接运行上面的测试用例,可以得到如下图所示的结果。

通过测试结果,我们可以发现:

  1. 我们的测试方法默认都会开启一个事务,测试完了之后就会进行回滚;

  2. 里面执行了 insert 和 select 两种操作;

  3. 如果开启了 Session Metrics 的日志的话,也可以观察出来其发生了一次 connection。

通过这个案例,我们可以知道 Repository 的测试用例写起来还是比较简单的,其中主要利用了 @DataJpaTest 的注解。下面我们打开 @DataJpaTest 的源码,看一下。

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class) //测试环境的启动方式
@ExtendWith(SpringExtension.class)//加载了Spring测试环境
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa//加载了依赖Spring Data JPA的原有配置
@AutoConfigureTestDatabase //加载默认的测试数据库,我们这里面采用默认的H2
@AutoConfigureTestEntityManager//加载测试所需要的EntityManager,主要是事务处理机制不一样
@ImportAutoConfiguration
public @interface DataJpaTest {
   //默认打开sql的控制台输出,所以当我们什么都没有做的时候就可以看到SQL了
   @PropertyMapping("spring.jpa.show-sql")
   boolean showSql() default true;
......}

通过源码会发现 @DataJpaTest 注解帮我们做了很多事情:

  1. 加载 Spring Data JPA 所需要的上下文,即数据库,所有的 Repository;

  2. 启用默认集成数据库 h2,完成集成测试。

现在我们知道了 @DataJpaTest 所具备的能力,那么在实际工作中,哪些场景会需要写 Repository 的测试用例呢?

Repository 的测试场景

可能在工作中,有的同事会说没有必要写 Repository 的测试用例,因为好多方法都是框架里面提供的,况且这个东西没有什么逻辑,写的时候有点浪费时间。

其实不然,如果能把 Repository 的测试用写好的话,这对我们的开发效率绝对是有提高的。否则当给你一个项目,让你直接改里面的代码,你可能就会比较慌,不敢改。所有你就要知道都有哪些场景我们必须要写 Repository 的测试用例。

场景一:当新增一个 Entity 和实体对应的 Repository 的时候,需要写个简单的 save 和查询测试用例,主要目的是检查我们的实体配置是否正确,否则当你写了一大堆 Repository 和 Entity 的时候,启动报错,你就傻眼了,不知道哪里配置得有问题,这样反而会降低我们的开发效率;

场景二:当实体里面有一些 POJO 的逻辑,或者某些字段必须要有的时候,我们就需要写一些测试用例,假设我们的 Address 实体里面不需要有 address 属性字段,并且有一个 @Transient 的字段和计算逻辑,如下述代码所示。

java
public class Address extends BaseEntity {
   @JsonProperty("myCity")
   private String city;
   private String address; //必要字段
   @Transient //非数据库字段,有一些简单运算
   private String addressAndCity;
   public String getAddressAndCity() {
      return address+"一些简单逻辑"+city;
   }
}

这时我们就需要写一些测试用例去验证一下了。

场景三:当我们有自定义的方法的时候,就可能需要测试一下,看看返回结果是否满足需求,代码如下所示。

java
public interface AddressRepository extends JpaRepository<Address, Long>{
    Page<Address> findByAddress(@Param("address") String address, Pageable pageable);
}

场景四:当我们利用 @Query注解,写了一些 JPQL 或者 SQL 的时候,就需要写一次测试用例来验证一下,代码如下所示。

java
public interface AddressRepository extends JpaRepository<Address, Long>{
    //通过@Query注解自定的JPQL或Navicat SQL
    @Query(value = "FROM Address where deleted=false ")
    Page<Address> findAll(Pageable pageable);
}

那么对应的复杂一点的测试用例就要变成如下面这段代码所示的样子。

java
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DataJpaTest
public class AddressRepositoryTest {
    @Autowired
    private AddressRepository addressRepository;
    @BeforeAll //利用 @BeforeAll准备一些Repositroy需要的测数据
    @Rollback(false)// 由于每个方法都是有事务回滚机制的,为了测试我们的Repository可能需要模拟一些数据,所以我们改变回滚机制
    @Transactional
    public void init() {
        Address address = Address.builder().city("shanghaiDeleted").deleted(true).build();
        addressRepository.save(address);
    }
    //测试没有包含删除的记录
    @Test
    public  void testFindAllNoDeleted() {
        List<Address> address1 = addressRepository.findAll();
        int deleteSize = address1.stream().filter(d->d.equals("shanghaiDeleted")).collect(Collectors.toList()).size();
        Assertions.assertTrue(deleteSize==0); //测试一下不包含删除的条数
    }
}

场景五:当我们测试一些 JPA 或者 Hibernate 的底层特性的时候,测试用例可以很好地帮助我们。因为如果依赖项目启动来做测试,效率太低了,例如我们之前讲的一些 @PersistenceContext 特性,那么就可以通过类似如下的测试用例完成测试。

java
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DataJpaTest
@Import(TestConfiguration.class)
public class UserInfoRepositoryTest {
    @Autowired
    private UserInfoRepository userInfoRepository;
    //测试一些手动flush的机制
    @PersistenceContext
            (properties = {@PersistenceProperty(
                    name = "org.hibernate.flushMode",
                    value = "MANUAL"//手动flush
            )})
    private EntityManager entityManager;

    @BeforeAll
    @Rollback(false)
    @Transactional
    public void init() {
        //提前准备一些数据方便我们测试
        UserInfo u1 = UserInfo.builder().id(1L).lastName("jack").version(1).build();
        userInfoRepository.save(u1);
    }
    @Test
    @Transactional
    public void testLife() {
        UserInfo userInfo = UserInfo.builder().name("new name").build();
        //新增一个对象userInfo交给PersistenceContext管理,即一级缓存
        entityManager.persist(userInfo);
        //此时没有detach和clear之前,flush的时候还会产生更新SQL
        userInfo.setName("old name");
        entityManager.flush();
        entityManager.clear();
//        entityManager.detach(userInfo);
        // entityManager已经clear,此时已经不会对UserInfo进行更新了
        userInfo.setName("new name 11");
        entityManager.flush();
        //由于有cache机制,相同的对象查询只会触发一次查询SQL
        UserInfo u1 = userInfoRepository.findById(1L).get();
        //to do some thing
        UserInfo u2 = userInfoRepository.findById(1L).get();
    }
}

测试场景可能远不止我上面举例的这些,总之你要灵活地利用测试用例来判断某些方法或者配置是否达成预期效果还是挺方便的。其中初始化数据的方法也有很多,我也只是举一个例子,期望你可以举一反三。

以上我们利用 @DataJpaTest 帮我们完成了数据层的集成测试,但是实际工作中,我们也会用到纯粹的单元测试,那么集成测试和单元测区别是什么?我们必须要搞清楚。

集成测试和单元测试的区别

什么是单元测试

通俗来讲,就是不依赖本类之外的任何方法完成本类里面的所有方法的测试,也就是我们常说的依赖本类之外的,都通过 Mock 的方式进行。那么在单元测试的模式下,我们一起看看 Service 层的单元测试应该怎么写。

Service 层单元测试

首先,我们模拟一个业务中的 Service 方法,代码如下所示。

java
@Component
public class UserInfoServiceImpl implements UserInfoService {
   @Autowired
   private UserInfoRepository userInfoRepository;
   //假设有个 findByUserId的方法经过一些业务逻辑计算返回了一个业务对象UserInfoDto
   @Override
   public UserInfoDto findByUserId(Long userId) {
      UserInfo userInfo = userInfoRepository.findById(userId).orElse(new UserInfo());
      //模拟一些业务计算改变一下name的值返回
      UserInfoDto userInfoDto = UserInfoDto.builder().name(userInfo.getName()+"_HELLO").id(userInfo.getId()).build();
      return userInfoDto;
   }
}

其次,service 通过 Spring 的 @Component 注解进行加载,UserInfoRepository 通过 spring 的 @Autowired 注入进来,我们来测试一下 findByUserId 这个业务 service 方法,单元测试写法如下。

java
@ExtendWith(SpringExtension.class)//通过这个注解利用Spring的容器
@Import(UserInfoServiceImpl.class)//导入要测试的UserInfoServiceImpl
public class UserInfoServiceTest {
    @Autowired //利用spring的容器,导入要测试的UserInfoService
    private UserInfoService userInfoService;
    @MockBean //里面@MockBean模拟我们service中用到的userInfoRepository,这样避免真实请求数据库
    private UserInfoRepository userInfoRepository;
    // 利用单元测试的思想,mock userInfoService里面的UserInfoRepository,这样Service层就不用连接数据库,就可以测试自己的业务逻辑了
    @Test
    public void testGetUserInfoDto() {
//利用Mockito模拟当调用findById(1)的时候,返回模拟数据
                Mockito.when(userInfoRepository.findById(1L)).thenReturn(java.util.Optional.ofNullable(UserInfo.builder().name("jack").id(1L).build()));
        UserInfoDto userInfoDto = userInfoService.findByUserId(1L);
        //经过一些service里面的逻辑计算,我们验证一下返回结果是否正确
        Assertions.assertEquals("jack",userInfoDto.getName());
    }
}

这样就可以完成了我们的 Service 层的测试了。

其中 @ExtendWith(SpringExtension.class) 是 spring boot 与 Junit 5 结合使用的时候,当利用 Spring 的 TesatContext 进行 mock 测试时要使用的。有的时候如果们做一些简单 Util 的测试,就不一定会用到 SpringExtension.class。

在 service 的单元测试中,主要用到的知识点有四个。

  1. 通过 @ExtendWith(SpringExtension.class) 加载 Spring 的测试框架及其 TestContext;

  2. 通过 @Import(UserInfoServiceImpl.class) 导入具体要测试的类,这样 SpringTestContext 就不用加载项目里面的所有类,只需要加载 UserInfoServiceImpl.class 就可以了,这样可以大大提高测试用例的执行速度;

  3. 通过 @MockBean 模拟 UserInfoSerceImpl 依赖的 userInfoRepository,并且自动注入 Spring test context 里面,这样 Service 里面就自动有依赖了;

  4. 利用 Mockito.when().thenReturn() 的机制,模拟测试方法。

这样我们就可以通过 Assertions 里面的断言来测试 serice 方法里面的逻辑是否符合预期了。那么接下来我们看看 Controller 层的测试用例要怎么写。

Controller 层单元测试

我们新增一个 UserInfoController 跟进 Id 获得 UserInfoDto 的信息,代码如下所示。

java
@RestController
public class UserInfoController {
   @Autowired
   private UserInfoService userInfoService;
   //跟进UserId取用户的详细信息
   @GetMapping("/user/{userId}")
   public UserInfoDto findByUserId(@PathVariable Long userId) {
      return userInfoService.findByUserId(userId);
   }
}

那么我们看一下 Controller 里面完整的测试用例,代码如下所示。

java
package com.example.jpa.demo;
import com.example.jpa.demo.service.UserInfoService;
import com.example.jpa.demo.service.dto.UserInfoDto;
import com.example.jpa.demo.web.UserInfoController;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserInfoController.class)
public class UserInfoControllerTest {
    @Autowired
    private MockMvc mvc;
    @MockBean
    private UserInfoService userInfoService;
    
    //单元测试mvc的controller的方法
    @Test
    public void testGetUserDto() throws Exception {
        //利用@MockBean,当调用 userInfoService的findByUserId(1)的时候返回一个模拟的UserInfoDto数据
        Mockito.when(userInfoService.findByUserId(1L)).thenReturn(UserInfoDto.builder().name("jack").id(1L).build());
        
        //利用mvc验证一下Controller里面的解决是否OK
        MockHttpServletResponse response = mvc
                .perform(MockMvcRequestBuilders
                        .get("/user/1/")//请求的path
                        .accept(MediaType.APPLICATION_JSON)//请求的mediaType,这里面可以加上各种我们需要的Header
                )
                .andDo(print())//打印一下
                .andExpect(status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("jack"))
                .andReturn().getResponse();
        System.out.println(response);
    }
}

其中我们主要利用了 @WebMvcTest 注解,来引入我们要测试的 Controller。打开 @WebMvcTest 可以看到关键源码,如下图所示。

我们可以看得出来,@WebMvcTest 帮我们加载了 @ExtendWith(SpringExtension.class),所以不需要额外指定,就拥有了 Spring 的 test context,并且也自动加载了 mvc 所需要的上下文 WebMvctestContextbootstrapper。

有的时候可能有一些全局的 Filter,也可以通过此注解里面的 includeFilters 和 excluedeFilters 加载和排除我们需要的 WebMvcFilter 进行测试。

当通过 @WebMvcTest(UserInfoController.class) 导入我们需要测试的 Controller 之后,就可以再通过 MockMvc 请求到我们加载的 Contoller 里面的 path 了,并且可以通过 MockMvc 提供的一些方法发送请求,验证 Controller 的响应结果。

下面概括一下 Contoller 层单元测试主要用到的三个知识点。

  1. 利用 @WebMvcTest 注解,加载我们要测试的 Controller,同时生成 mvc 所需要的 Test Context;

  2. 利用 @MockBean 默认 Controller 里面的依赖,如 Service,并通过 Mockito.when().thenReturn();的语法 mock 依赖的测试数据;

  3. 利用 MockMvc 中提供的方法,发送 Controller 的 Rest 风格的请求,并验证返回结果和状态码。

那么单元测试我们先介绍这么多,下面看一下什么是集成测试。

什么是集成测试

顾名思义,就是指多个模块放在一起测试,和单元测试正好相反,并非采用 mock 的方式测试,而是通过直接调用的方式进行测试。也就是说我们依赖 spring 容器进行开发,所有的类之间直接调用,模拟应用真实启动时候的状态。我们先从 Service 层进行了解。

Service 层的基层测试用例写法

我们还用刚才的例子,看一下 UserInfoService 里面的 findByUserId 通过集成测试如何进行。测试用例的写法如下。

java
@DataJpaTest
@ComponentScan(basePackageClasses= UserInfoServiceImpl.class)
public class UserInfoServiceIntegrationTest {
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private UserInfoRepository userInfoRepository;
    @Test
    @Rollback(false)//如果我们事务回滚设置成false的话,数据库可以真实看到这条数据
    public void testIntegtation() {
        UserInfo u1 = UserInfo.builder().name("jack-db").ages(20).id(1L).telephone("1233456").build();
        //数据库真实加一条数据
        userInfoRepository.save(u1);//数据库里面真实保存一条数据
        UserInfoDto userInfoDto =  userInfoService.findByUserId(1L);
        userInfoDto.getName();
        Assertions.assertEquals(userInfoDto.getName(),u1.getName()+"_HELLO");
    }
}

我们执行一下测试用例,结果如下图所示。

这时你会发现数据已经不再回滚,也会正常地执行 SQL,而不是通过 Mock 的方式测试。Service 的集成测试相对来说还比较简单,那么我们看下 Controller 层的集成测试用例应该怎么写。

Controller 层的集成测试用例的写法

我们用集成测试把刚才 UserInfoCotroller 写的 user/1/ 接口测试一下,将集成测试的代码做如下改动。

java
@SpringBootTest(classes = DemoApplication.class,
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //加载DemoApplication,指定一个随机端口
public class UserInfoControllerIntegrationTest {
    @LocalServerPort //获得模拟的随机端口
    private int port;
    @Autowired //我们利用RestTemplate,发送一个请求
    private TestRestTemplate restTemplate;
    @Test
    public void testAllUserDtoIntegration() {
        UserInfoDto userInfoDto = this.restTemplate
                .getForObject("http://localhost:" + port + "/user/1", UserInfoDto.class);//真实请求有一个后台的API
        Assertions.assertNotNull(userInfoDto);
    }
}

我们再看日志的话,会发现此次的测试用例会在内部启动一个 tomcat 容器,然后再利用 TestResTemplate 进行真实请求,返回测试结果进行测试。

而其中会涉及一个注解 @SpringBootTest,它用来指定 Spring 应用的类是哪个,也就是我们真实项目的 Application 启动类;然后会指定一个端口,此处必须使用随机端口,否则可能会有冲突(如果我们启动的集成测试有点多的情况)。日志如下图所示。

如果我们看 @SprintBootTest 源码的话,会发现这个注解也是加载了 Spring 的测试环境 SpringExtension.class,并且里面有很多属性可以设置,测试的时候的配置文件 properties 和一些启动的环境变量 WebEnv;然后我们又利用了 Spring Boot Test 提供的 @LocalServerPort 获得启动时候的端口。源码如下图所示。

虽然集成测试用法也是比较简单的,甚至可能比 Mock 的测试环境更简单,因为集成测试可以取到 Application 启动之后加载的任何 Bean。但是实际工作中我们使用集成测试的时候,还是需要思考一些问题。

集成测试的一些思考

1.所有的方法都需要集成测试吗?

这是我们写集成测试用的时候需要思考的,因为集成测试用例需要内部启动 Tomcat 容器,所以可能会启动得慢一点。如果我们的项目加载的配置文件越来越多,势必会导致测试也会变慢。假设我们测试一个简单的逻辑就需要启动整个 Application,那么显然是不妥的。

那么我们整个 Application 不需要集成测试吗?也显然不是的,因为有些时候只有集成在一起才会发生问题,最简单的一个集成测试是我们需要测试是否能正常启动,所以一个项目里面会有个 ApplicationTests 来测试项目是否能正常启动。代码如下所示。

java
@SpringBootTest
class DemoApplicationTests {
//测试项目是否能正常启动
   @Test
   void contextLoads() {
   }
}

2.一定是非集成测试就是单元测试吗?

实际工作中并没有划分那么清楚,有的时候我们集成了 N 个组件一起测试,可能就是不连数据库。比如我们可能会使用 Feign-Client 根据第三方的接口获取一些数据,那么我们正常的做法就是新建一个 Service,代码如下所示。

java
/**
 * 测试普通JSON返回结果,根据第三方接口取一个数据
 */
@FeignClient(name = "aocFeignTest", url = "http://room-api.staging.jack.net")
interface AppSettingService {
    @GetMapping("/api/v1/app/globalSettings")
    HashMap<String,Object> getAppSettings();
}

那么这个时候如果我们要测试,显然不需要启动整个 Application 来完成,但是需要按需加载一些 Configration 才能测试,那么测试用例会变成如下情况。

java
@ExtendWith(SpringExtension.class)//利用Spring上下文
@Import({FeignSimpleConfiguration.class, FeignAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class})//导入此处Fegin-Client测试所需要的配置文件
@EnableFeignClients(clients = AppSettingService.class)//通过FeignClient的注解加载AppSettingService客户端。
/**
 * 依赖HTTPMessageConverter的使用方法(import FeignSimpleConfiguration junit)
 */
public class FeignJsonTest {
    @Autowired利用Spring的上下文注入appSettingService
    private AppSettingService appSettingService;
    @Test
    public void testJsonFeignClient() {
        HashMap<String,Object>  r = .getAppSettings();
        Assert.assertNotNull(r.get("data"));//测试一下接口返回的结果
    }
}

你会发现这个时候其实并没有启动这个 Application,但是我们也集成了 Fegin-Client 所需要的上下文 Configuration,从而利用 SpringExtension 加载所需要依赖的类,完成一次测试。

所以你一定要理解单元测试和集成测试的本质,根据自己的实际需要选择性地加载一些类来完成测试用例,而不是每次测试的时候都需要把所有类都加载一遍,这样返回会使测试用例的时间变长,从而降低工作效率。

由于目前现状是 Junit 4 和 Junit 5 一样流行,所以你使用的时候要注意使用的是 Junit 5 还是 Junit 4,不要弄混了。下面我再介绍一些 Junit 5 和 Junit 4 的区别,来帮你加深印象。

Junit 4 和 Junit 5 在 Spring Boot 中的区别

第一,Spring Boot 2.2+ 以上的版本默认导入的是 Junit 5 的 jar 包依赖,以下的版本默认导入的是 Junit 4 的 jar 包依赖的版本,所以你在使用不同版本的 Spring Boot 的时候需要注意一下依赖的 jar 包是否齐全。

第二,org.junit.junit.Test 变成了 org.junit.jupiter.api.Test。

第三,一些注解发生了变化:

  • @Before 变成了 @BeforeEach

  • @After 变成了 @AfterEach

  • @BeforeClass 变成了 @BeforeAll

  • @AfterClass 变成了 @AfterAll

  • @Ignore 变成了 @Disabled

  • @Category 变成了 @Tag

  • @Rule 和 @ClassRule 没有了,用 @ExtendWith 和 @RegisterExtension 代替

第四,引用 Spring 的上下文 @RunWith(SpringRunner.class) 变成了 @ExtendWith(SpringExtension.class)。

第五,org.junit.Assert 下面的断言都移到了org.junit.jupiter.api.Assertions 下面,所以一些断言的写法会发生如下变化:

java
//junit4断言的写法
Assert.assertEquals(200, result.getStatusCodeValue());
Assert.assertEquals(true, result.getBody().contains("employeeList"));
//junit5断言的写法
Assertions.assertEquals(400, ex.getRawStatusCode());
Assertions.assertEquals(true, ex.getResponseBodyAsString().contains("Missing request header"));

第六,Junit 5 提供 @DisplayName("Test MyClass") 用来标识此次单元测试的名字,代码如下所示。

java
@DisplayName("Test MyClass")
class MyClassTest {
    @Test
    @DisplayName("Verify MyClass.myMethod returns true")
    void testMyMethod() throws Exception {    
        // ...
    }
}

常用的变化我就介绍这么多了,很多时候我看身边的开发人员写测试用例的时候,没有自己的知识脉络,从网上看到一块代码就 Copy 过来了,那么希望你可以通过上面的介绍,认清楚测试用例的整体情况,在实际开发过程中再根据具体的语法进行调整。

总结

这一讲我们主要介绍了 @DataJpaTest、@ExtendWith(SpringExtension.class)、@MockBean、@WebMvcTest、@MockMvc、@SpringBootTest 等注解的用法,明白了测试用例中几个最关键的集成测试和单元测试的区别,知道了测试用例中如何利用 Spring 的上下文,也晓得了 Junit 5 的一些基本语法。

这里我只是带领你入了测试用例的门,剩下的期望你能结合自己的实际情况,将单元测试重视起来,并灵活运用 Spring Boot 给我们带来的单元测试的便利性,完成必要的单元测试,从而减少代码的 bug 率,提升自己的开发效率。

本讲内容到这里就结束了,下一讲我们来聊聊 Spring Data 和 ES 如何结合使用。再见。

点击下方链接查看源码(不定时更新)
https://github.com/zhangzhenhuajack/spring-boot-guide/tree/master/spring-data/spring-data-jpa