51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 731|回复: 0
打印 上一主题 下一主题

[转贴] Apitest 接口自动化测试工具

[复制链接]
  • TA的每日心情
    无聊
    前天 09:06
  • 签到天数: 941 天

    连续签到: 3 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-5-17 11:46:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    安装
      Apitest工具是单可执行文件,不需要安装,放到PATH路径下面就可以直接运行:
    1. # linux
    2.   curl -L -o apitest https://github.com/sigoden/apitest/releases/latest/download/apitest-linux
    3.   chmod +x apitest
    4.   sudo mv apitest /usr/local/bin/
    5.   # macos
    6.   curl -L -o apitest https://github.com/sigoden/apitest/releases/latest/download/apitest-macos
    7.   chmod +x apitest
    8.   sudo mv apitest /usr/local/bin/
    9.   # npm
    10.   npm install -g @sigodenjs/apitest
    复制代码
    开始使用
      编写测试文件 httpbin.jsona
    1.  {
    2.     test1: {
    3.       req: {
    4.         url: "https://httpbin.org/anything",
    5.         query: {
    6.           k1: "v1",
    7.         },
    8.       },
    9.       res: {
    10.         body: { @partial
    11.           args: {
    12.             "k1": "v2", // 注意,这儿应该是"v1", 我们故意写"v2"以测试Apitest的反应
    13.           },
    14.           url: "https://httpbin.org/anything?k1=v1",
    15.         }
    16.       }
    17.     }
    18.   }
    复制代码
    执行如下命令测试接口。

    1.  apitest httpbin.jsona
    复制代码
    其结果如下:

    1.  main
    2.     test1 (2.554) ?
    3.     main.test1.res.body.args.k1: v2 ≠ v1
    4.     {
    5.       "req": {
    6.         "url": "https://httpbin.org/anything",
    7.         "query": {
    8.           "k1": "v1"
    9.         }
    10.       },
    11.       "res": {
    12.         "headers": {
    13.           "date": "Thu, 17 Jun 2021 15:01:51 GMT",
    14.           "content-type": "application/json",
    15.           "content-length": "400",
    16.           "connection": "close",
    17.           "server": "gunicorn/19.9.0",
    18.           "access-control-allow-origin": "*",
    19.           "access-control-allow-credentials": "true"
    20.         },
    21.         "status": 200,
    22.         "body": {
    23.           "args": {
    24.             "k1": "v1"
    25.           },
    26.           "data": "",
    27.           "files": {},
    28.           "form": {},
    29.           "headers": {
    30.             "Accept": "application/json, text/plain, */*",
    31.             "Host": "httpbin.org",
    32.             "User-Agent": "axios/0.21.1",
    33.             "X-Amzn-Trace-Id": "Root=1-60cb63df-1b8592de3767882a6e865295"
    34.           },
    35.           "json": null,
    36.           "method": "GET",
    37.           "origin": "119.123.242.225",
    38.           "url": "https://httpbin.org/anything?k1=v1"
    39.         }
    40.       }
    41.     }
    复制代码
    Apitest 发现了k1的值异常 main.test1.res.body.args.k1: v2 ≠ v1 并打印错误,同时还打印了接口请求响应详情。
      如果我们修改 main.test1.res.body.args.k1 值 v2 => v1 后再执行测试。
    1.   apitest httpbin.jsona
    复制代码
    其结果如下:
    1. main
    2.     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数据元素长度和各元素完全相等,元素顺序也要一致
    1.  {
    2.     test1: { @client("echo")
    3.       req: {
    4.         any: null,
    5.         bool: true,
    6.         str: "string",
    7.         int: 3,
    8.         float: 0.3,
    9.         obj: {a:3, b:4},
    10.         arr: [3,4],
    11.       },
    12.       res: {
    13.         any: null,
    14.         bool: true,
    15.         str: "string",
    16.         int: 3,
    17.         float: 0.3,
    18.         obj: {a:3, b:4},
    19.         // obj: {b:4, b:3}, object类数据字段顺序可以不一致
    20.         arr: [3,4],
    21.       }
    22.     }
    23.   }
    复制代码
     Apitest 保证:只有当实际接收到的 res 数据与我们用例中描述的 res 数据全等,测试才会通过。
      数组校验技巧
      Apitest 默认全等校验,而接口返回的array数据可能几十上百条,怎么办?
      通常接口数据是结构化的,我们可以只校验数组第一个元素。
    1.  {
    2.     test1: { @client("echo")
    3.       req: {
    4.         arr: [
    5.           {name: "v1"},
    6.           {name: "v2"},
    7.           {name: "v3"},
    8.         ]
    9.       },
    10.       res: {
    11.         arr: [ @partial
    12.           {
    13.             name: "", @type
    14.           }
    15.         ],
    16.       }
    17.     }
    18.   }
    复制代码
    如果array数据的长度也很关键呢?
    1. {
    2.     test1: { @client("echo")
    3.       req: {
    4.         arr: [
    5.           {name: "v1"},
    6.           {name: "v2"},
    7.           {name: "v3"},
    8.         ]
    9.       },
    10.       res: {
    11.         arr: [ @every
    12.           [ @partial
    13.               {
    14.                 name: "", @type
    15.               }
    16.           ],
    17.           `$.length === 3`, @eval
    18.         ],
    19.       }
    20.     }
    21.   }
    复制代码
    对象校验技巧
      Apitest 默认全等校验,而接口返回的object数据的属性很多,我们只关注其中部分属性?
    1. {
    2.     test1: { @client("echo")
    3.       req: {
    4.         obj: {
    5.           a: 3,
    6.           b: 4,
    7.           c: 5,
    8.         }
    9.       },
    10.       res: {
    11.         obj: { @partial
    12.           b: 4,
    13.         }
    14.       }
    15.     }
    16.   }
    复制代码
    查询字符串
      通过 req.query 传入QueryString
    1. {
    2.     test1: {
    3.       req: {
    4.         url: "https://httpbin.org/get",
    5.         query: {
    6.           k1: "v1",
    7.           k2: "v2",
    8.         }
    9.       },
    10.       res: {
    11.         body: { @partial
    12.           url: "https://httpbin.org/get?k1=v1&k2=v2",
    13.         }
    14.       }
    15.     }
    16.   }
    复制代码
    当然你可以把QueryString直接写在req.url中。
    1. {
    2.     test1: {
    3.       req: {
    4.         url: "https://httpbin.org/get?k1=v1&k2=v2",
    5.       },
    6.       res: {
    7.         body: { @partial
    8.           url: "https://httpbin.org/get?k1=v1&k2=v2",
    9.         }
    10.       }
    11.     }
    12.   }
    复制代码
    路径变量
      通过 req.params 传入路径变量。
    1. {
    2.     test1: {
    3.       req: {
    4.         url: "https://httpbin.org/anything/{id}",
    5.         params: {
    6.           id: 3,
    7.         }
    8.       },
    9.       res: {
    10.         body: { @partial
    11.           url: "https://httpbin.org/anything/3"
    12.         }
    13.       }
    14.     }
    15.   }
    复制代码
    请求头/响应头
      通过 req.headers 传入请求头,通过 res.headers 校验响应头。
    1. {
    2.     setCookies: { @describe("response with set-cookies header")
    3.       req: {
    4.         url: "https://httpbin.org/cookies/set",
    5.         query: {
    6.           k1: "v1",
    7.           k2: "v2",
    8.         },
    9.       },
    10.       res: {
    11.         status: 302,
    12.         headers: { @partial
    13.           'set-cookie': [
    14.             "k1=v1; Path=/",
    15.             "k2=v2; Path=/",
    16.           ],
    17.         },
    18.         body: "", @type
    19.       }
    20.     },
    21.     useCookies: { @describe("request with cookie header")
    22.       req: {
    23.         url: "https://httpbin.org/cookies",
    24.         headers: {
    25.           Cookie: `setCookies.res.headers["set-cookie"]`, @eval
    26.         }
    27.       },
    28.       res: {
    29.         body: { @partial
    30.           cookies: {
    31.             k1: "v1",
    32.             k2: "v2",
    33.           }
    34.         }
    35.       },
    36.     },
    37.   }
    复制代码
    用例数据变量导出与引用
      凡是执行过的用例其数据均可以当做已自动导出变量,它们均可以被后续用例引用。
      Apitest 中可以使用 @eval 注解引用用例数据。
      比如上面例子中setCookies.res.headers["set-cookie"],就是引用前面setCookies用例的set-cookie响应头数据。
      表单: x-www-form-urlencoded
    1.  {
    2.     test1: { @describe('test form')
    3.       req: {
    4.         url: "https://httpbin.org/post",
    5.         method: "post",
    6.         headers: {
    7.           'content-type':"application/x-www-form-urlencoded"
    8.         },
    9.         body: {
    10.           v1: "bar1",
    11.           v2: "Bar2",
    12.         }
    13.       },
    14.       res: {
    15.         status: 200,
    16.         body: { @partial
    17.           form: {
    18.             v1: "bar1",
    19.             v2: "Bar2",
    20.           }
    21.         }
    22.       }
    23.     },
    24.   }
    复制代码
    表单: multipart/form-data
      结合 @file 注解实现文件上传。
    1.  {
    2.     test1: { @describe('test multi-part')
    3.       req: {
    4.         url: "https://httpbin.org/post",
    5.         method: "post",
    6.         headers: {
    7.           'content-type': "multipart/form-data",
    8.         },
    9.         body: {
    10.           v1: "bar1",
    11.           v2: "httpbin.jsona", @file
    12.         }
    13.       },
    14.       res: {
    15.         status: 200,
    16.         body: { @partial
    17.           form: {
    18.             v1: "bar1",
    19.             v2: "", @type
    20.           }
    21.         }
    22.       }
    23.     }
    24.   }
    复制代码
    GraphQL

    1. {
    2.     test1: { @describe("test graphql")
    3.       req: {
    4.         url: "https://api.spacex.land/graphql/",
    5.         body: {
    6.           query: `\`query {
    7.     launchesPast(limit: ${othertest.req.body.count}) {
    8.       mission_name
    9.       launch_date_local
    10.       launch_site {
    11.         site_name_long
    12.       }
    13.     }
    14.   }\`` @eval
    15.         }
    16.       },
    17.       res: {
    18.         body: {
    19.           data: {
    20.             launchesPast: [ @partial
    21.               {
    22.                 "mission_name": "", @type
    23.                 "launch_date_local": "", @type
    24.                 "launch_site": {
    25.                   "site_name_long": "", @type
    26.                 }
    27.               }
    28.             ]
    29.           }
    30.         }
    31.       }
    32.     }
    33.   }
    复制代码
    http(s)代理

    1.  {
    2.     @client({
    3.       name: "default",
    4.       type: "http",
    5.       options: {
    6.         proxy: "http://localhost:8080",
    7.       }
    8.     })
    9.     test1: {
    10.       req: {
    11.         url: "https://httpbin.org/ip",
    12.       },
    13.       res: {
    14.         body: {
    15.           origin: "", @type
    16.         }
    17.       }
    18.     }
    19.   }
    复制代码
    Apitest 支持通过 HTTP_PROXY HTTPS_PROXY 环境变量开全局代理。
      多个接口服务地址
    1. {
    2.     @client({
    3.       name: "api1",
    4.       type: "http",
    5.       options: {
    6.         baseURL: "http://localhost:3000/api/v1",
    7.       }
    8.     })
    9.     @client({
    10.       name: "api2",
    11.       type: "http",
    12.       options: {
    13.         baseURL: "http://localhost:3000/api/v2",
    14.       }
    15.     })
    16.     test1: { @client("api1")
    17.       req: {
    18.         url: "/signup", // => http://localhost:3000/api/v1/signup
    19.       }
    20.     },
    21.     test2: { @client("api2")
    22.       req: {
    23.         url: "/signup", // => http://localhost:3000/api/v2/signup
    24.       }
    25.     }
    26.   }
    复制代码
    自定义超时
      你可以设置客户端超时,影响所有使用该客户端的接口。
    1.  {
    2.     @client({
    3.       name: "default",
    4.       type: "http",
    5.       options: {
    6.         timeout: 30000,
    7.       }
    8.     })
    9.   }
    复制代码
    你也可以为某个用例设置超时。

    1.  {
    2.     test1: { @client({options:{timeout: 30000}})
    3.     }
    4.   }
    复制代码
    环境变量传递数据

    1. {
    2.     test1: {
    3.       req: {
    4.         headers: {
    5.           "x-key": "env.API_KEY", @eval
    6.         }
    7.       }
    8.     }
    9.   }
    复制代码
    mock数据

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

     Apitest 支持近40个mock函数。下面列些常用的。

    1. {
    2.     test1: {
    3.       req: {
    4.         email: 'email', @mock
    5.         username: 'username', @mock
    6.         integer: 'integer(-5, 5)', @mock
    7.         image: 'image("200x100")', @mock
    8.         string: 'string("alpha", 5)', @mock
    9.         date: 'date', @mock  // iso8601格式的当前时间 // 2021-06-03T07:35:55Z
    10.         date2: 'date("","2 weeks ago")', @mock // 2周前
    11.         sentence: 'sentence', @mock
    12.         cnsentence: 'cnsentence', @mock // 中文段落   
    13.       }
    14.     }
    15.   }
    复制代码
     用例组

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

     上面的测试文件打印如下:

    1.  这是一个模块
    2.     这是一个组
    3.       最内用例
    4.       这是一个嵌套组
    5.         嵌套组内的用例
    复制代码
     跳过用例(组)

     {
        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',
              ]
            }
          },
        }
      }

    如果不在意数据,只想重复执行多少次的话,可以这样设置。

    1.  {
    2.     test1: {
    3.       run: {
    4.         delay: 1000,
    5.         items: `Array(5)`, @eval
    6.       }
    7.     }
    8.   }
    复制代码
    强制打印详情
      常规模式下,接口如果没有出错是不会打印数据详情的。通过设置run.dump为true强制打印详情数据。
    1. {
    2.     test1: { @client("echo")
    3.       req: {
    4.       },
    5.       run: {
    6.         dump: true,
    7.       }
    8.     }
    9.   }
    复制代码
    抽离公用逻辑以复用
      首先创建一个文件存储Mixin定义的文件。
    1. // mixin.jsona
    2.   {
    3.     createPost: { // 抽离路由信息到mixin
    4.       req: {
    5.         url: '/posts',
    6.         method: 'post',
    7.       },
    8.     },
    9.     auth1: { // 抽离鉴权到minxin
    10.       req: {
    11.         headers: {
    12.           authorization: `"Bearer " + test1.res.body.token`, @eval
    13.         }
    14.       }
    15.     }
    16.   }
    复制代码
    1.  @mixin("mixin") // 引入 mixin.jsona 文件
    2.   {
    3.     createPost1: { @describe("写文章1") @mixin(["createPost", "auth1"])
    4.       req: {
    5.         body: {
    6.           title: "sentence", @mock
    7.         }
    8.       }
    9.     },
    10.     createPost2: { @describe("写文章2,带描述") @mixin(["createPost", "auth1"])
    11.       req: {
    12.         body: {
    13.           title: "sentence", @mock
    14.           description: "paragraph", @mock
    15.         }
    16.       }
    17.     },
    18.   }
    复制代码
    越是频繁用到的数据越适合抽离到Mixin。 
      自定义函数
      某些情况下,Apitest 内置的注解不够用,你可以使用自定义函数。
      编写函数lib.js
    1.  // 创建随机颜色
    2.   exports.makeColor = function () {
    3.     const letters = "0123456789ABCDEF";
    4.     let color = "#";
    5.     for (let i = 0; i < 6; i++) {
    6.       color += letters[Math.floor(Math.random() * 16)];
    7.     }
    8.     return color;
    9.   }
    10.   // 判断是否是ISO8601(2021-06-02:00:00.000Z)风格的时间字符串
    11.   exports.isDate = function (date) {
    12.     return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(date)
    13.   }
    复制代码
    使用函数
    1. @jslib("lib") // 引入js文件
    2.   {
    3.     test1: {
    4.       req: {
    5.         body: {
    6.           color: 'makeColor()', @eval // 调用 `makeColor` 函数生成随机颜色
    7.         }
    8.       },
    9.       res: {
    10.         body: {
    11.           createdAt: 'isDate($)', @eval // $ 表示须校验字段,对应响应数据`res.body.createdAt`
    12.           // 当然你可以直接使用regex
    13.           updatedAt: `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test($)`, @eval
    14.         }
    15.       }
    16.     }
    17.   }
    复制代码

















    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-5-2 05:55 , Processed in 0.065583 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表