目录

前言

1.RESTful API 接口的单元测试  

2.模拟数据测试 

3.使用 Spring Boot Test 进行测试 

总结

前言

单元测试是软件开发中的一种关键测试类型,它是指对软件中的最小可测试单元进行检查和验证。对于面向对象编程,最小单元就是方法,独立的函数或过程也可以是最小单元。

在 Java 中,通常一个单元测试属于一种特定的测试工具框架,如 JUnit,它们可以很容易地插入到自动化构建过程或持续集成工具中。

单元测试的主要目标是隔离软件系统的各部分,并逐个测试。这将确保每个部分都按照预期工作。单元测试有助于提高软件质量,而且也使得代码在修改后更容易维护。

1.RESTful API 接口的单元测试  

一个 RESTful API 接口项目是最适合进行单元测试的,因为逻辑单元足够小,符合单元测试的定义。

Spring Boot 提供了 Spring Test 模块进行单元测试,还需要搭配 spring-boot-test-autoconfigure 实现测试的自动化配置。

Spring Test 是使用 junit 作为默认的单元测试模块,junit 的常用注解如下。 

注解 用处 @Test 修饰某个方法为测试方法 @Before 在每个测试方法执行前执行一次 @After 在每个测试方法执行后执行一次 @AfterClass 在所有测试方法执行之后运行 @RunWith 更改测试运行器 @BeforeClass 在所有测试方法执行前运行 @Igore 修饰的类火灾方法被忽略

添加单元测试和项目所需的所有模块依赖,pom.xml 内容代码如下:

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.3.4.RELEASE

com.freejava

unit-test-restfulapi

0.0.1-SNAPSHOT

unit-test-restfulapi

Unit test project for Spring Boot

1.8

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-test

test

org.junit.vintage

junit-vintage-engine

org.projectlombok

lombok

1.18.10

org.springframework.boot

spring-boot-starter-web

junit

junit

test

org.springframework.boot

spring-boot-maven-plugin

设计一个操作玩具数据的 restful 接口,具体见设计表。 

请求类型 URL 备注说明 GET /toys 查询玩具列表 POST /toys 创建玩具数据 PUT /toys/id 根据 id 更新玩具 DELETE /toys/id 根据 id 删除玩具

定义一个玩具实体类

package com.freejava.unittestrestfulapi.entity;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Toy {

private Long id;

private String name;

private Float price;

private String desc;

}

定义一个玩具控制器

package com.freejava.unittestrestfulapi.controller;

import com.freejava.unittestrestfulapi.entity.Toy;

import org.springframework.web.bind.annotation.*;

import java.util.*;

@RestController

public class ToyController {

// 创建线程安全的Map对象

static Map toys = Collections.synchronizedMap(new HashMap());

/**

* 获取玩具列表

*

* @return

*/

@GetMapping(value = "/toys")

public List getToyList() {

List toyLists = new ArrayList(toys.values());

return toyLists;

}

/**

* 创建玩具

*

* @param toy

* @return

*/

@RequestMapping(value = "/toys", method = RequestMethod.POST)

public String createToy(@ModelAttribute Toy toy) {

toys.put(toy.getId(), toy);

return "success";

}

/**

* 通过id获取玩具

*

* @param id

* @return

*/

@GetMapping(value = "/toys/{id}")

public Toy getToy(@PathVariable Long id) {

return toys.get(id);

}

/**

* 更新玩具

*

* @param toy

* @return

*/

@PutMapping(value = "/toys")

public String updateToy(@RequestBody Toy toy) {

Toy t = toys.get(toy.getId());

t.setDesc(toy.getDesc());

toys.put(toy.getId(), t);

return "success";

}

/**

* 根据id删除玩具

*

* @param id

* @return

*/

@DeleteMapping(value = "/toys/{id}")

public String deleteToy(@PathVariable Long id) {

toys.remove(id);

return "success";

}

}

编写单元测试,将测试编写在 test 目录下的文件中(与源目录相对应)。

package com.freejava.unittestrestfulapi;

import com.freejava.unittestrestfulapi.controller.ToyController;

import org.junit.Before;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.test.web.servlet.MockMvc;

import org.springframework.test.web.servlet.RequestBuilder;

import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.hamcrest.Matchers.equalTo;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)

@SpringBootTest

public class TestToyController {

@Autowired

private ToyController restController;

private MockMvc mvc;

@Before

public void setUp() throws Exception {

mvc = MockMvcBuilders.standaloneSetup(restController).build();

}

@Test

public void testToyController() throws Exception {

// 测试UserController

RequestBuilder request = null;

// get查一下toy列表,目前应该为空

request = get("/toys/");

mvc.perform(request)

.andExpect(status().isOk())

.andExpect(content().string(equalTo("[]")));

// 使用post方式提交一个toy数据

request = post("/toys/")

.param("id", "1")

.param("name", "功夫熊猫")

.param("price", "210.00")

.param("desc", "同名电影玩偶");

mvc.perform(request)

.andExpect(content().string(equalTo("success")));

// get方式获取toys列表

request = get("/toys").characterEncoding("UTF-8");

mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"功夫熊猫\",\"price\":210.00, \"desc\":\"同名电影玩偶\"}]")));

// put方式修改id为1的toy

request = put("/toys/1").param("name", "葫芦小精钢");

mvc.perform(request)

.andExpect(content().string(equalTo("success")));

// delete方式删除id为1的toy

request = delete("/toys/1");

mvc.perform(request).andExpect(content().string(equalTo("success")));

}

}

上面的这段代码将所有 toys 接口都依次进行测试,使用 IDE 运行该测试文件即可。 

2.模拟数据测试 

在单元测试过程中有时候会依赖上下游的接口服务和相关数据,例如要测试结算全流程,需要先进行商品选择,然后下单、支付,最后才能进行商家结算。如果只是单纯想测试中间的某一个环境,那么就需要上一步提供数据和服务,依赖性很强。在比较大的团队中,由于各自排期不同,所需的数据接口可能不稳定或者处于正在开发中,而又需要进行单元测试,那么可以考虑 Mock(模拟)数据来测试。

Spring Boot 提供了一款模拟数据的框架:Mockito。它的功能非常强大,可以进行模拟工作,如可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等。例如,当 Class A 需要 Class B 和 Class C 提供的接口时,可以考虑分别模拟出 Class B 和 Class C ,如图所示。

测试项目依赖;

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.example

unit_mock

1.0-SNAPSHOT

8

8

UTF-8

org.springframework.boot

spring-boot-starter-parent

2.3.9.RELEASE

org.projectlombok

lombok

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-test

test

org.junit.vintage

junit-vintage-engine

junit

junit

test

口袋妖怪实体类

package org.example.entity;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

@Data

@AllArgsConstructor

@NoArgsConstructor

public class PocketMonster {

// 编号 ID

private long id;

// 口袋要挂名称

private String name;

//类型,如水系

private String classType;

//技能

private String skill;

public PocketMonster(Long id, String name) {

this.id = id;

this.name = name;

}

}

service文件

package org.example.service;

import org.example.dao.PocketMonsterDao;

import org.example.entity.PocketMonster;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Component

public class PocketMonsterServicec {

@Autowired

private PocketMonsterDao pocketMonsterDao;

public PocketMonster getPocketMonsterServicecById(Long id){

return pocketMonsterDao.getpocketMonsterDaoById(id);

}

}

测试类:

package org.example;

import org.example.entity.PocketMonster;

import org.example.service.PocketMonsterServicec;

import org.junit.Assert;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.mockito.Mockito;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)

@SpringBootTest

public class PocketMonsterTest {

@Autowired

private PocketMonsterServicec pocketMonsterServicec;

@Test

public void getPocketMonsterById() {

//定义当调用 PocketMonsterDao 的 PocketMonsterDao方法的时候

//,并且参数为 2的时候,则返回 id 为 300,名称为 little fire dragon 对象

Mockito.when(pocketMonsterServicec.getPocketMonsterServicecById((long) 2))

.thenReturn(new PocketMonster(300L, "little fire dragon"));

//返回上面设置好的对象

PocketMonster monster = pocketMonsterServicec.getPocketMonsterServicecById(2L);

//开始断言

Assert.assertNotNull(monster);

Assert.assertEquals(monster.getId(), 300L);

Assert.assertEquals(monster.getName(), "little fire dragon");

}

}

如上述代码通过使用 Mockito 对象的 when 和 thenReturn 方法分别设置模拟的条件和模拟请求的返回值,这样就能顺利地进行模拟测试。 

3.使用 Spring Boot Test 进行测试 

Spring Boot Test 是在Spring Test 的基础上二次封装而来的库,增加了基于 AOP 的测试,还提供了模拟能力。

Spring Boot Test 支持单元测试,切片测试以及完整的功能测试,安装方式也很简单,如初始化时已经默认将 spring-boot-test 添加到 pom.xml 。所谓切片测试,就是介于单元测试和集成测试中间的范围的一些特定组件的测试。这些组件如 MVC 中的 Controller,JDBC 数据库访问、Redis 客户端等,需要在特定的环境下才能被正常执行。 

常用注解和基本用法 

@RunWith(SpringRunner.class)

@SpringBootTest

public class OrderServiceTests {

@Autowired

OrderService orderService;

@Test

public void contextLoads() {

}

}

(1)@RunWith:设置运行的环境,此处 Junit 执行的类设置为 SpringRunner,一般不会修改。

(2)@SpringBootTest: 注解表示会自动检索程序的配置文件,检索顺序是从当前包开始,逐级向上查找被 @SpringBootApplication 或 @SpringBootConfiguration 注解的类。除了基础的测试功能,还提供了 ContextLoader。 

(3)@Test :可以加载需要被测试的方法上,Spring 仅加载相关的 bean,无关内容不被加载。添加了@Test 注解之后 junit 上的注解如@After、@Before、@AfterClass 都可以在这里使用。

根据用途的不同,分别总结配置类型注解

配置注解表 

注解 说明 @TestComponent 指定某个Bean专门用于测试 @TestConfiguration 类似@ TestComponent,补充额外的Bean或已经覆盖的 Bean @TestExcludeFilters 排除当前被注解的类或者方法,一般不常用 @EnableAutoConfiguration 加在启动类,会开启自动配置,自动生成一些 Bean @OverrideAutoConfiguration 用于覆盖 @EnableAutoConfiguration 和 @ImportAutoConfiguration

自动配置注解表 

注解 说明 @AutoConfiureDataJpa 自动配置JPA @AutoConfiureJson 自动配置Json @AutoConfiureMockMvc 自动配置MockMvc @AutoConfiureWebMvc 自动配置WebMvc @AutoConfiureWebClient 自动配置WebClient

启动测试类型注解 

注解 说明 @SspringBootTest 自动检索程序的配置文件 @Test 被修饰的方法接口将被测试 @JsonTest 用于测试Json数据的反序列化和序列化 @WebMvcTest 测试SpringMvc中的controllers @DataJdbcTest 测试基于SpringDataJDBC的数据库操作

使用 SpringBootTest 测试Service 

测试用的服务类 

package com.freejava.unittestrestfulapi.service;

import org.springframework.stereotype.Service;

@Service

public class OrderService {

public String getOrder(String name) {

return "This " + name + " is my order";

}

}

编写测试代码就是直接在测试类中引用它。 

@RunWith(SpringRunner.class)

@SpringBootTest

public class OrderServiceTests {

@Autowired

OrderService orderService;

@Test

public void contextLoads() {

String orderString = orderService.getOrder("FreeJava");

Assert.assertThat(orderString, Matchers.is("This FreeJava is my order"));

}

}

运行该测试代码,可以全部通过断言判断。

使用 SpringBootTest 测试Controller

测试 Controller 就是在需要测试的 Controller 文件上增加注解,例如针对 UserController 增加注解并编写独立的测试类代码。

测试所需的用户实体类:

package com.freejava.unittestrestfulapi.entity;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

@Data

@AllArgsConstructor

@NoArgsConstructor

public class User {

private Long id;

private String name;

private String password;

}

编写一个简单的控制器,分别编写 hi 方法和 addUser 方法,具体代码如下:

package com.freejava.unittestrestfulapi.controller;

import com.freejava.unittestrestfulapi.entity.User;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RestController;

/**

* 用于测试的User控制器

*/

@RestController

public class UserController {

// 只返回一个say hi的字符串

@GetMapping("/hi")

public String hi(String username) {

return "Hey, " + username + "!Fighting!";

}

// 添加用户

@PostMapping("/user")

public String addUser(@RequestBody User user) {

return user.toString();

}

}

最后编写测试类 UserApplicationTests,具体代码如下:

package com.freejava.unittestrestfulapi;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.freejava.unittestrestfulapi.entity.User;

import org.junit.Before;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.http.MediaType;

import org.springframework.test.context.junit4.SpringRunner;

import org.springframework.test.web.servlet.MockMvc;

import org.springframework.test.web.servlet.MvcResult;

import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)

@SpringBootTest

public class UserApplicationTests {

@Autowired

WebApplicationContext wa;

MockMvc mockMvc;

@Before

public void setUp() throws Exception {

mockMvc = MockMvcBuilders.webAppContextSetup(wa).build();

}

@Test

public void testHi() throws Exception {

MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/hi")

.contentType(MediaType.APPLICATION_FORM_URLENCODED)

.param("username", "freephp"))

.andExpect(MockMvcResultMatchers.status().isOk())

.andDo(MockMvcResultHandlers.print())

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

@Test

public void testAddUser() throws Exception {

ObjectMapper objMapper = new ObjectMapper();

User user = new User();

user.setId((long) 1);

user.setName("CDC极客君");

user.setPassword("chengdu_is_great");

String s = objMapper.writeValueAsString(user);

MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders

.post("/user")

.contentType(MediaType.APPLICATION_JSON)

.content(s))

.andExpect(MockMvcResultMatchers.status().isOk())

.andReturn();

System.out.println(mvcResult.getResponse().getContentAsString());

}

}

总结

单元测试的主要目标是隔离软件系统的各部分,并逐个测试。这将确保每个部分都按照预期工作。单元测试有助于提高软件质量,而且也使得代码在修改后更容易维护。

文章链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: