spring-boot-单元测试
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
</dependency>
快捷键
ctrl + shift + t
service层 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {
@Autowired
private LearnService learnService;
@Test
public void getLearn() {
LearnResource learnResource = learnService.selectByKey(1001L);
Assert.assertThat(learnResource.getAuthor(), is("嘟嘟MD独立博客"));
}
/**
* 这样测试完数据就会回滚了,不会造成垃圾数据
* <p>
*
* @Transactional :单元个测试的时候如果不想造成垃圾数据,可以开启事物功能,记在方法或者类头部添加
* <p>
* @Rollback(false): @Rollback 表示事务执行完回滚,支持传入一个参数value,默认true 即回滚,false不回滚。
*/
@Test
@Transactional
// @Rollback(false)
public void add() {
LearnResource bean = new LearnResource();
bean.setAuthor("测试回滚");
bean.setTitle("回滚用例");
bean.setUrl("http://tengj.top");
learnService.save(bean);
}
}
controller 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mvc;
private MockHttpSession session;
@Before
public void setupMockMvc() {
//初始化MockMvc对象
mvc = MockMvcBuilders.webAppContextSetup(wac).build();
//构建session
session = new MockHttpSession();
User user = new User("root", "root");
//拦截器那边会判断用户是否登录,所以这里注入一个用户
session.setAttribute("user", user);
}
/**
* 新增教程测试用例
* <p>
* post 请求
*
* @throws Exception
*/
@Test
public void addLearn() throws Exception {
// 手动写 json
//String json = "{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";
// 前端传递的 json 格式,对象 转 json
LearnResource learnResource = new LearnResource();
learnResource.setAuthor("HAHAHAAs");
learnResource.setTitle("Spring");
learnResource.setUrl("http://tengj.top/");
Gson gson = new Gson();
String json = gson.toJson(learnResource);
//mockMvc.perform执行一个请求
//MockMvcRequestBuilders构造一个请求
mvc.perform(MockMvcRequestBuilders.post("/learn/add")
//发送的数据格式
.accept(MediaType.APPLICATION_JSON_UTF8)
//传json参数 通过 @RequestBody注解 接受的参数
.content(json.getBytes())
// 注入一个session
.session(session)
)
//andExpect添加执行完成后的断言
.andExpect(MockMvcResultMatchers.status().isOk())
//andDo添加一个结果处理器,表示要对结果做点什么事情
.andDo(MockMvcResultHandlers.print());//输出整个响应结果信息
}
/**
* 获取教程测试用例
* <p>
* get 请求
*
* @throws Exception
*/
@Test
public void qryLearn() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
//jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
.andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
.andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
.andDo(MockMvcResultHandlers.print());
}
/**
* get 请求
* @RequestParam 注解接收参数 ,也可以用于无注解修饰的对象接受参数,如果字段名一样,则自动进行赋值
*
* @throws Exception
*
*/
@Test
public void qryLearn() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/learn/queryLean")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session)
.param("id", "1001")
)
.andExpect(MockMvcResultMatchers.status().isOk())
//jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
.andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
.andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
.andDo(MockMvcResultHandlers.print());
}
/**
* get 请求
* 对象接收参数,但无注解修饰,字段一样,则赋值
*
* @throws Exception
*/
@Test
public void qryLearn() throws Exception {
LearnResource learnResource = new LearnResource();
learnResource.setId(999L);
learnResource.setAuthor("zhang");
learnResource.setTitle("zhang");
//learnResource.setUrl("http://www.baidu.com");
Map<String, Object> stringObjectMap = objectToMap(learnResource);
String requestParams = stringJoinAt(stringObjectMap);
mvc.perform(MockMvcRequestBuilders.get("/learn/queryLean" + requestParams)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
//jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不通过
.andExpect(MockMvcResultMatchers.jsonPath("$.author").value("官方SpriongBoot例子"))
.andExpect(MockMvcResultMatchers.jsonPath("$.title").value("官方SpriongBoot例子"))
.andDo(MockMvcResultHandlers.print());
}
/**
* object 转 map ,字段值为 null 的,则添加
*
* @param obj
* @return
*/
public static Map<String, Object> objectToMap(Object obj) {
Map<String, Object> map = new HashMap<>();
Class<?> clazz = obj.getClass();
System.out.println(clazz);
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
Object value = null;
try {
value = field.get(obj);
if (Objects.isNull(value)) {
continue;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
map.put(fieldName, value);
}
return map;
}
/**
* map 转为 & 符号进行拼接的 字符串
*
* @param map
* @return
*/
public static String stringJoinAt(Map<String, Object> map) {
StringBuffer content = new StringBuffer();
// 按照key做首字母升序排列
List<String> keys = new ArrayList<String>(map.keySet());
Collections.sort(keys, String.CASE_INSENSITIVE_ORDER);
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i).toString();
String value = map.get(key).toString();
// 空串不参与签名
if (StringUtils.isBlank(value)) {
continue;
}
content.append((i == 0 ? "" : "&") + key + "=" + value);
}
String signSrc = content.toString();
if (signSrc.startsWith("&")) {
signSrc = signSrc.replaceFirst("&", "");
}
return "?" + signSrc;
}
/**
* 修改教程测试用例
*
* @throws Exception
*/
@Test
public void updateLearn() throws Exception {
String json = "{\"author\":\"测试修改\",\"id\":1031,\"title\":\"Spring Boot干货系列\",\"url\":\"http://tengj.top/\"}";
mvc.perform(MockMvcRequestBuilders.post("/learn/update")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes())//传json参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
/**
* 删除教程测试用例
*
* @throws Exception
*/
@Test
public void deleteLearn() throws Exception {
String json = "[1031]";
mvc.perform(MockMvcRequestBuilders.post("/learn/delete")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes())//传json参数数组形式
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
}
单元测试事物没有回滚
- 查看MySQL当前默认的存储引擎:
mysql> show variables like '%storage_engine%';
- user表用了什么引擎
mysql> show create table user;
- 将user表修为InnoDB存储引擎
mysql> ALTER TABLE user ENGINE=INNODB;
以下情况,事务也不回滚
使用RANDOM_PORT或DEFINED_PORT这种安排隐式提供了一个真正的servlet环境, 在这种情况下,在服务器上启动的任何事务都不会回滚。
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@LocalServerPort
private int port;
断言 Assert.assertThat使用
例如:CoreMatchers.equalTo
assertThat( [value], [matcher statement] );
//value 是接下来想要测试的变量值;
//matcher statement 是使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明
//如果 value 值与 matcher statement 所表达的期望值相符,则测试成功,否则测试失败。
// 想判断某个字符串 s 是否含有子字符串 "developer" 或 "Works" 中间的一个
assertThat(s, anyOf(containsString("developer"), containsString("Works")));
// 匹配符 anyOf 表示任何一个条件满足则成立,类似于逻辑或 "||", 匹配符 containsString 表示是否含有参数子
// 联合匹配符not和equalTo表示“不等于”
assertThat( something, not( equalTo( "developer" ) ) );
// 联合匹配符not和containsString表示“不包含子字符串”
assertThat( something, not( containsString( "Works" ) ) );
// 联合匹配符anyOf和containsString表示“包含任何一个子字符串”
assertThat(something, anyOf(containsString("developer"), containsString("Works")));
字符相关匹配符
/**
* equalTo匹配符断言被测的testedValue等于expectedValue,
* equalTo可以断言数值之间,字符串之间和对象之间是否相等,相当于Object的equals方法
*/
assertThat(testedValue, equalTo(expectedValue));
/**equalToIgnoringCase匹配符断言被测的字符串testedString
*在忽略大小写的情况下等于expectedString
*/
assertThat(testedString, equalToIgnoringCase(expectedString));
/**equalToIgnoringWhiteSpace匹配符断言被测的字符串testedString
*在忽略头尾的任意个空格的情况下等于expectedString,
*注意:字符串中的空格不能被忽略
*/
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);
/**containsString匹配符断言被测的字符串testedString包含子字符串subString**/
assertThat(testedString, containsString(subString) );
/**endsWith匹配符断言被测的字符串testedString以子字符串suffix结尾*/
assertThat(testedString, endsWith(suffix));
/**startsWith匹配符断言被测的字符串testedString以子字符串prefix开始*/
assertThat(testedString, startsWith(prefix));
一般匹配符
/**nullValue()匹配符断言被测object的值为null*/
assertThat(object,nullValue());
/**notNullValue()匹配符断言被测object的值不为null*/
assertThat(object,notNullValue());
/**is匹配符断言被测的object等于后面给出匹配表达式*/
assertThat(testedString, is(equalTo(expectedValue)));
/**is匹配符简写应用之一,is(equalTo(x))的简写,断言testedValue等于expectedValue*/
assertThat(testedValue, is(expectedValue));
/**is匹配符简写应用之二,is(instanceOf(SomeClass.class))的简写,
*断言testedObject为Cheddar的实例
*/
assertThat(testedObject, is(Cheddar.class));
/**not匹配符和is匹配符正好相反,断言被测的object不等于后面给出的object*/
assertThat(testedString, not(expectedString));
/**allOf匹配符断言符合所有条件,相当于“与”(&&)*/
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16) ) );
/**anyOf匹配符断言符合条件之一,相当于“或”(||)*/
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
数值相关匹配符
/**closeTo匹配符断言被测的浮点型数testedDouble在20.0¡À0.5范围之内*/
assertThat(testedDouble, closeTo( 20.0, 0.5 ));
/**greaterThan匹配符断言被测的数值testedNumber大于16.0*/
assertThat(testedNumber, greaterThan(16.0));
/** lessThan匹配符断言被测的数值testedNumber小于16.0*/
assertThat(testedNumber, lessThan (16.0));
/** greaterThanOrEqualTo匹配符断言被测的数值testedNumber大于等于16.0*/
assertThat(testedNumber, greaterThanOrEqualTo (16.0));
/** lessThanOrEqualTo匹配符断言被测的testedNumber小于等于16.0*/
assertThat(testedNumber, lessThanOrEqualTo (16.0));
集合相关匹配符
/**hasEntry匹配符断言被测的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项*/
assertThat(mapObject, hasEntry("key", "value" ) );
/**hasItem匹配符表明被测的迭代对象iterableObject含有元素element项则测试通过*/
assertThat(iterableObject, hasItem (element));
/** hasKey匹配符断言被测的Map对象mapObject含有键值“key”*/
assertThat(mapObject, hasKey ("key"));
/** hasValue匹配符断言被测的Map对象mapObject含有元素值value*/
assertThat(mapObject, hasValue(value));
Junit基本注解介绍
@BeforeClass 在所有测试方法前执行一次,一般在其中写上整体初始化的代码
@AfterClass 在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码
@Before 在每个测试方法前执行,一般用来初始化方法(比如我们在测试别的方法时,类中与其他测试方法共享的值已经被改变,为了保证测试结果的有效性,我们会在@Before注解的方法中重置数据)
@After 在每个测试方法后执行,在方法执行完成后要做的事情
@Test(timeout = 1000) 测试方法执行超过1000毫秒后算超时,测试将失败
@Test(expected = Exception.class) 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败
@Ignore(“not ready yet”) 执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
@Test 编写一般测试用例
Test注解主要包含有expected属性,可指定所希望抛出的异常类型;也即如果抛出了指定的异常,则该测试被认为成功,否则为失败
@Test(expected = RuntimeException.class)
public void testException() {
ArrayList<Object> objects = new ArrayList<>();
Assert.assertThat(objects, notNullValue());
throw new RuntimeException("查询失败");
}
打包测试
测试套件,包含有一系列的需要测试的类;
我们用一个类,把所有的测试类整理进去,然后直接运行这个类,所有的测试类都会执行
@RunWith(Suite.class)
@Suite.SuiteClasses(LearnServiceTest.class)
public class SuitsTest {
}
MockMvc
@RunWith 在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。
assertThat一般与CoreMatchers一同使用
参考:
原文: http://tengj.top/2017/12/28/springboot12/
作者: 嘟嘟MD
作者:痴乙
来源:CSDN
原文:https://blog.csdn.net/fxbin123/article/details/80617754