以后点点点就OK了
在本小节中,将介绍如何通过拦截HTTP请求,通过录制的方式形成测试用例
首先,我们来尝试一下如下的一个简单场景
1)调用MeterSphere的某个无参GET接口
2)录制该接口的请求和返回
3) 利用录制的结果再次执行前述接口调用
这个,就有点像“狗咬尾巴”了
用切面来抓取HTTP请求
首先来编写如下的一个切面
代码语言:javascript复制 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)预期结果
测试用例
来写一个测试用例验证一下
代码语言:javascript复制 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中执行用例。
再来看一下执行方法
代码语言:javascript复制 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请求
类似的,我们通过一个登录请求来展示如何拦截并实现带参POST请求的录制回放。
首先来看下这个请求
代码语言:javascript复制 @PostMapping(value = "/signin")
public ResultHolder login(@RequestBody LoginRequest request) {
SecurityUtils.getSubject().getSession().setAttribute("authenticate", UserSource.LOCAL.name());
return userService.login(request);
}
对于入参是这样定义的
代码语言:javascript复制 @Getter
@Setter
public class LoginRequest {
private String username;
private String password;
}
因此,我们会有如下的一个简单的测试用例
代码语言:javascript复制 @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类型的请求的处理。
代码语言:javascript复制 if (request.getMethod().equalsIgnoreCase(RequestMethod.POST.name())) {
Object[] args = joinPoint.getArgs();
mapperRecord.setArgs(args);
这里的Args主要对应的是存放在http.body中的请求体内容。
runcase的处理
在原先的runcase方法中额外再增加对POST类型的支持
代码语言:javascript复制 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的一个简单实现
代码语言:javascript复制 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();
}
测试用例-再次登录
在成功实现登录之后,我们再通过拦截录制得到的数据再次发起登录,有如下的用例,
代码语言:javascript复制 @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请求均达成了目标。