录制回放实现测试用例自由

2021-06-02 06:59:10 浏览数 (1)

以后点点点就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请求均达成了目标。

0 人点赞