前言
今天研究了下SpringBoot-Junit做测试,发现此类博客很多,唯一的缺憾就是有点旧,Springboot都升级到1.5.6.RELEASE(idea自动集成版本),咱们也该与时俱进嘛,故此有了下文。在此感谢BraveWangDev,本文是在此篇文章基础之上进行的版本升级。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ideabook</groupId>
<artifactId>springboot_junit</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot_junit</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 测试模块,包括JUnit、Hamcrest、Mockito -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(一)基础代码以及注解解释
@RunWith(SpringRunner.class) //引入Spring-test框架支持,查看源码发现SpringRunner继承SpringJUnit4ClassRunner
@SpringBootTest(classes = MockServletConfig.class)
public class JunitApplicationTest {
private MockMvc mvc;
/*
一、 MockServletContext
由于这是一个新建项目,只有一个helloWord路由,所以我们使用MockServletContext来测试
使用MockServletContext来构建一个空的WebApplicationContext,这样我们创建的HelloController
就可以在@Before函数中创建并传递到MockMvcBuilders.standaloneSetup()函数中。
*/
@Before
public void setUp() throws Exception{
mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
}
/* 二、注意 status() content() equalTo()三个方法
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
*/
@Test
public void getHello() throws Exception{
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("hello!!!")));
}
/* 三、test方法注解介绍
//所有测试方法执行前.执行一次,作用:整体初始化
@BeforeClass
//所有测试方法完成后,执行一次,作用:销毁和释放资源
@AfterClass
//每个测试方法前执行,作用:初始化方法
@Before
//每个测试方法后执行,作用:还原现场
@After
// 测试方法超过1000毫秒,记为超时,测试失败
@Test(timeout = 1000)
// 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败
@Test(expected = Exception.class)
// 执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
@Ignore(“not ready yet”)
@Test
@RunWith
在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。
如果我们只是简单的做普通Java测试,不涉及spring Web项目,你可以省略@RunWith注解,这样系统会自动使用默认Runner来运行你的代码。
*/
}
在测试这部分代码的时候,遇到一个有趣的问题:运行getHello()方法的时候,idea输出窗口 要么静悄悄啥都没有,要么红彤彤一片,还以为是自己抄写的代码不准确,囧,断言。。。
controller 代码demo
@RestController
public class HelloController {
@RequestMapping(value = "/hello",method = RequestMethod.GET)
public String helloGet(){
return "hello!!!";
}
@RequestMapping(value = "/hello",method = RequestMethod.POST)
public String helloPost(String name){
return name + " ,hello!!!";
}
@RequestMapping(value = "/hello",method = RequestMethod.PUT)
public String helloPut(@RequestBody User user){
return user + " ,hello!!!";
}
@RequestMapping(value = "/hello",method = RequestMethod.DELETE)
public String helloDelete(@RequestBody User user){
return user + " ,hello!!!";
}
}
//restful风格API
(二)参数化测试
1. 断言介绍
/*
Assert断言方法介绍
1、assertEquals
函数原型:assertEquals([String message],expected,actual)
参数说明:
message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
expected是期望值,通常都是用户指定的内容。
actual是被测试的代码返回的实际值。
例:assertEquals("equals","1","1");
*/
private String getStr(){
return "asd";
}
@Test
public void testAssertEquals1(){
assertEquals("不相等","a",getStr());
assertEquals("不相等","as",getStr());
}
/*
函数原型:assertEquals([String message],expected,actual,tolerance)
参数说明:
message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
expected是期望值,通常都是用户指定的内容。
actual是被测试的代码返回的实际值。
tolerance是误差参数,参加比较的两个浮点数在这个误差之内则会被认为是相等的。
*/
@Test
public void testAssertEquals2(){
assertEquals("你又不乖了。。。",3.33,10/3,0.04);
}
/*
2、assertTrue
函数原型:assertTrue ([String message],Boolean condition)
参数说明:
message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
condition是待验证的布尔型值。
该断言用来验证给定的布尔型值是否为真,假如结果为假,则验证失败。当然,更有验证为假的测试条件:
函数原型:assertFalse([String message],Boolean condition)
该断言用来验证给定的布尔型值是否为假,假如结果为真,则验证失败。
例: assertTrue("true",1==1);
assertFalse("false",2==1);
*/
/*
3、assertNull
函数原型:assertNull([String message],Object object)
参数说明:
message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
object是待验证的对象。
该断言用来验证给定的对象是否为null,假如不为null,则验证失败。相应地,还存在能够验证非null的断言:
函数原型:assertNotNull([String message],Object object)
该断言用来验证给定的对象是否为非null,假如为null,则验证失败。
例:assertNull("null",null);
assertNotNull("not null",new String());
*/
@Test
public void testAssertNull(){
//assertNull("null",null);
// List list = new ArrayList();
// assertNull("new ArrayList is not null" ,list);
List list1 = Collections.emptyList();
assertNull("emptyList is not null",list1);
}
/*
4、assertSame
函数原型:assertSame ([String message], expected,actual)
参数说明:
message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
expected是期望值。
actual是被测试的代码返回的实际值。
该断言用来验证expected参数和actual参数所引用的是否是同一个对象,假如不是,则验证失败。
函数原型:assertNotSame ([String message], expected,actual)
该断言用来验证expected参数和actual参数所引用的是否是不同对象,假如所引用的对象相同,则验证失败。
例:assertSame("same",2,4-2);
assertNotSame("not same",2,4-3);
*/
@Test
public void testSame(){
//assertSame("same:这俩对象真不一样!!!",2,4-2);
//assertSame("same:这俩对象真不一样!!!",2L,4-2);
//assertSame("same:这俩对象真不一样!!!",new String("aaa"),new String("aaa"));
//assertSame("same:这俩对象一样!!!","aaa","aaa");
//assertSame("same:这俩对象真不一样!!!","aaa",new String("aaa"));
String a,b;
a = "aaaaa";
b = a;
assertSame("same:这俩对象一样!!!",a,b);
assertSame("same:这俩对象一样!!!",a,"aaaaa");
}
/*
5、fail
函数原型:fail([String message])
参数说明:
message是个可选的消息,假如提供,将会在发生错误时报告这个消息。
该断言会使测试立即失败,通常用在测试不能达到的分支上(如异常)。
测试方法是否进行完毕,如果没有则报错并中断,如下:i 的值不同,结果不同。
*/
@Test
public void testFail(){
for (int i = 0 ; i < 10 ; i ++){
if (i == 5){
fail("fail....");
}
System.out.println(i);
}
}
/*
6、assertArrayEquals
函数原型:assertArrayEquals([String message], Object[] expecteds,Object[] actuals)
参数说明:
message是个可选的消息,假如提供,将会在发生错误时报告这个消息,以及不一致的具体信息(数组长度、首次出现不一致的地方..)
expecteds 是多个期望值。
actuals是被测试的代码返回的实际值。
*/
@Test
public void testAssertArrayEquals(){
//assertArrayEquals("这俩数组不一样??",new Integer[]{1,3,5,7,9},new Integer[]{1,3,5});
/* java.lang.AssertionError: 这俩数组不一样??: array lengths differed, expected.length=5 actual.length=3 */
//assertArrayEquals("这俩数组不一样??",new Integer[]{1,3,5,7,9},new Integer[]{1,3,5,7,9});
//assertArrayEquals("这俩数组不一样??",new Integer[]{1,3,5,7,9},new Integer[]{2,4,6,8,10});
/*这俩数组不一样??: arrays first differed at element [0]; .... */
assertArrayEquals("这俩数组不一样??",new Integer[]{1,3,5,7,9},new Integer[]{1,3,6,8,10});
/* 这俩数组不一样??: arrays first differed at element [2];
Expected :5
Actual :6
*/
}
(三)参数化测试
@RunWith(Parameterized.class)
public class ParameterTest {
private String name;
private String password;
//1.初始化测试参数
@Parameters
public static Collection<?> data(){
System.out.println("===data===========");
return Arrays.asList(new Object[][]{
{"Test","123"},
{"ATest","12"},
{"BTest","123"},
});
}
//2.将@Parameters注解的方法中的Object数组中值的顺序对应
public ParameterTest(String name, String password) {
super();
System.out.println("===ParameterTest===================");
this.name = name;
this.password = password;
}
//3.逻辑测试
/*
import static org.junit.Assert.assertTrue;
assertTrue():断言,如果结果为true,啥事没有
否则,就会弹出“java.lang.AssertionError” + 错误信息(红红的错误很醒目,还以为代码写错了,囧~~~)
*/
@Test
public void test() {
System.out.println("==test==========");
assertTrue(name.contains("Test")==true);
assertTrue(password.equals("12"));
}
}
3.api测试
主要针对 get 、post方法的测试,而put、delete测试部分个人感觉 Junit支持的不够,限制太多。
测试的API对象, 仍然是HelloController。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=RANDOM_PORT)// 使用0表示端口号随机,也可以指定端口
public class RestApiTest {
private String dateReg;
private Pattern pattern;
private RestTemplate template = new TestRestTemplate().getRestTemplate();
@Value("${local.server.port}")// 注入端口号
private int port;
@Test
public void testApi_Get() throws URISyntaxException {
URI uri = new URI("http://tingapi.ting.baidu.com/v1/restserver/ting?" +
"format=json%E6%88%96xml&calback=&from=webapp_music" +
"&method=baidu.ting.billboard.billList&type=1&size=10&offset=0");
String result = template.getForObject(uri, String.class);
System.err.println(result);
}
@Test
public void testApi_Post() throws URISyntaxException {
String url = "http://localhost:"+port+"/hello";
System.out.println("");
System.err.println("url = " + url);
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("name", "value1");
String result = template.postForObject(url, map, String.class);
System.err.println(result);
}
//put测试方法,没有返回值,改用exchange
//exchange 以json形式请求,所以要定义Header,不然会报错,说是不支持这种MediaType。
//而对应的Api方法如下,换了方式就很难测试成功,也是醉醉哒!!
// @RequestMapping(value = "/hello",method = RequestMethod.PUT)
// public String helloPut(@RequestBody User user){}
@Test
public void testApi_Put() throws Exception {
String reqJsonStr = "{\"age\":\"200\",\"name\":\"zsssss\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
String url = "http://localhost:"+port+"/hello";
ResponseEntity<String> exchange = template.exchange(url, HttpMethod.PUT, entity, String.class);
String body = exchange.getBody();
System.err.println(body);
System.err.println(exchange);
}
@Test
public void testApi_Delete() throws Exception {
String reqJsonStr = "{\"age\":\"200\",\"name\":\"zsssss\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
String url = "http://localhost:"+port+"/hello";
ResponseEntity<String> exchange = template.exchange(url, HttpMethod.DELETE, entity, String.class);
String body = exchange.getBody();
System.err.println(body);
System.err.println(exchange);
}
// put delete 虽然说是测试通过了,但是限制忒多,感觉不实用。
// 如 entity的设置,貌似只能用json形式的参数。 其他形式试了很久也没成功,囧。。。。欢迎补充!!!
}
(四)打包测试
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* @Author: lzh
* @Description: 打包测试,就是新增一个类,将其他测试类配置在一起,运行这个类达到运行多个测试类的目的
* @Date: Created in 2017/8/10 14:20
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({JunitApplicationTest.class,JunitApplicationTest.class,SpringBootJunitApplicationTests.class})
public class SuiteTest {
// 类中不需要编写代码
}