技术标签: spring boot 单元测试 http Spring Boot
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
**test**表示依赖的组件仅仅参与测试相关的工作,包括测试代码的编译和执行,不会被打包包含进去;spring-boot-starter-test是Spring Boot提供项目测试的工具包,内置了多种测试工具,方便我们在项目中做单元测试、集成测试。
引入spring-boot-starter-test后,该启动器提供了常见的单元测试库:
测试库 | 描述 |
---|---|
JUnit | 一个Java语言的单元测试框架 |
Spring Test & Spring Boot Test | 为Spring Boot应用提供集成测试和工具支持 |
AssertJ | 支持流式断言的Java测试框架 |
Hamcrest | 一个匹配器库 |
Mockito | 一个java mock框架 |
JSONassert | 一个针对JSON的断言库 |
JsonPath | JSON XPath库 |
①单元测试代码写在src/test/java目录下
②单元测试类命名为*Test,前缀为要测试的类名
Spring Boot中单元测试类写在src/test/java目录下,可以手动创建具体测试类,如果是IDEA,则可以通过IDEA快捷键自动创建测试类
系统 | 快捷键 |
---|---|
Window | Ctrl+Shift+T |
MAC | ⇧⌘T |
一个测试方法主要包括三部分:
1)setup
2)执行操作
3)验证结果
JUnit运行使用Spring的测试支持。SpringRunner是SpringJUnit4ClassRunner的新名字,这样做的目的仅仅是为了让名字看起来更简单一点。
该注解为SpringApplication创建上下文并支持Spring Boot特性。
参数 | 描述 |
---|---|
Mock | 加载WebApplicationContext并提供Mock Servlet环境,嵌入的Servlet容器不会被启动 |
RANDOM_PORT | 加载一个EmbeddedWebApplicationContext并提供一个真实的servlet环境。嵌入的Servlet容器将被启动并在一个随机端口上监听 |
DEFINED_PORT | 加载一个EmbeddedWebApplicationContext并提供一个真实的servlet环境。嵌入的Servlet容器将被启动并在一个默认的端口上监听(application.properties配置端口或者默认端口8080) |
NONE | 使用SpringApplication加载一个ApplicationContext,但是不提供任何的servlet环境 |
用于自动配置MockMvc
该注解被限制为一个单一的controller,需要利用@MockBean去Mock合作者(如service)。
在开发中,有时候接口需要去远程调用其他的接口,而在单元测试中,如果出现别人的接口没有开发完成或者远程服务不可用的情况,那么单元测试就不能进行下去,这时候就需要使用到下面的测试方法了,可以指定远程调用方法返回一个指定符合规则的返回值,不用受限于远程接口的返回值,让单元测试能够进行下去
在ApplicationContext里为一个bean定义一个Mockito mock;使用此注解注入的类,表明类中的所有方法都使用自定义返回的值,这样在测试的时候就不会真的去调用远程接口,而是返回一个预设的值
调用方法定义:TicketBuyOrderInfoVo getBuyOrderInfoVoByOrderId(@RequestParam(“orderId”) String orderId);
@MockBean
protected AirTicketBuyDataServiceFeignClient buyDataServiceFeignClient;
@Test
public void addComsumptions() {
String json = "";
TicketBuyOrderInfoVo orderInfoVo = GsonUtil.gson.fromJson(json, TicketBuyOrderInfoVo.class);
when(buyDataServiceFeignClient.getBuyOrderInfoVoByOrderId(anyString())).thenReturn(orderInfoVo);
}
thenReturn(orderInfoVo):设置调用指定方法之后的返回值,这里是返回一个TicketBuyOrderInfoVo对象;而不使用when().thenReturn()写法,直接调用,方法返回值为Null。
定制化Mock某些方法。使用@SpyBean除了被打过桩的函数,其它的函数都将真实返回。
使用此注解注入的类,表明类中的某一个方法使用自定义返回的值,在测试时,如果使用到了多个方法,那么只是遵循@SpyBean写法的方法会返回自定义的值,在使用时在使用方法上和@MockBean类似,不过写法有所区别
调用方法定义:AutoOrder getAutoOrderByOrderId(String orderId, Date bizTime);
@SpyBean
protected IAutoTicketDao autoTicketDao;
@Test
public void addComsumptionsOut() {
String json = "";
AutoOrder autoOrder = GsonUtil.gson.fromJson(json1, AutoOrder.class);
doReturn(autoOrder).when(autoTicketDao).getAutoOrderByOrderId(anyString(), anyObject());
}
doReturn(autoOrder):设置的调用指定方法之后的返回值;遵循doReturn().when()写法的会返回自定义的返参,如果直接调用的话还是会真正调用服务;而在使用时,方法的入参也有所改变。
AutoOrder getAutoOrderByOrderId(String orderId, Date bizTime),如果参数是String类型,测试时入参为anyString(),如果为对象类型,入参为anyObject(),布尔类型为anyBoolean(),Integer类型为anyInt()…,还有很对类型相对应的方法,而如果方法的入参是一个固定的值,那么在测试时也要写为相同的值
如果只是简单的做普通Java测试,不涉及Spring Web项目,可以省略@RunWith注解,根据需要选择不同的Runner来运行测试代码
在所有测试方法执行前执行一次,一般在其中写上整体初始化的代码
在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码
// 注意这两个都是静态方法
@BeforeClass
public static void testStart(){}
@AfterClass
public static void testEnd(){}
在每个方法测试前执行,一般用来初始化方法(比如:在测试别的方法时,类中与其他测试方法共享的值已经被改变,为了保证测试结果的有效性,会在@Before注解的方法中重置数据)
在每个测试方法执行后,在方法执行完成后要做的事情
测试方法执行超过1000毫秒后算超时,测试将失败
测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败
执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
编写一般测试用例用
在Junit中有很多个Runner,他们负责调用测试代码,每一个Runner都有各自的特殊功能,你根据需要选择不同的Runner来运行测试代码
下面是最简单的单元测试写法,顶部只要@RunWith(SpringRunner.class)和@SpringBootTest即可,想要执行的时候,鼠标放在对应的方法,右键选择run该方法即可。
@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {
@Autowired
private LearnService learnService;
@Test
public void getLearn(){
LearnResource learnResource = learnService.selectByKey(1001L);
// assertThat断言
Assert.assertThat(learnResource.getAuthor(),is("測試"));
}
}
单元测试回滚:单元个测试的时候如果不想造成垃圾数据,可以开启事物功能,在方法或者类头部添加@Transactional注解即可。
关闭回滚:在方法上加上@Rollback(false)注解即可。@Rollback表示事务执行完回滚,支持传入一个参数value,默认true即回滚,false不回滚。
如果使用的数据库是Mysql,有时候会发现加了注解@Transactional也不会回滚,那么就要查看数据库的默认引擎是不是InnoDB,如果不是就要改成InnoDB。
Spring测试框架提供MockMvc对象,可以在不需要客户端-服务端请求的情况下进行MVC测试,完全在服务端这边就可以执行Controller的请求,跟启动了测试服务器一样。(不必启动工程测试这些接口)
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便。
测试开始之前需要建立测试环境,setup方法被@Before修饰。通过MockMvcBuilders工具,使用WebApplicationContext对象作为参数,创建一个MockMvc对象。
@Controller
@RequestMapping("/learn")
public class LearnController extends AbstractController {
@Autowired
private LearnService learnService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping(value = "/add", method = RequestMethod.POST)
@ResponseBody
public AjaxObject addLearn(@RequestBody LearnResource learn){
learnService.save(learn);
return AjaxObject.ok();
}
}
@RunWith(SpringRunner.class)
// 这里的Application是springboot的启动类名
@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc
public class StyleControllerTest {
@Autowired
private WebApplicationContext context;
@Autowired
private MockMvc mockMvc;
private MockHttpSession session;
private ObjectMapper mapper = new ObjectMapper();
@Before
public void setupMockMvc() throws Exception {
// 初始化MockMvc对象
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
// 拦截器那边会判断用户是否登录,所以这里注入一个用户
session = new MockHttpSession();
User user = new User("root","root");
session.setAttribute("user",user);
}
/**
* 新增教程测试用例
* @throws Exception
*/
@Test
public void addLearn() throws Exception{
String json="{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";
mvc.perform(MockMvcRequestBuilders.post("/learn/add")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes()) //传json参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
@Test
public void testSend() throws Exception {
Long id =1l;
//调用接口,传入添加的用户参数
mockMvc.perform(MockMvcRequestBuilders.get("/style/listStyleById")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(mapper.writeValueAsString(id)))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultHandlers.print());
}
}
mockMvc.perform:执行一个请求
MockMvcRequestBuilders.get(“/user/1”):构造一个请求,Post请求就用.post方法
contentType(MediaType.APPLICATION_JSON_UTF8):代表发送端发送的数据格式是application/json;charset=UTF-8
accept(MediaType.APPLICATION_JSON_UTF8):代表客户端希望接受的数据类型为application/json;charset=UTF-8
session(session):注入一个session,这样拦截器才可以通过
ResultActions.andExpect:添加执行完成后的断言
ResultActions.andExpect(MockMvcResultMatchers.status().isOk()):方法看请求的状态响应码是否为200如果不是则抛异常,测试不通过
andExpect(MockMvcResultMatchers.jsonPath(“$.author”).value(“md”)):这里jsonPath用来获取author字段比对是否为md,不是就测试不通过
ResultActions.andDo:添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息
以下是Feign接口的单元测试示例,①启动项目,可以测试本jar提供的服务。②不启动服务,改为远程服务地址,可以测试远程jar提供的服务
类似实际应用调用相关服务一样
@FeignClient(value = "loan-server", url = "http://localhost:9070/")
public interface UserServiceFeignClient extends UserServiceClient {
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = UserControllerTest.class)
@Import({ FeignAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class })
@EnableFeignClients(clients = UserServiceFeignClient.class)
public class UserControllerTest {
@Autowired
private UserServiceFeignClient userServiceFeignClient;
@Test
public void getUser() {
User user = userServiceFeignClient.getSDKUserById(1);
System.out.println(user);
}
}
使用RestTemplate发起GET或POST请求,其中@SpringBootTest这两行注释掉就不启动SpringBoot容器直接进行远程调用测试
@RunWith(SpringJUnit4ClassRunner.class)
public class LoanControllerTest {
private final static String url = "http://localhost:9070/";
private static RestTemplate restTemplate = new RestTemplate();
@Test
public void test(){
ResponseEntity<String> response = restTemplate.exchange(url + "/loan/getLoanById?id=1",
HttpMethod.GET,
new HttpEntity(null),
String.class);
System.out.println("result: " + response.getBody());
}
}
assertThat基本语法:assertThat( [value], [matcher statement] );
参数 | 描述 |
---|---|
value | 想要测试的变量值 |
matcher statement | 使用Hamcrest匹配符来表达的对前面变量所期望的值的声明,如果value值与matcher statement所表达的期望值相符,则测试成功,否则测试失败 |
(1)以前JUnit提供了很多的assertion语句,如:assertEquals、assertNotSame、assertFalse、assertTrue、assertNotNull、assertNull等,现在有了JUnit 4.4,一条assertThat即可以替代所有的assertion语句,这样可以在所有的单元测试中只使用一个断言方法,使得编写测试用例变得简单,代码风格变得统一,测试代码也更容易维护
(2)assertThat使用了Hamcrest的Matcher匹配符,用户可以使用匹配符规定的匹配准则精确的指定一些想设定满足的条件,具有很强的易读性,而且使用起来更加灵活
使用匹配符Matcher和不使用之间的比较
使用匹配符Matcher和不使用之间的比较
// 想判断某个字符串“s”是否含有子字符串“developer”或“Works”中间的一个
// JUnit 4.4以前的版本:assertTrue(s.indexOf("developer")>-1||s.indexOf("Works")>-1 );
// JUnit 4.4:
// 匹配符anyOf表示任何一个条件满足则成立,类似于逻辑或“||”, 匹配符containsString表示是否含有参数
assertThat(s, anyOf(containsString("developer"), containsString("Works")));
(3)assertThat不再像assertEquals那样,使用比较难懂的“谓宾主”语法模式(如:assertEquals(3, x);),相反,assertThat使用了类似于“主谓宾”的易读语法模式(如:assertThat(x,is(3));),使得代码更加直观、易读。
(4)可以将这些 Matcher 匹配符联合起来灵活使用,达到更多目的
assertThat(testedValue, equalTo(expectedValue));
assertThat(testedString, equalToIgnoringCase(expectedString));
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);
assertThat(testedString, containsString(subString) );
assertThat(testedString, endsWith(suffix));
assertThat(testedString, startsWith(prefix));
方法 | 描述 |
---|---|
equalTo(expectedValue) | 匹配符断言被测的testedValue等于expectedValue。断言判断数值、字符串和对象之间是否相等,相当于Object的equals方法 |
equalToIgnoringCase(expectedString) | 匹配符断言被测的字符串testedString,在忽略大小写的情况下等于expectedString |
equalToIgnoringWhiteSpace(expectedString) | 匹配符断言被测的字符串testedString,在忽略头尾的任意个空格的情况下等于expectedString。注意:字符串中的空格不能被忽略 |
containsString(subString) | 匹配符断言被测的字符串testedString包含子字符串subString |
endsWith(suffix) | 匹配符断言被测的字符串testedString以子字符串suffix结尾 |
startsWith(prefix) | 匹配符断言被测的字符串testedString以子字符串prefix开始 |
assertThat(object,nullValue());
assertThat(object,notNullValue());
assertThat(testedString, is(equalTo(expectedValue)));
assertThat(testedValue, is(expectedValue));
assertThat(testedObject, is(Cheddar.class));
assertThat(testedString, not(expectedString));
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16)));
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8)));
方法 | 描述 |
---|---|
nullValue() | 匹配符断言被测object的值为null |
notNullValue() | 匹配符断言被测object的值不为null |
is(equalTo(expectedValue)) | 匹配符断言被测的object等于后面给出匹配表达式 |
is(expectedValue) | 匹配符is(equalTo(x))的简写,断言testedValue等于expectedValue |
is(Cheddar.class) | 匹配符is(instanceOf(SomeClass.class))的简写,断言testedObject为Cheddar的实例 |
not(expectedString) | not匹配符和is匹配符正好相反,断言被测的object不等于后面给出的object |
allOf(greaterThan(8), lessThan(16)) | allOf匹配符断言符合所有条件,相当于“与”(&&) |
anyOf(greaterThan(16), lessThan(8)) | anyOf匹配符断言符合条件之一,相当于“或”(||) |
assertThat(testedDouble, closeTo( 20.0, 0.5 ));
assertThat(testedNumber, greaterThan(16.0));
assertThat(testedNumber, lessThan (16.0));
assertThat(testedNumber, greaterThanOrEqualTo (16.0));
assertThat(testedNumber, lessThanOrEqualTo (16.0));
方法 | 描述 |
---|---|
closeTo(20.0, 0.5) | closeTo匹配符断言被测的浮点型数testedDouble在20.0¡À0.5范围之内 |
greaterThan(16.0) | greaterThan匹配符断言被测的数值testedNumber大于16.0 |
lessThan (16.0) | lessThan匹配符断言被测的数值testedNumber小于16.0 |
greaterThanOrEqualTo (16.0) | greaterThanOrEqualTo匹配符断言被测的数值testedNumber大于等于16.0 |
lessThanOrEqualTo (16.0) | lessThanOrEqualTo匹配符断言被测的testedNumber小于等于16.0 |
assertThat(mapObject, hasEntry("key", "value" ) );
assertThat(iterableObject, hasItem (element));
assertThat(mapObject, hasKey ("key"));
assertThat(mapObject, hasValue(value));
方法 | 描述 |
---|---|
hasEntry(“key”, “value”) | hasEntry匹配符断言被测的Map对象mapObject含有一个键值为“key”对应元素值为“value”的Entry项 |
hasItem(element) | hasItem匹配符表明被测的迭代对象iterable Object含有元素element项,则测试通过 |
hasKey (“key”) | hasKey匹配符断言被测的Map对象mapObject含有键值“key” |
hasValue(value) | hasValue匹配符断言被测的Map对象mapObject含有元素值value |
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf