SpringBoot对单元测试支持、常用单元测试功能使用实例

发表于:2021-2-09 09:37

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:程序员高级码农II    来源:今日头条

  SpringBoot 单元测试
  Spring Boot 提供了许多注解和工具帮助开发人员测试应用,在其官方文档中也用了大量篇幅介绍单元测试的使用。在谷歌每周的 TGIF (ThanksGod, it's Friday)员工大会中有一项就是 宣布-周单元测试竞赛获胜的工程师。谷歌之所以这么重视单元测试,就是为了保证程序质量,鼓励大家多写测试代码。国内大多数开发人员对单元测试有所忽视,这也是我写本章内容的原因所在。
  本章会围绕 Spring Boot 对单元测试的支持、常用单元测试功能的使用实例以及 MockMvc的自动配置机制展开。
  Spring Boot 对单元测试的支持
  Spring Boot 对单元测试的支持重点在于提供了-系列注解和工具的集成,它们是通过两个项目提 供 的 : 包 含 核 心 功 能 的 spring-boot-test 项 目 和 支 持 自 动 配 置 的spring-boot-test-autoconfigure。
  通常情况下,我们通过 spring-boot-starter-test 的 Starter 来引入 SpringBoot 的核心支持项目以及单元测试库。spring-boot-starter-test 包 含的类库如 JUnit:一个 Java 语言的单元测试框架。
  Spring Test & Spring Boot Test:为 Spring Boot 应用提供集成测试和工具支持。
  AssertJ:支持流式断言的 Java 测试框架。
  .Hamcrest: 一个匹配器库。
  Mockito :一个 Java Mock 框架。
  JSONassert:一个针对 JSON 的断言库。
  JsonPath:一个 JSON XPath 库。
  如果 Spring Boot 提供的基础类库无法满足业务需求,我们也可以自行添加依赖。依赖注入的优点之一就是可以轻松使用单元测试。 这种方式可以直接通过 new 来创建对象,而不需要涉及 Spring。当然,也可以通过模拟对象来替换真实依赖。
  如果需要集成测试,比如使用 Spring 的 ApplicationContext, Spring 同样能够提供无须部署应 用 程 序 或 连 接 到 其 他 基 础 环 境 的 集 成 测 试 。 而 SpringBoot 应 用 本 身 就 是 一 个ApplicationContext,因此除了正常使用 Spring.上下文进行测试,无须执行其他操作。
  常用单元测试注解
  以 Junit 为例,在单元测试中会常用到一些注解,比如 Spring Boot 提供的@SpringBootTest
  @MockBean、@SpyBean 、@WebMvcTest@AutoConfigureMockMvc 以及 Junit 提供的@RunWith 等。下面以- 一个简单的订单插入的功能示例进行说明。
  @RunWith(SpringRunner .class)
  public class OrderServiceTest {
  @Autowired
  private OrderService orderService;
  ublic void testInsert() {Order order = new Order()
  order . setOrderNo("A001");
  order. setUserId(100);
  orderService. insert (order);
  }
  }
  我们先来看 Junit 中的@RunWith 注解,该注解用于说明此测试类的运行者,比如示例中使用 的 SpringRunner 。 SpringRunner 是 由 spring-test 提 供 的 , 它 实 际 上 继 承 了SpringJUnit4ClassRunner 类,并且未重新定义任何方法,我们可以将 SpringRunner 理解为 SpringJUnit4ClassRunner 更简洁的名字。
  @SpringBootTest 注解由 Spring Boot 提供,该注解为 SpringApplication 创建上下文并支持 Spring Boot 特性。
  该测试项目中引入了 spring-boot-starter-test 依赖,默认情况下此依赖使用的单元测试类库为 J∪nit4,此时@SpringBootTest 注解需要配合@RunWith(SpringRunner.class)注解使用,否则注解会被忽略。
  查看@SpringBootTest 注解的源码,会发现其内部枚举类 WebEnvironment 提供了支持的多种单元测试模式。
  @Target(ElementType. TYPE)
  @Retention(Retent ionPolicy . RUNTIME)
  @Documented
  @Inherited
  @BootstrapWith(SpringBootTestContextBootstrapper. class)
  @ExtendWith(SpringExtension. class)
  public @interface SpringBootTest {
  @AliasFor("properties")
  String[] value() default {};
  @AliasFor("value" )
  String[] properties() default {};
  String[] args() default {};
  Class<?>[] classes() default {};
  WebEnvi ronment webEnvironment() default WebEnvironment . MOCK;
  enum WebEnvironment {
  MOCK(false),
  RANDOM PORT(true)
  DEFINED_ PORT(true),
  NONE(false); } }
  从@SpringBootTest 的源代码中可以看出,通过 WebEnvironment 枚举类提供了 MOCK、RANDOM_ PORT、DEFINED_ PORT 和 NONE 这 4 种环境配置。
  :Mock:加载 WebApplicationContext 并提供 Mock Servlet 环境,嵌入的 Servlet 容器不会被启动。
  :RANDOM_ PORT:加载一个 EmbeddedWebApplicationContext 并提供真实的 Servlet 环境。嵌入的 Servlet 容器将被启动,并在一个随机端口上监听。
  :DEFINED_ PORT:加载一个 EmbeddedWebApplicationContext 并提供真实的 Servlet 环境。嵌入的 Servlet 容器将被启动,并在一个默认的端口上监听(application.properties 配 置端口或者默认端口 8o8o)。
  :NONE:使用 SpringApplication 加载一个 ApplicationContext,但是不提供任何 Servlet 环境。
  示例中默认采用此种方式。
  关于其他的注解就不再展开了,在后面章节中会结合具体示例进行说明。
  JUnit5 单元测试示例
  在上节中已经提到 JUnit5 与 JUnit4 有所不同,本节还是用同样的示例来看一下 JUnit5 的使用。
  @SpringBootTest
  public class OrderServiceTest {
  @Resource
  private OrderService orderService;
  @Test
  public void testInsert()
  Order order = new Order();
  order . setOrderNo( "A001");
  order . setUserId(100);
  orderService . insert (order);}
  通过上面的代码,我们可以看出默认情况下只需要使用@SpringBootTest 注解即可,而在上节@SpringBootTest 源代码中已经看到组合了@ExtendWith(SpringExtension.class)注解,因此此示例无须注解。
  这里需要注意的是 Spring Boot 的版本信息,在 2.1.x 之后@SpringBootTest 注解中才组合了@ExtendWith(SpringExtension.class)注解。因此,需要根据具体使用的版本来确定是否需要@ExtendWith(SpringExtension.class)注解,否则可能会出现注解无效的情况虽然单元测试类的代码与 JUnit4 基本相同,但本质上还是有区别的。比如,在使用 JUnit5时, 默认的 spring-boot- starter-test 依赖类库已经无法满足,需要手动引|入 junit-jupiter.
  <!-- Junit 5 -->
  <dependency><groupId>org . junit. jupiter</ groupId>
  <artifactId>junit - jupiter</ artifactId>
  <version>5.5.2</version>
  <scope>test</scope>
  / dependency>
  同时,如果必要则需要将 junit-vintage-engine 进行排除。
  <dependency>
  <groupId>org. springframework . boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
  <exclusion>
  <groupId>org. junit . vintage</ groupId>
  <artifactId>junit -vintage - engine</artifactId>
  </exc lusion>
  </exclusions>
  </dependency>
  上面的测试代码还有一个经常会遇到的问题,就是从 JUnit4 升级到 JUnit5 时,如果你只是把类上的注解换了,会发现通过@Resource 或@Autowired 注入的 OrderService 会抛出空指针异常。这是为什么呢?
  原因很简单,从 JUnit4 升级到 JUnit5 时,在 testInsert 方法 上的@Test 注解变了。在 JUnit4中默认使用的@Test 注解为 org.junit.Test,而在 JUnit5 中需要使用 org.junit.jupiter.api.Test.因此,如果在升级的过程中出现莫名其妙的空指针异常时,需考虑到此处。
  总体来说,JUnit5 的最大变化是 @Test 注解改为由几个不同的模块组成,其中包括 3 个不同子项目: JUnit Platform、JUnit Jupiter 和 JUnit Vintage.同时,JUnit5 也提供了一套自己的注解。
  .@ BeforeAll 类似于 JUnit 4 的@BeforeAll,表示使用了该注解的方法应该在当前类中所有使用了@Test、@ RepeatedTest、@ ParameterizedTest 或者@TestFactory 注解的方法之前执行,且必须为 static。
  .@ BeforeEach 类似于 JUnit 4 的@Before,表示使用了该注解的方法应该在当前类中所有使用了@Test、@ RepeatedTest、@ ParameterizedTest 或者@TestFactory 注解的方法之前执行。
  .@Test 表示该方法是一个测试方法。
  .@ DisplayName 为测试类或测试方法声明一个自定义的显示名称。
  .@AfterEach 类似于 JUnit 4 的@After,表示使用了该注解的方法应该在当前类中所有使用了@Test、@RepeatedTest 、@ ParameterizedTest 或者@ TestFactory 注解的方法之后执行。
  .@AfterAll 类似于 JUnit 4 的@AfterClass, 表示使用了该注解的方法应该在当前类中所有使用了@Test、@RepeatedTest、 @ ParameterizedTest 或者@ TestFactory 注解的方法之后执行,且必须为 static。
  .@Disable 用于禁用一个测试类或测试方法,类似于 JUnit 4 的@Ignore.
  .@ExtendWith 用于注册自定义扩展功能。
  关于这些注解的详细使用,我们就不一一举例了。
  Web 应用单元测试
  在面向对象的程序设计中,模拟对象(mock object)是以可控的方式模拟真实对象行为的假对象。在编程过程中,通常通过模拟一些输入数据,来验证程序是否达到预期效果。
  模拟对象-般应用于真实对象有以下特性的场景:行为不确定、真实环境难搭建、行为难触发、速度很慢、需界面操作、回调机制等。
  在上面章节中实现了 Service 层的单元测试示例,而当对 Controller 层进行单元测试时,便需要使用模拟对象,这里采用 spring-test 包中提供的 MockMvc。MockMvc 可以做到不启动项目工程就可以对接口进行测试。
  MockMvc 实现了对 HTTP 请求的模拟,能够直接使用网络的形式,转换到 Controller 的调用,这样可以使得测试速度快、不依赖网络环境,同时提供了一套验证的工具, 使得请求的验证统-一而且方便。
  下面以一个具体的示例来对 MockMvc 的使用进行讲解。在使用之前,依旧需要先引入对应的依赖。
  <dependency>
  <groupId>org. springfr amework . boot</ groupId>
  <artifactId>spring boot - starter- test</ artifactId>
  <scope>test</scope>
  </ dependency>
  这里创建一一个简单的 TestController,提供一个 hello 方法, 返回一个字符串。
  @RestController
  public class TestController {
  @RequestMapping(" /mock" )
  public String mock(String name) {
  return "Hello ”+ name + "!"; }}
  下面编写单元测试的类和方法,我们这里都采用基于 JUnit4 和 SpringBoot 2.x 版本进行操作。
  @RunWith(SpringRunner . class)@SpringBootTest
  @AutoConfigureMockMvc
  public class TestControllerTest {
  @Autowired
  private MockMvc mockMvc;
  @Test
  public void testMock() throws Exception {
  // mockMvc. perform 执行一 个请求
  mockMvc . perform(MockMvcRequestBuilders
  // MockMvcRequestBuilders. get( "XX")构造一个请求. get(" /mock")
  //设置返回值类型为 utf-8, 否则默认为 ISO- 8859-1
  . accept (MediaType . APPLICATION_ JSON_ _UTF8_ _VALUE)
  / ResultActions . param 添加请求传值
  . param( "name", "MockMvc"))
  // Resul tActions . andExpect 添加执行完成后的断言
  . andExpect (MockMvcResultMatchers . status(). isOk())
  . andExpect (MockMvcResultMatchers . content(). string("Hello MockMvc!"))
  // Resul tActions . andDo 添加一一个结果处理器,此处打印整个响应结果信息
  . andDo(MockMvcResultHandlers . print());
  }
  }
  执行该单元测试打印结果部分内容如下。
  MockHttpServletRequest:
  HTTP Method
  GET
  Request URI = /mock
  Parameters = {name=[MockMvc]}
  Headers = [Accept:" application/json; charset=UTF-8" ]
  Body = null
  Session Attrs = {}
  Handler:
  Type = com. secbro2. learn. controller. TestController
  Method = public java. lang . String com. secbro2 . learn. controller .Test
  Controller .mock(java. lang. String)
  MockHttpServletResponse:
  Status = 200Error message = nul
  Headers = [Content -Type :"application/json;charset=UTF-8", Content-Length:
  "14" ]
  Content type = application/json;charset-UTF-8
  Body = Hello MockMvc!
  在以上单元测试中,@RunWith(SpringRunner. class )和@SpringBootTest 的作用我们已经知道,另外的@AutoConfigureMockMvc 注解提供了自动配置 MockMvc 的功能。 因此,只需通过@Autowired 注入 MockMvc 即可。
  MockMvc 对象也可以通过接口 MockMvcBuilder 的实现类来获得。该接口提供一个唯一的build 方法来构造 MockMvc。主要有两个实现类:
  StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,分别对应两种测试方式,即独立安装和集成 Web 环境测试(并不会集成真正的 web 环境,而是通过相应的 Mock API 进行模拟测 试 , 无 须 启 动 服 务 器 ) 。 MockMvcBuilders 提 供 了 对 应 的 standaloneSetup 和webAppContextSetup 两种创建方法,在使用时直接调用即可。MockMvc 对象的创建默认使用 DefaultMockMvcBuilder,后面章节会详细介绍这一过程。
  整个单元测试包含以下步骤:准备测试环境、执行 MockMvc 请求、 添加验证断言、添加结果处理器、得到 MvcResult 进行自定义断言/进行下一步的异步请求、卸载测试环境。
  关于 Web 应用的测试,还有许多其他内容,比如:检测 Web 类型、检测测试配置、排除测试配置以及事务回滚(通过@ Transactional 注解),读者朋友可根据需要自行编写单元测试用例进行尝试。

  上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号