# Silky **Repository Path**: sophis/silky ## Basic Information - **Project Name**: Silky - **Description**: 丝滑的java自动化测试框架 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: 项目搬迁 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-02-10 - **Last Updated**: 2022-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 企业版接口自动化测试 ## 新的入门指南已搬到语雀,后续版本变更和介绍也请移步至 [silky指南](https://www.yuque.com/xiejiarong/silky/eaggv9) ## 更新日志 10-15 框架升级 09-25 支持动态token ## 模块介绍 * demo : 接口测试demo实战 * core: 框架的封装(无需关注) * web: 模拟的后端服务,执行demo需先启动web服务 ## 流程简介 大致还是沿用TestNg的数据驱动思想, 把数据以json文件的形式配置在 外部,接口使用注解,运行时由底层框架解析数据绑定到方法参数上,实现数据和代码的解耦。 > 与之前最大的不同是,不需要在每个测试方法中过多的编写http请求模板代码,全部以java接口形式,开发和QA > 提前约定好数据格式,QA只需声明接口,接口会自动生成代理,屏蔽掉复杂的http请求。 ##快速入门 这里以saas3.0内容管理服务端接口为例, 项目空间接口yapi: [项目空间](https://yapi.gaoding.com/project/1297/interface/api/cat_16020) + 假设测试 [创建项目](https://yapi.gaoding.com/project/1297/interface/api/98606) ,该接口定义如下图: ![image.png](https://i.loli.net/2021/09/13/IOfLwMpU3lTVdCA.png) uri: /projects method:post request body: { "name": "项目1", "userIds": [ 0 ], "type": "SENIOR" } } + 切换至模块 demo , 检查src/main/resources/application.yml 配置参数,主要有以下几个核心参数: ```yml 1.app: env: dev #稿定框架必填环境参数,值可自己修改 spring: application: name: qa-test #稿定框架必填参数, 值可自己修改 forest: variables: cm: http://localhost:8088/ # 代码中使用${cm}即可引用到此值 # 拦截器,不要改 interceptors: - com.gaoding.qa.auto.interceptor.SimpleInterceptor timeout: 10000 connect-timeout: 3000 # 全局请求头,可以配置token global: headers: Authorization: Bearer 78da296b-b63e-42fb-9f57-16d0bdf722b3 ``` + 创建接口定义, 新建类ProjectApi ```java package com.gaoding.qa.auto.api.cm; import com.dtflys.forest.annotation.BaseRequest; import com.dtflys.forest.annotation.DeleteRequest; import com.dtflys.forest.annotation.GetRequest; import com.dtflys.forest.annotation.JSONBody; import com.dtflys.forest.annotation.PostRequest; import com.dtflys.forest.annotation.PutRequest; import com.dtflys.forest.annotation.Query; import com.dtflys.forest.annotation.Var; import com.gaoding.saas.cm.common.dto.project.request.CreateProjectReqDTO; import com.gaoding.saas.cm.common.dto.project.request.SearchProjectReqDTO; import com.gaoding.saas.cm.common.dto.project.request.SetMemberReqDTO; import com.gaoding.saas.cm.common.dto.project.response.ProjectFolderRespDTO; import com.gaoding.saas.cm.common.dto.project.response.ProjectRespDTO; /** * 项目空间接口 * * @author mobai * @since 2021/9/13 00:09 */ @BaseRequest(baseURL = "${cm}/projects") public interface ProjectApi { @PostRequest(dataType = "json") ProjectFolderRespDTO create(@JSONBody CreateProjectReqDTO reqDTO); @DeleteRequest("/${id}") void delete(@Var("id") Long id); @PutRequest("/${id}/members") void setMembers(@Var("id") Long id, @JSONBody SetMemberReqDTO setMemberReqDTO); @GetRequest("/search") ProjectRespDTO search(@Query SearchProjectReqDTO request); } ``` 以下主要介绍几个核心注解的使用: 1.@BaseRequest :放在接口头部, baseUrl属性可以用来设置该接口的域名前缀,如上最终值为 http://localhost:8088/projects 2. @PostRequest @DeleteRequest @PutRequest @GetRequest 从注解名可知道这是模拟Http请求的功能注解,标记在方法上,表示该方法最终会是对应一次Http请求,value值表示 对应的接口uri,可以使用路径模板表达式,如 @DeleteRequest("/${id}") 表示id为变量值,若需要动态赋值,则在方法参数中使用注解@Var标注对应参数,可以实现路径的动态参数赋值 dataType表示该http请求的返回值反序列化方式, 可不写。 3.@JSONBody: 标记在方法参数上,表示该参数最终会被序列化成json字符串,如 ```java @Post( url = "http://localhost:8080/hello/user", contentType = "application/json" ) String send(@JSONBody User user); // User user=new User(); user.setName("小白"); user.setId(1); xxx.send(user) ``` 最终请求渲染成: ```java POST http://localhost:8080/hello/user HEADER: Content-Type: application/json BODY: {"id":1,"name":"小白"} ``` 4. @Query: 标记在方法参数上,表示该参数会转成路径参数,如下: ```java /** * 使用 @Query 注解,可以直接将该注解修饰的参数动态绑定到请求url中 * 注解的 value 值即代表它在url的Query部分的参数名 */ @getRequest("http://localhost:8080/abc) String send2(@Query("a") String a, @Query("b") String b); // xxx.send2("1","2"); ``` 最终效果为: ```java GET http://localhost:8080/abc?a=1&b=2 ``` 也可以使用一个java类参数: ```java /** * 使用 @Query 注解,可以修饰 Map 类型的参数 * 很自然的,Map 的 Key 将作为 URL 的参数名, Value 将作为 URL 的参数值 * 这时候 @Query 注解不定义名称 */ @Get("http://localhost:8080/abc?id=0") String send2(@Query Map map); /** * @Query 注解也可以修饰自定义类型的对象参数 * 依据对象类的 Getter 和 Setter 的规则取出属性 * 其属性名为 URL 参数名,属性值为 URL 参数值 * 这时候 @Query 注解不定义名称 */ @Get("http://localhost:8080/abc?id=0") String send2(@Query UserInfo user); ``` + 模块demo ,切换到src/main/test下 , java包下用来写测试用例, resources下用来放对应的测试数据 1. 新建测试用例CmTest,代码如下 ```java package com.gaoding.qa.qiye.demo; import com.gaoding.qa.auto.QiyeApiTestApplication; import com.gaoding.qa.auto.annotation.TestObject; import com.gaoding.qa.auto.api.cm.ProjectApi; import com.gaoding.qa.auto.base.BaseApiTest; import com.gaoding.saas.cm.common.dto.project.request.CreateProjectReqDTO; import com.gaoding.saas.cm.common.dto.project.request.SearchProjectReqDTO; import com.gaoding.saas.cm.common.dto.project.request.SetMemberReqDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.testng.annotations.Test; /** * 内容管理api自动化测试demo * @author mobai * @since 2021/9/13 00:27 */ @SpringBootTest(classes = QiyeApiTestApplication.class) public class CmTest extends BaseApiTest { @Autowired ProjectApi projectApi; /** * 创建项目空间 */ @Test(dataProvider = "object") @TestObject(value = "cm/project/create.json") public void create(CreateProjectReqDTO reqDTO){ projectApi.create(reqDTO); } /** * 删除项目 * */ @Test(dataProvider = "object") @TestObject(value = "cm/project/delete.json") public void delete( Long id) { projectApi.delete(id); } /** * 设置项目成员 * */ @Test(dataProvider = "object") @TestObject(value = "cm/project/setMember.json") public void setMembers( Long id, SetMemberReqDTO setMemberReqDTO){ projectApi.setMembers(id,setMemberReqDTO); } @Test(dataProvider = "object") @TestObject(value = "cm/project/search.json") public void search(SearchProjectReqDTO request){ System.out.println(projectApi.search(request)); } } ``` 介绍几个核心注解的作用 : 1. @Test: 每个测试用例要运行必须要在方法上标记这个注解, 属性dataProvider表示它的数据来源,这边统一都写成object(框架已封装) 2. @TestObject: 每个测试用例需要绑定外部的json数据都需要标记这个注解,属性 value为对应文件的位置, 如value为cm/project/setMember.json, 则运行时会从src/main/test/resources/testObject/cm/project/setMember.json这个位置加载对应文件。 3. 每个方法参数必须和json文件中的data内部json对象个数匹配且命名一致,否则会抛出异常。 + 配置json文件,json数据格式如下: ```json --- json文件为一个数组形式,数组内有几个对象默认该用例会执行几次,如下json则表示绑定该文件的 测试用例会执行两次。 [{ "data": { --- reqDTO 命名必须与方法一致 "reqDTO": { "name": "项目1", "userIds": [ 0 ], "type": "SENIOR" } }, --- ignore 表示该数据是否跳过测试,配置为true则本条数据跳过测试 "ignore": false, --- httpStatus 表示该数据预期的http响应码, 若最终http请求与之不一致,该次测试会抛出异常 "httpStatus": 404 }, { "data": { "reqDTO": { "name": "项目1", "userIds": [ 0 ], "type": "SENIOR" } }, "ignore": false, "httpStatus": 200 } ] ``` ## 项目结构 ```java |-qiyeApiTest |-core 框架模块(无需关注) |-demo demo用例 |- src |- main |- java |- com.gaoding.qa.auto |- api |- cm |- ProjectApi 项目空间接口定义 QiyeApiTestApplication # 核心启动类 |- resources |- application.yml 全局配置文件 |- web web模块(rest接口) |- src |- main |- java |- com.gaoding.qa.auto |- api |- cm |- ProjectController 项目空间接口服务 QiyeApiTestApplication # 核心启动类 |- resources |- application.yml 全局配置文件,配置服务端端口 ``` ## 动态token 为了应对更复杂的测试场景,假设有 a->b->c顺序的请求, 需要给每一步设置单独的用户请求,即动态token, 原来的配置文件中配置headers便显得不好控制,而如今你可以这么写: ```java public void test(){ //模拟 第一次a 的http请求,此时headers还是以yml中配置的为主 a.get(); //此时可能我需要换成另外一个用户身份,可以如下这样写 HttpHelper.generateToken("xxx","xxx"); //此时b请求的请求头token就会以上面具体设置的账户为主,但是只会在b请求生效 b.get(); // c请求还是会使用yml中的配置,如果需要动态token,再重复以上步骤 //即, 动态token只会在声明之后的第一个http请求生效 c.get(); } ```