Apitest 接口自动化测试工具

测试工具 创建于:2022-05-17
  
安装   
Apitest工具是单可执行文件,不需要安装,放到PATH路径下面就可以直接运行:   # linux   curl -L -o apitest https://github.com/sigoden/apitest/releases/latest/download/apitest-linux    chmod +x apitest   sudo mv apitest /usr/local/bin/   # macos   curl -L -o apitest https://github.com/sigoden/apitest/releases/latest/download/apitest-macos   chmod +x apitest   sudo mv apitest /usr/local/bin/   # npm   npm install -g @sigodenjs/apitest


  开始使用   编写
测试文件 httpbin.jsona   {     test1: {       req: {         url: "https://httpbin.org/anything",         query: {           k1: "v1",         },       },       res: {         body: { @partial           args: {             "k1": "v2", // 注意,这儿应该是"v1", 我们故意写"v2"以测试Apitest的反应           },           url: "https://httpbin.org/anything?k1=v1",         }       }     }   }

  执行如下命令测试接口。   apitest httpbin.jsona

  其结果如下:   main     test1 (2.554) ?     main.test1.res.body.args.k1: v2 ≠ v1     {       "req": {         "url": "https://httpbin.org/anything",         "query": {           "k1": "v1"         }       },       "res": {         "headers": {           "date": "Thu, 17 Jun 2021 15:01:51 GMT",           "content-type": "application/json",           "content-length": "400",           "connection": "close",           "
server": "gunicorn/19.9.0",           "access-control-allow-origin": "*",           "access-control-allow-credentials": "true"         },         "status": 200,         "body": {           "args": {             "k1": "v1"           },           "data": "",           "files": {},           "form": {},           "headers": {             "Accept": "application/json, text/plain, */*",             "Host": "httpbin.org",             "User-Agent": "axios/0.21.1",             "X-Amzn-Trace-Id": "Root=1-60cb63df-1b8592de3767882a6e865295"           },           "json": null,           "method": "GET",           "origin": "119.123.242.225",           "url": "https://httpbin.org/anything?k1=v1"         }       }     }

  Apitest 发现了k1的值异常 main.test1.res.body.args.k1: v2 ≠ v1 并打印错误,同时还打印了接口请求响应详情。   如果我们修改 main.test1.res.body.args.k1 值 v2 => v1 后再执行测试。   apitest httpbin.jsona

  其结果如下:   main     test1 (1.889) 

  Apitest 报告测试通过了。  
 原理   Apitest 执行测试文件时会加载全部
测试用例,逐一执行,其执行过程可以描述为:根据 req 部分构造请求发送给服务器,收到响应后依据 res 校验响应数据,然后打印结果。   Apitest 中的用例文件格式是 JSONA。 JSONA是JSON的超集,减轻了一些JSON语法限制(不强制要求双引号,支持注释等),再添加了一个特性:注解。上面例子中的@partial就是注解。   为什么使用JSONA?   
接口测试的本质的就是构造并发送req数据,接收并校验res数据。数据即是主体又是核心,而JSON是最可读最通用的数据描述格式。   接口测试还需要某些特定逻辑。比如请求中构造随机数,在响应中只校验给出的部分数据。   JSONA = JSON + Annotation(注解)。JSON负责数据部分,注解负责逻辑部分。完美的贴合接口测试需求。  
 特性   
·跨平台   
· DSL    - 类JSON,没有学习难度    - 编写简单,阅读容易    - 不要求编写者会编程   
· 数据即断言   
· 数据可访问   
· 支持Mock   
· 支持Mixin   
· 支持CI   
· 支持TDD   
· 支持用户定义函数   
· 跳过,延时,重试和循环   
· 支持Form,文件上传,GraphQL   
示例
  全等校验   默认请求下,Apitest 进行全等校验。   
· 简单类型数据(null,boolean,string,number)完全相等   
· object数据属性和属性值完全相等,字段顺序可以不一致   
· array数据元素长度和各元素完全相等,元素顺序也要一致   {     test1: { @client("echo")       req: {         any: null,         bool: true,         str: "string",         int: 3,         float: 0.3,         obj: {a:3, b:4},         arr: [3,4],       },       res: {         any: null,         bool: true,         str: "string",         int: 3,         float: 0.3,         obj: {a:3, b:4},         // obj: {b:4, b:3}, object类数据字段顺序可以不一致         arr: [3,4],       }     }   }

  Apitest 保证:只有当实际接收到的 res 数据与我们用例中描述的 res 数据全等,测试才会通过。
  数组校验技巧   Apitest 默认全等校验,而接口返回的array数据可能几十上百条,怎么办?   通常接口数据是结构化的,我们可以只校验数组第一个元素。   {     test1: { @client("echo")       req: {         arr: [           {name: "v1"},           {name: "v2"},           {name: "v3"},         ]       },       res: {         arr: [ @partial           {             name: "", @type           }         ],       }     }   }

  如果array数据的长度也很关键呢?   {     test1: { @client("echo")       req: {         arr: [           {name: "v1"},           {name: "v2"},           {name: "v3"},         ]       },       res: {         arr: [ @every           [ @partial               {                 name: "", @type               }           ],           `$.length === 3`, @eval         ],       }     }   }

 
 对象校验技巧   Apitest 默认全等校验,而接口返回的object数据的属性很多,我们只关注其中部分属性?   {     test1: { @client("echo")       req: {         obj: {           a: 3,           b: 4,           c: 5,         }       },       res: {         obj: { @partial           b: 4,         }       }     }   }

 
 查询字符串   通过 req.query 传入QueryString   {     test1: {       req: {         url: "https://httpbin.org/get",         query: {           k1: "v1",           k2: "v2",         }       },       res: {         body: { @partial           url: "https://httpbin.org/get?k1=v1&k2=v2",         }       }     }   }

  当然你可以把QueryString直接写在req.url中。   {     test1: {       req: {         url: "https://httpbin.org/get?k1=v1&k2=v2",       },       res: {         body: { @partial           url: "https://httpbin.org/get?k1=v1&k2=v2",         }       }     }   }


  路径变量   通过 req.params 传入路径变量。   {     test1: {       req: {         url: "https://httpbin.org/anything/{id}",         params: {           id: 3,         }       },       res: {         body: { @partial           url: "https://httpbin.org/anything/3"         }       }     }   }


  请求头/响应头   通过 req.headers 传入请求头,通过 res.headers 校验响应头。   {     setCookies: { @describe("response with set-cookies header")       req: {         url: "https://httpbin.org/cookies/set",         query: {           k1: "v1",           k2: "v2",         },       },       res: {         status: 302,         headers: { @partial           'set-cookie': [             "k1=v1; Path=/",             "k2=v2; Path=/",           ],          },         body: "", @type       }     },     useCookies: { @describe("request with cookie header")       req: {         url: "https://httpbin.org/cookies",         headers: {           Cookie: `setCookies.res.headers["set-cookie"]`, @eval         }       },       res: {         body: { @partial           cookies: {             k1: "v1",             k2: "v2",           }         }       },     },   }

 
 用例数据变量导出与引用   凡是执行过的用例其数据均可以当做已自动导出变量,它们均可以被后续用例引用。   Apitest 中可以使用 @eval 注解引用用例数据。   比如上面例子中setCookies.res.headers["set-cookie"],就是引用前面setCookies用例的set-cookie响应头数据。   
表单: x-www-form-urlencoded   {     test1: { @describe('test form')       req: {         url: "https://httpbin.org/post",         method: "post",         headers: {           'content-type':"application/x-www-form-urlencoded"         },         body: {           v1: "bar1",           v2: "Bar2",         }       },       res: {         status: 200,         body: { @partial           form: {             v1: "bar1",             v2: "Bar2",           }         }       }     },   }

  
表单: multipart/form-data   结合 @file 注解实现文件上传。   {     test1: { @describe('test multi-part')       req: {         url: "https://httpbin.org/post",         method: "post",         headers: {           'content-type': "multipart/form-data",         },         body: {           v1: "bar1",           v2: "httpbin.jsona", @file         }       },       res: {         status: 200,         body: { @partial           form: {             v1: "bar1",             v2: "", @type           }         }       }     }   }

  
GraphQL   {     test1: { @describe("test graphql")       req: {         url: "https://api.spacex.land/graphql/",         body: {           query: `\`query {     launchesPast(limit: ${othertest.req.body.count}) {       mission_name       launch_date_local       launch_site {         site_name_long       }     }   }\`` @eval         }       },       res: {         body: {           data: {             launchesPast: [ @partial               {                 "mission_name": "", @type                 "launch_date_local": "", @type                 "launch_site": {                   "site_name_long": "", @type                 }               }             ]           }         }       }     }   }

 
 http(s)代理   {     @client({       name: "default",       type: "http",       options: {         proxy: "http://localhost:8080",       }     })     test1: {       req: {         url: "https://httpbin.org/ip",       },       res: {         body: {           origin: "", @type         }       }     }   }

  Apitest 支持通过 HTTP_PROXY HTTPS_PROXY 环境变量开全局代理。   
多个接口服务地址   {     @client({       name: "api1",       type: "http",       options: {         baseURL: "http://localhost:3000/api/v1",       }     })     @client({       name: "api2",       type: "http",       options: {         baseURL: "http://localhost:3000/api/v2",       }     })     test1: { @client("api1")       req: {         url: "/signup", // => http://localhost:3000/api/v1/signup       }     },     test2: { @client("api2")       req: {         url: "/signup", // => http://localhost:3000/api/v2/signup       }     }   }

  
自定义超时   你可以设置客户端超时,影响所有使用该客户端的接口。   {     @client({       name: "default",       type: "http",       options: {         timeout: 30000,        }     })   }

  你也可以为某个用例设置超时。   {     test1: { @client({options:{timeout: 30000}})     }   }

 
 环境变量传递数据   {     test1: {       req: {         headers: {           "x-key": "env.API_KEY", @eval         }       }     }   }


  mock数据   {     login1: {       req: {         url: "/signup",         body: {           username: 'username(3)', @mock           password: 'string(12)', @mock           email: `req.username + "@gmail.com"`, @eval         }       }     }   }

  Apitest 支持近40个mock函数。下面列些常用的。   {     test1: {       req: {         email: 'email', @mock         username: 'username', @mock         integer: 'integer(-5, 5)', @mock         image: 'image("200x100")', @mock         string: 'string("alpha", 5)', @mock         date: 'date', @mock  // iso8601格式的当前时间 // 2021-06-03T07:35:55Z         date2: 'date("","2 weeks ago")', @mock // 2周前         sentence: 'sentence', @mock         cnsentence: 'cnsentence', @mock // 中文段落           }     }   }

 
 用例组   {     @describe("这是一个模块")     @client({name:"default",kind:"echo"})     group1: { @group @describe("这是一个组")       test1: { @describe("最内用例")         req: {         }       },       group2: { @group @describe("这是一个嵌套组")         test1: { @describe("嵌套组内的用例")           req: {           }         }       }     }   }

  上面的测试文件打印如下:   这是一个模块     这是一个组       最内用例        这是一个嵌套组         嵌套组内的用例 

  
跳过用例(组)   {     test1: { @client("echo")       req: {       },       run: {         skip: `othertest.res.status === 200`, @eval       }     }   }

  
延时执行用例(组)   {     test1: { @client("echo")       req: {       },       run: {         delay: 1000, // 延时毫秒       }     }   }

 
 重试用例(组)   {     test1: { @client("echo")       req: {       },       run: {         retry: {           stop:'$run.count> 2', @eval // 终止重试条件           delay: 1000, // 重试间隔毫秒         }       },     }   }

 
 重复执行用例(组)   {     test1: { @client("echo")       req: {         v1:'$run.index', @eval         v2:'$run.item', @eval       },       run: {         loop: {           delay: 1000, // 重复执行间隔毫秒           items: [  // 重复执行数据             'a',             'b',             'c',           ]         }       },     }   }

  如果不在意数据,只想重复执行多少次的话,可以这样设置。   {     test1: {       run: {         delay: 1000,         items: `Array(5)`, @eval       }     }   }

 
 强制打印详情   常规模式下,接口如果没有出错是不会打印数据详情的。通过设置run.dump为true强制打印详情数据。   {     test1: { @client("echo")       req: {       },       run: {         dump: true,       }     }   }

  
抽离公用逻辑以复用   首先创建一个文件存储Mixin定义的文件。   // mixin.jsona   {     createPost: { // 抽离路由信息到mixin       req: {         url: '/posts',         method: 'post',       },     },     auth1: { // 抽离鉴权到minxin       req: {         headers: {           authorization: `"Bearer " + test1.res.body.token`, @eval         }       }     }   }

  @mixin("mixin") // 引入 mixin.jsona 文件   {     createPost1: { @describe("写文章1") @mixin(["createPost", "auth1"])       req: {         body: {           title: "sentence", @mock         }       }     },     createPost2: { @describe("写文章2,带描述") @mixin(["createPost", "auth1"])       req: {         body: {           title: "sentence", @mock           description: "paragraph", @mock         }       }     },   }

  越是频繁用到的数据越适合抽离到Mixin。   
 自定义函数   某些情况下,Apitest 内置的注解不够用,你可以使用自定义函数。   编写函数lib.js   // 创建随机颜色   exports.makeColor = function () {     const letters = "0123456789ABCDEF";     let color = "#";     for (let i = 0; i < 6; i++) {       color += letters[Math.floor(Math.random() * 16)];     }     return color;   }   // 判断是否是ISO8601(2021-06-02:00:00.000Z)风格的时间字符串   exports.isDate = function (date) {     return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(date)   }

  使用函数   @jslib("lib") // 引入js文件   {     test1: {       req: {         body: {           color: 'makeColor()', @eval // 调用 `makeColor` 函数生成随机颜色         }       },       res: {         body: {           createdAt: 'isDate($)', @eval // $ 表示须校验字段,对应响应数据`res.body.createdAt`           // 当然你可以直接使用regex           updatedAt: `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test($)`, @eval         }       }     }   }




  
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理

权威发布,测试选择不纠结!第15届软件测试行业报告,直击行业发展,把握未来方向!

原文地址:http://www.51testing.com/?action-viewnews-itemid-6657681

免责声明:本文来源于互联网,版权归合法拥有者所有,如有侵权请公众号联系管理员

* 本站提供的一些文章、资料是供学习研究之用,如用于商业用途,请购买正版。

发表于:2022-5-17 09:16 作者:sigo 来源:稀土掘金