Spring Boot的单元测试(概念)

举报
别团等shy哥发育 发表于 2023/02/05 16:49:23 2023/02/05
【摘要】 @[toc](Spring Boot的单元测试) 一、前言  测试是系统开发中非常重要的工作,单元测试是在帮助开发人员编写高品质的程序、提升代码质量方面发挥了极大的作用。Spring Boot未测试提供了一个名为spring-boot-starter-test的Starter。使用Spring Initializr创建Spring Boot应用时,将自动添加spring-boot-start...

@[toc](Spring Boot的单元测试)

一、前言

  测试是系统开发中非常重要的工作,单元测试是在帮助开发人员编写高品质的程序、提升代码质量方面发挥了极大的作用。
Spring Boot未测试提供了一个名为spring-boot-starter-test的Starter。使用Spring Initializr创建Spring Boot应用时,将自动添加spring-boot-starter-test依赖。这样在测试时,就没有必要再添加额外的jar包。
spring-boot-starter-test主要提供了以下测试库。

  • JNnit:标准的单元测试Java应用程序
  • Spring Test&Spring Boot Test:针对Spring Boot应用程序的单元测试。
  • Mockito:Java mocking框架,用于模拟任何Spring管理的Bean,例如在单元测试中模拟一个第三方系统Service接口返回的数据,而不去真正调用第三方系统。
  • AssertJ:一个流畅的assertion库,同时也提供了更多的期望值与测试返回值的比较方式。
  • JSONassert:对JSON对象或JSON字符串断言的库。
  • JsonPath:提供类似于Xpath(一门在XML文档中查找信息的语言)那样的符号来获取JSOn数据片段。

二、Spring Boot单元测试程序模板

在这里插入图片描述
@RunWith注解是JUnit标准的一个注解,目的是告诉JUnit框架不要使用内置的方法进行单元测试,而应使用@RunWith指明的类来进行单元测试,所有的Spring单元测试总是使用SpringRunner.class。
@SpringBootTest用于Spring Boot应用测试,它默认根据包名逐级网上找,一直找到Spring Boot主程序(包含@SpringBootApplication注解的类),并在单元测试时启动该主程序来创建Sping上下文环境。

三、测试Service

   单元测试Service代码与通过Controller调用Service代码相比,需要特别考虑该Service是否依赖其他还未开发完毕的Service(第三方接口)。如果依赖其他还未开发完毕的Service,我们需要使用Mockito来模拟未完成的Service。
假设,在UserService中依赖CreditService(第三方接口)的getCredit方法获得用户积分

@Service
public class UserServiceImpl implements UserService{
	@AutoWired
	private CreditService creditService;
	@AutoWired
	UserRepository userRepository;
	@Override
	public int getCredit(Integer uid){
		User user=userRepository.getOne(uid);
		if(user!=null){
			return creditService.getCredit(uid);
		}else{
			return -1;
		}	
	}
}

  那么,我们如何测试UserService呢?问题是单元测试不能实际调用CreditService(因为CreditService是第三方系统),因此,我们在单元测试类需要使用Mockito的注解@MockBean自动注入Spring管理的Service,用来提供模拟实现,在Spring上下文中,CreditService实现已经被模拟实现代替了。

import org.mockito.BDDMockito;
import org.springframework.boot.test.mock.mockito.MockBean;
@RunWith(SpringRunner.class)
@Transactional
public class UserServiceTest{
	//注入要测试的service
	@Autowired
	private UserService userService;
	@MockBean
	private CreditService creditService;
	@Test
	public void testUserService(){
		int uid=1;
		int expectedCredit=50;
		//given是BDDMockito的一个静态方法,用来模拟一个Service方法调用返回,anyInt()表示可以传入任何参数,willReturn方法说明这个调用将返回50
		BDDMockito.given(creditService.getCredit(anyInt())).willReturn(expectedCredit);
		int credit=userService.getCredit(uid);
		//assert定义测试的条件,expectedCredit与credit相等时,assertEquals方法保持沉默,不等时抛出异常。
		assertEquals(expectedCredit,credit);
	}
}

四、测试Controller

  在Spring Boot应用中,可以单独测试Controller代码,用来验证与Controller相关的URL路径映射、文件上传、参数绑定、参数校验等特性。可以通过@WebMvcTest注解来完成Controller单元测试,当然也可以通过@SpringBootTest测试Controller。
通过@WebMvcTest测试Controller得代码模板如下:

import org.mockito.BDDMockito;
import org.springframework.boot.test.mock.mockito.MockBean;
@RunWith(SpringRunner.class)
//被测试的Controller
@WebMvcTest(UserController.class)
public class UserControllerTest{
//MockMvc是Spring提供的专用于测试Controller的类
@Autowired
private MockMvc mvc;
//用@MockBean模拟实现UserService,这是因为在测试Controller时,Spring容器并不会初始化@Service注解的Service类
@MockBean
private UserService userService;
	@Test
	public void testMvc(){
		int uid=1;
		int expectedCredit=50;
		//given是BDDMockito的一个静态方法,用来模拟一个Service方法调用返回。这里模拟userService
		BDDMockito.given(userService.getCredit(uid)).willReturn(50);
		//peform完成一次Controller的调用,Controller测试是一张模拟测试,实际上并未发起一次真正的HTTP请求;get方法模拟了一次Get请求,请求地址为/getCredit/{id},这里的{id}被其后的参数uid代替,因此请求路径是/getCredit/1;andExpect表示期望的返回结果。
		mvc.peform(get("/getCredit/{id}",uid))
		.andExpect(content().string(String.valueof(expectedCredit)));
	}
}

关键点:

需要注意的是,我们在使用@WebMvcTest注解测试Controller时,带有@Service以及别的注解组件类不会自动被扫描注册为Spring容器管理的Bean,而@SpringBootTest注解告诉Spring Boot去寻找一个主配置类(一个带@SpringBootApplication的类),并使用它来启动Spring应用程序上下文,注入所有Bean。另外,还需要注意的是,MockMvc用来在Servlet容器内对Controller进行单元测试,并未真正发起了HTTP请求调用Controller。
@WebMvcTest用于从服务器端对Controller层进行统一测试;如果需要从客户端与应用程序交互时,应该使用@SpringBootTest做集成测试。

五、模拟Controller请求

MockMvc的核心方法是:

public ResultActions perform(RequestBuilder requestBuilder)

RequestBuilder类可以通过调用MockMvcRequestBuilders的get、post、multipart等方法来模拟Controller请求,常用示例如下:
模拟一个get请求:

mvc.peform(get("/getCredit/{id}", uid));

模拟一个post请求:

mvc.peform(post("/getCredit/{id}", uid));

模拟文件上传:

mvc.peform(multipart("/upload").file("file", "文件内容".getBytes("UTF-8")));

模拟请求参数:

//模拟提交errorMessage参数
mvc.peform(get("/getCredit/{id}/{uname}", uid, uname).param("errorMessage", "用户名或密码错误"));
//模拟提交check
mvc.peform(get("/getCredit/{id}/{uname}", uid, uname).param("job", "收银员", "IT" ));

六、比较Controller请求返回的结果

我们知道,MockMvc的perform方法返回ResultActions实例,这个实例代表了请求Controller返回的结果。它提供了一系列andExpect方法来对请求Controller返回的结果进行比较。示例代码如下:

mvc.peform(get("/getOneUser/10"))
	.andExpect(status().isOk())  //期望请求成功,即状态码为200
	//期望返回内容是application/json
	.andExpect(content().contentType(MediaType.APPLICATION_JSON)) 
	//使用JsonPath比较返回的JSON内容
	.andExpect(jsonPath("$.name").value("chenheng")); //检查返回内容


除了上述对请求Controller返回的结果进行比较,还有如下的常见结果比较。
1.比较返回的视图

mvc.peform(get("/getOneUser/10"))
	.andExpect(view().name("/userDetail"));

2.比较模型

mvc.peform(post("/addOneUser"))
	.andExpect(status().isOk())
	.andExpect(model().size(1))
	.andExpect(model().attributeExists("oneUser"))
	.andExpect(model().attribute("oneUser", "chenheng"))

3.比较转发或重定向

mvc.peform(post("/addOneUser"))
	.andExpect(forwardedUrl("/user/selectAll")); //或者 redirectedUrl("/user/selectAll")

4.比较返回的内容

andExpect(content().string("测试很好玩")); //比较返回的字符串
andExpect(content().xml(xmlContent)); //返回内容是XML,并且与xmlContent(变量)一样
andExpect(content().json(jsonContent)); //返回内容是JSON,并且与jsonContent(变量)一样

七、实例

篇幅较长,见这篇文章:https://blog.csdn.net/qq_43753724/article/details/115559734

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。