springboot-单元测试梳理

springboot-单元测试梳理

今天主要是梳理springboot单元测试,基于MockMvc进行实战演示。

1. sringboot的测试类库

springboot本身就集成并且提供了非常多的实用工具以及注解。帮助开发者更加专注业务开发。

在这里主要介绍一下,springboot提供的测试方面的工具以及注解。主要包括两个模块:

  • spring-boot-test: 支持测试的核心内容。

  • spring-boot-test-autoconfigure:支持测试的自动化配置

我们只需要引入上面的依赖,springboot就会帮我们引入测试模块。我们只需要使用注解。就能轻松完成调用。

比如引入的测试类库有:

  • Junit: java单元测试的标准类库。
  • Spring Test & Spring Boot Test: Springboot功能集成化测试支持。
  • AssertJ: 一个轻量级的断言类库
  • Hamcrest: 一个对象匹配器类库
  • Mockito: 一个java Mock测试框架

2. Junit 4常用注解

  • @BeforeClass: 针对所有的测试,只执行一次,且方法还必须是static void

  • @Before: 初始化方法,执行当前测试类的每个测试方法前执行

  • @Test: 测试方法,在这里执行测试,也可以测试期望异常和超时时间(可以查看注解源码,查看使用属性)

  • @After: 释放资源,可以执行当前测试类的每一个测试方法后执行

  • @AfterClass: 针对所有的测试,只执行一次,且方法还必须是static void

  • @Ignore: 忽略此测试方法

  • @Runwith: 可以更改测试运行器

    一个单元测试类执行的顺序:

    @BeforeClass -> @Before -> @Test -> @After -> @AfterClass

    每个一个测试方法执行的调用顺序:

    @Before -> @Test -> @After

3. @RunWith @SpringBootTest

3.1 @RunWith

在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。在测试的时候,不指定使用的是默认的Runner。只能测试普通的java测试。不涉及spring web项目

在springboot中,SpringBoot 2.X 默认使用Junit4 我们使用注解@RunWith(SpringRunner.class)进行指定

public final class SpringRunner extends SpringJUnit4ClassRunner {
	public SpringRunner(Class<?> clazz) throws InitializationError {
		super(clazz);
	}
}

可以看到源码中,SpringRunner继承的SpringJUnit4ClassRunner

3.2 @SpringBootTest

@SpringBootTest注解是SpringBoot自1.4.0版本开始引入的一个用于测试的注解。他是属于SpringBoot的注解。

在进行web 环境测试的时候,我通常使用@SpringBootTest(classes = SpringbootApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

其中classes = SpringbootApplication.class是指定启动类、webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT是指定web环境

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest {
	@AliasFor("properties")
	String[] value() default {};
	@AliasFor("value")
	String[] properties() default {};
	Class<?>[] classes() default {};
	WebEnvironment webEnvironment() default WebEnvironment.MOCK;
	enum WebEnvironment {
		MOCK(false),
		RANDOM_PORT(true),
		DEFINED_PORT(true),
		NONE(false);
		private final boolean embedded;
		WebEnvironment(boolean embedded) {
			this.embedded = embedded;
		}
		public boolean isEmbedded() {
			return this.embedded;
		}
	}
}

查看源码发现:

SpringBootTest.WebEnvironment中一共有 4 个枚举值

  • MOCK: MOCK 是用的比较多的一个属性。它会判断,当你使用 servlet API 时,则使用模拟 servlet 环境创建 WebApplicationContext;如果是使用 WebFlux,则使用 ReactiveWebApplicationContext;否则,使用常规的 ApplicationContext。
  • RANDOM_PORT: 创建一个 web 应用程序上下文,可以是 reactive,也可以是 servlet。它将随机起一个端口并监听。到底是 reactive 还是 Servlet,它会自己根据上下文来判断
  • DEFINED_PORT: 创建一个(reactive) web 应用程序上下文,使用默认端口。针对这个属性,网上翻译的解读比较多,错误的也多。
  • NONE: 它不会指定 SpringApplication.setWebApplicationType。以非 Web 环境来运行。也就是非 Servlet 和 Reactive 环境

还有不少人的文章说,SpringBootTest.WebEnvironment.RANDOM_PORT 必须或通常和 @LocalServerPort 一起使用。我看源码的注释,解释并没有说跟@LocalServerPort 一起使用。我本身也不用。

为了减少篇幅,文中源码注释我都做了删除处理。感兴趣可以自己去查看。

4. MockMVC介绍

MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形势,转换到Controller的调用,是的测试速度快,不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。
基于RESTful 风格的springMVC测试。可以测试整个SpringMVC流程。从URL请求到Controller控制层的测试。

4.1 MockMvc

  • 作用: 服务器端SpringMVC测试的主入口点
  • 创建: 通过MockMVCBuilders的静态方法建造MockMVCBuilder,MockMvcMockMVCBuilder构造
  • 调用: perform(RequestBuilder rb),执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理,该方法的返回值是一个ResultActions。

4.2 MockMVCBuilders

  • 作用: 负责创建MockMVCBuilder对象
  • 创建: 有两种创建方式
    • standaloneSetup(Object… controllers): 通过参数指定一组控制器,这样就不需要从上下文获取了。
    • webAppContextSetup(WebApplicationContext wac):指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMv

4.3 MockMVCBuilder

  • 作用: MockMVCBuilder使用构造者模式来构建MockMvc的构造器
  • 创建: 主要的实现:StandaloneMockMvcBuilderDefaultMockMvcBuilder
  • 也可以直接使用静态工厂MockMvcBuilders创建即可,不需要直接使用上面两个实现类

4.4 MockMvcRequestBuilders

  • 作用: 用来构建Request请求的。
  • 创建: 其主要有两个子类MockHttpServletRequestBuilderMockMultipartHttpServletRequestBuilder(如文件上传使用),即用来Mock客户端请求需要的所有数据。

4.5 ResultActions

方法介绍:

  • andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确。
  • andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台。
  • andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理。
  • MockMvcResultMatchers
    • 用来匹配执行完请求后的**结果验证。
    • 果匹配失败将抛出相应的异常。
    • 包含了很多验证API方法。
  • MockMvcResultHandlers
    • 结果处理器,表示要对结果做点什么事情。
    • 比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。

4.6 MvcResult

  • 作用: 单元测试执行结果,可以针对执行结果进行自定义验证逻辑。采用断言验证数据。

5. 实战

完整的代码github路径: [email protected]:agreewangshuai/marsha_springboot.git(一个完整的springboot骨架集成统一结果返回、统一异常处理、统一日志框架以及单元测试、swagger)

添加Gradle依赖

testImplementation('org.springframework.boot:spring-boot-starter-test')

model

@ApiModel(value = "HelloWorld参数类")
public class HelloWorld {
	@ApiModelProperty(value = "要说内容")
	@NotNull(message = "不能为空或者\"\"")
	private String say;
	public String getSay() {
		return say;
	}
	public void setSay(String say) {
		this.say = say;
	}	
}

controller

@RestController
@RequestMapping("/agree")
@Api(value = "类描述",tags = {"hello demo功能"})
public class HelloWorldController {

	@Autowired
	HelloWorldService helloWorldService;
	
	@ApiOperation(value = "返回say hello",notes = "方法的具体描述信息")
	@GetMapping("/helloworld")
	public ResponseData getHello(@RequestBody  @Valid HelloWorld helloWorld) {
		String say = helloWorld.getSay();
		String sayhello = helloWorldService.sayhello(say);
		return new ResponseData().ok().data(sayhello);
	}
	
}

service

public interface HelloWorldService {
	/**
	 * @param say
	 * @return
	 */
   String sayhello(String say);
}

serviceImpl

@Service
public class HelloWorldServiceImpl implements HelloWorldService{
	@Override
	public String sayhello(String say) {
		return say;
	}
}

Test

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloWorldControllerTest {
	public static final Logger log = LoggerFactory.getLogger(HelloWorldControllerTest.class);

	@Autowired
	private WebApplicationContext ctx;
	private MockMvc mvc;

	@Before
	public void before() {
		mvc = MockMvcBuilders.webAppContextSetup(ctx).build();
	}
	@Test
	public void getHelloTest() throws Exception {
		RequestBuilder request = null;
		MvcResult mvcResult = null;
		HelloWorld helloWorld = new HelloWorld();
		// helloWorld.setSay("say agree");
        /**
        * 添加请求参数,此处示例传参格式为json
        * new JsonHelper().objectToJson(helloWorld)是我自己写的一个util类,将model转为json
        * 嫌麻烦也可以直接写成json格式:
        *  content("{\"say\":\"hello world\"}")
        */
		request = MockMvcRequestBuilders.get("/agree/helloworld").content(new JsonHelper().objectToJson(helloWorld))
				.contentType(MediaType.APPLICATION_JSON);
		mvcResult = mvc.perform(request).andExpect(status().isOk()).andReturn();
		// 获取数据
		JSONObject jsonObject = new JSONObject(mvcResult.getResponse().getContentAsString());
		log.info("输出返回结果:{}", jsonObject);
		// 加断言,判断属性值的问题。
		Assert.assertNotNull(jsonObject.get("code"));
		Assert.assertEquals(jsonObject.get("code"), "0");
		Assert.assertNotNull(jsonObject.get("message"));
		Assert.assertEquals(jsonObject.get("message"), "操作成功");
		Assert.assertNotNull(jsonObject.get("data"));
	}
}

小结:

本文单元测试主要描述了springboot单元测试的MockMvc。主要针对controller层的单元测试方法的介绍。目前暂时用的功能就这么多。后续有新的研究心得,会持续更新。如有纰漏,欢迎指正。谢谢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章