在本小节中,将介绍如何通过拦截HTTP请求,通过录制的方式形成测试用例
首先,我们来尝试一下如下的一个简单场景
1)调用MeterSphere的某个无参GET接口
2)录制该接口的请求和返回
3) 利用录制的结果再次执行前述接口调用
这个,就有点像“狗咬尾巴”了
首先来编写如下的一个切面
package io.metersphere.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.*; @Aspect @Component @Slf4j public class DBMapperIntercept { public static List<MapperRecord> requests=new ArrayList<>(); MapperRecord mapperRecord = new MapperRecord(); @Pointcut("execution(public * io.metersphere..controller..*.*(..))") public void webLog(){} //指定切点前的处理方法 @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Exception { //获取request对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if(attributes==null){ log.info("请求为空"); return; } HttpServletRequest request = attributes.getRequest(); mapperRecord.setClassName(request.getRequestURI()); mapperRecord.setMethodName(request.getMethod()); } //指定切点前的处理方法 @AfterReturning(pointcut = "webLog()",returning = "result") public void doAfterReturning(Object result) { mapperRecord.setReturning(result); requests.add(mapperRecord); } }
定义了 @Pointcut("execution(public * io.metersphere..controller...(..))") 这个切面,表示拦截所有controller的public方法调用。然后,在Spring Boot中,可以通过以下这个方法获取当前Request的属性,
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
在获取到了当前HTTP请求的URI和调用类型(GET/POST)之后,我们将这些数据写入到一个record记录之中。
另外,我们还需要在@AfterReturning中拿到该HTTP接口的执行结果一并录入到记录中。
这样,我们拿到了一次HTTP服务接口测试所需的数据
1)服务的URL
2) 服务类型
3)预期结果
来写一个测试用例验证一下
package io.metersphere.controller; import com.alibaba.fastjson.JSON; import io.metersphere.TestApp; import io.metersphere.aspect.DBMapperIntercept; import io.metersphere.aspect.DBMock; import io.metersphere.aspect.MapperRecord; import io.metersphere.aspect.MockDBExtension; import net.javacrumbs.jsonunit.core.Option; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import java.util.List; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class HelloControllerTest extends TestApp { @BeforeEach public void testHelloController() throws Exception { String result= mockMvc.perform(get("/anonymous/hello")) .andExpect(status().isOk()) //返回的状态是200 .andDo(print()) //打印出请求和相应的内容 .andReturn().getResponse().getContentAsString(); //将相应的数据转换为字符串 ResultHolder resultHolder= JSON.parseObject(result,ResultHolder.class); assertThat(resultHolder.isSuccess()).isTrue(); } @Test public void testGetWithoutParameters() throws Exception { List<MapperRecord> recordList=DBMapperIntercept.requests; assertThat(recordList).isNotEmpty(); MapperRecord record=recordList.get(0); String result= runCase(record); assertThatJson(result).when(Option.IGNORING_EXTRA_FIELDS).isEqualTo(record.getReturning()); } }
在这个用例中,首先在@BeforeEach中调用了一个无参的GET方法,并且断言该接口调用成功。在这个过程中,通过切面的请求拦截,将获取到的数据保存在了requests之中,用于在@Test中执行用例。
再来看一下执行方法
public String runCase(MapperRecord record) throws Exception { String result = null; if (record.getMethodName().contains("GET")) { result = mockMvc.perform(get(record.getClassName())) .andExpect(status().isOk()) //返回的状态是200 .andDo(print()) //打印出请求和相应的内容 .andReturn().getResponse().getContentAsString(); //将相应的数据转换为字符串 } return result; }
目前这个方法非常简单,就是保证了上述请求能够顺利进行。
类似的,我们通过一个登录请求来展示如何拦截并实现带参POST请求的录制回放。
首先来看下这个请求
@PostMapping(value = "/signin") public ResultHolder login(@RequestBody LoginRequest request) { SecurityUtils.getSubject().getSession().setAttribute("authenticate", UserSource.LOCAL.name()); return userService.login(request); }
对于入参是这样定义的
@Getter @Setter public class LoginRequest { private String username; private String password; }
因此,我们会有如下的一个简单的测试用例
@Order(0) @Test public void loginSetup() throws Exception { LoginRequest loginRequest= new LoginRequest(); loginRequest.setUsername("admin"); loginRequest.setPassword("metersphere"); doPost("/signin",JSON.toJSONString(loginRequest)); }
这个用例会调用"/signin"接口,以"admin"用户成功登录。
在原先的案例中,我们在 public void doBefore(JoinPoint joinPoint) 方法中对GET请求进行了拦截并获取到了请求类型,根据请求类型为GET来进行相应的处理。
在这里,我们可以另外增加对POST类型的请求的处理。
if (request.getMethod().equalsIgnoreCase(RequestMethod.POST.name())) { Object[] args = joinPoint.getArgs(); mapperRecord.setArgs(args);
这里的Args主要对应的是存放在http.body中的请求体内容。
在原先的runcase方法中额外再增加对POST类型的支持
public String runCase(MapperRecord record) throws Exception { String result = null; String methodName = record.getMethodName(); if (methodName.contains("GET")) { result = doGet(record.getClassName()); } else if (methodName.contains("POST")) { result = doPost(record.getClassName(), JSON.toJSONString(record.getArgs()[0])); } return result; }
这里对MockMVC的GET和POST请求的发送接收提供了统一的方法doGet和doPost。此外,还在原先GET方法处理的基础上,对POST方法也提供了处理。
这里提醒读者注意的是,由于在切面中抓取到的入参是一个Object [], 而实际上真正的POST请求的参数是一个登录对象。因此,我们需要从入参数组中取出该对象,并进行序列化,从而提供给doPost一个正确的请求入参。
以下是doPost的一个简单实现
public String doPost(String url,String content ) throws Exception { return mockMvc.perform(post(url) .content(content) .contentType("application/json;charset=utf-8;") .session(session) ) .andDo(print()) //打印出请求和相应的内容 .andExpect(status().isOk()) //返回的状态是200 .andReturn().getResponse().getContentAsString(); }
测试用例-再次登录
在成功实现登录之后,我们再通过拦截录制得到的数据再次发起登录,有如下的用例,
@Order(1) @Test public void testLoginRequest() throws Exception { List<MapperRecord> recordList= DBMapperIntercept.requests; assertThat(recordList).isNotEmpty(); MapperRecord record=recordList.get(0); String result= runCase(record); assertThatJson(result).when(Option.IGNORING_EXTRA_FIELDS).isEqualTo(record.getReturning()); }
这是执行该用例后,从服务端返回的结果。
可以看到status =200,请求的返回体中带有success=true的字样,说明admin用户成功登录了。
这说明POST请求也成功被拦截和录制回放了。
至此,简单的GET/POST请求均达成了目标。
本文分享自微信公众号 - 软件测试那些事(antony-not-available),作者:风月同天测试人
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2020-12-27
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句