Skip to main content

bintest自动化框架

Project description

bintest通过json形式维护case结构,通过解析器将HAR包解析为满足要求的json格式,再通过执行器运行json,少代码甚至0代码编写自动化case

1、case结构

bintest是以json格式作为case保存的基本结构,一条能够被bintest执行器执行的json格式是有一定要求的,具体说明请看本章节的详细内容

1.1 case基本结构

参数名称 类型 说明
caseName String case名称,必填
description String case详情,非必填
priority int 优先级,必填
tags list 标签列表,非必填,每一个元素为string类型的标签
testSteps list case步骤,必填,每一个元素为一个dict,代表一个步骤,详细说明见1.2

1.2 testSteps中元素的结构

参数名称 类型 说明
step int 步骤编号,需递增,必填
request dict 请求,必填,dict类型,具体说明见1.2.1
action list 动作列表,非必填,每一个元素为string类型的动作,是一个bintest字符串
assert list 断言列表,非必填,每一个元素为一个dict,代表一个步骤,详细说明见1.2.2

1.2.1 request结构

参数名称 类型 说明
url String 请求url,必填
path String 请求路径,必填
method String String类型的枚举值,是"GET", "POST", "PUT", "DELETE", "HEAD"中的一种,必填
headers dict 请求header,dict类型,非必填
param dict 请求queryString,dict类型比如?userId=123 对应 {"userId": "123"},非必填
body dict 请求体,非必填

1.2.2 assert中元素的结构

参数名称 类型 说明
expect String 期望值,必填
actual String 实际值,必填
operator String 断言操作符,必填,有默认,也可自定义

1.3 样例

{
  "caseName": "事件记录查询", // case名称,必填
  "description": "", // case详情,非必填
  "priority": 0, // case优先级,必填
  "tags": ["交付", "线上功能回归", "监控"], // case标签列表
  "testSteps": [ // case步骤列表
    {
      "step": 0, // 步骤编号,递增,必填
      "request": { // 请求,必填
        "url": "$BT[var($conf.dev.host)]", // 请求url,必填
        "path": "/aiob-server/manage/console/callEvent/reservation", // 请求路径,必填
        "method": "POST", // 请求方法,必填,需大写GET, POST, PUT, DELETE等
        "headers": { // 请求header,非必填,dict类型,对应的是请求中header的键值对
          "Accept": "application/json, text/plain, */*",
          "Accept-Encoding": "gzip, deflate, br",
          "Accept-Language": "zh-CN,zh;q=0.9",
          "AgentId": "10183",
          "Connection": "keep-alive",
          "Content-Length": "63",
          "Content-Type": "application/json",
          "Cookie": "aicc-auth-type=COLLABORATOR; Hm_lvt_28a17f66627d87f1d046eae152a1c93d=1715589263; uc_login_unique=fd868eb2a76056538c9b2a93f26c91e3; __bce-console-referrer__=https://aicc.bce.baidu.com/; Hm_lpvt_28a17f66627d87f1d046eae152a1c93d=1715601782; SIGNIN_UC=70a2711cf1d3d9b1a82d2f87d633bd8a04656142400AVAmcmZQuQy7YchvHsGTQoxmZE4uQYjeKQS%2B1O%2FsupFKfigzFIoR%2F4CABHaYi98bROdRlOICCFCsQTrTzTWkOsda5QQc1gcBBpb%2BZa2ns543nLtLIHs8lPf95LQizENNwQXOynXuP7ZBpr3TFD9BFO%2FlWTRbT1Tt2t0YBlNb5C2Gdr2C0i8Khyh5%2F5frSomywQXOynXuP7ZBpr3TFD9BFO%2FlWTRbT1Tt2t0YBlNb5C3L2Kxl1xv4mVWlZ6BsBt2fJa8C4dRuWedOgm%2F4vAYkrW2fPKVX4hXM644IdJMDCFuhZ1Bdk5cs0KEYnjaFXy63hZxZ3bZeuprSPOfukm85QsL1%2BfRxeQGU%2BJzICeY9qjg%3D11761773989359425321345716539084; uc_recom_mark=cmVjb21tYXJrXzM0NDk0MzEy; bce-login-type=UC; bce-login-type-detail=UC; x-bce-login-redirect-url=https%3A%2F%2Faicc.bce.baidu.com%2Fconfig%2Frobot%2Fmanage; bce-fe-load-type=self; x-bce-login-redirect-url-console=https%3A%2F%2Faicc.bce.baidu.com%2Fconfig%2Frobot%2Fmanage; __cas__rn__=465614240; __cas__st__285=4827f46ab6a1ab0cda13d01f082a4504ade8fcd00c3c96910b53143a0ef863dc7dadc6cfa11aa7a14b23b50f; __cas__id__285=34494312; bce-auth-type=UC; bce-sessionid=002c5e880454e4546879a087dccc9eff366; bce-ctl-client-cookies=\"__cas__st__285,__cas__id__285,__cas__rn__,SIGNIN_UC,bce-device-cuid,bce-device-token,BAIDUID,ucbearer-clientid,ucbearer-devicecode,ucbearer-token,ucbearer-ucid\"; bce-ctl-client-parameters=castk&brt; bce-ctl-client-headers=\"\"; bce-user-info=\"2024-05-13T20:03:13Z|4493111d47e555b5fbdb7b42314200ca\"; bce-login-display-name=%E6%99%BA%E8%83%BD%E5%A4%96%E5%91%BC%E5%8E%8B%E6%B5%8B; bce-userbind-source=PASSPORT%3BUUAP; bce-session=49437e31081c4a939bb661c2ff9de6977edc9a9cf3da40e39f5938f56f74166f|c8983fcf7b819871289c138c3c35bf06; bce-ctl-sessionmfa-cookie=bce-session; bce-locale=zh-cn; UUAP_P_TOKEN=PT-996889614444429314-NPqljYMCBF-uuap; SECURE_UUAP_P_TOKEN=PT-996889614444429314-NPqljYMCBF-uuap; UUAP_TRACE_TOKEN=0338350554af5c8d98cad5be471888d7; BAIDUID=A273FCE05F3D6D1EE8F6BA6F9D02D257:FG=1; BAIDUID_BFESS=A273FCE05F3D6D1EE8F6BA6F9D02D257:FG=1; RT=\"z=1&dm=baidu.com&si=5ff08384-78e8-4efd-9be2-cd1a23936df1&ss=lw4pcdjm&sl=w&tt=5bx&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=arus5\"",
          "Host": "aicc.bce.baidu.com",
                    "Origin": "$BT[var($conf.dev.host)]",
                    "Referer": "https://aicc.bce.baidu.com/aiob/external/call/event",
                    "Sec-Fetch-Dest": "empty",
                    "Sec-Fetch-Mode": "cors",
                    "Sec-Fetch-Site": "same-origin",
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
                    "User-PK": "414",
                    "X-Login-Source": "baidu",
                    "X-Request-Tenantid": "10183",
                    "X-Trace-Id": "edbbdd40-112c-11ef-99cf-615b6dabadcf",
                    "aiccCsrfToken": "",
                    "enName": "recordNew",
                    "sec-ch-ua": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
                    "sec-ch-ua-mobile": "?0",
                    "sec-ch-ua-platform": "\"macOS\"",
                    "username": "tenantAdmin"
                },
                "params": { // 请求参数,dict类型,即queryString
                    "t": "1715607013652_698575"
                },
                "body": { // 请求体,dict类型
                    "orderBy": null,
                    "order": null,
                    "pn": 1,
                    "ps": 20,
                    "total": 0,
                    "page": 1
                }
            },
            "action": [ // 动作列表,list类型,非必填,表示要执行的一系列动作
                "$BT[action(sleep(10))]",
                "$BT[set(var, func(get_userid))]"
            ],
            "assert": [ // 断言列表,list类型,非必填,表示要执行的一系列断言,每一个断言为一个dict
                {
                    "expect": 200, // 期望值
                    "actual": "$BT[var($resp['0'].status_code)]", // 实际值,必填
                    "operator": "==" // 断言方式
                }
            ]
        },
        {
            "step": 1,
            "request": {
                "url": "$BT[var($conf.dev.host)]",
                "path": "/ab/robot/drawDownList",
                "method": "GET",
                "headers": {
                    "Accept": "application/json, text/plain, */*",
                    "Accept-Encoding": "gzip, deflate, br",
                    "Accept-Language": "zh-CN,zh;q=0.9",
                    "AgentId": "10183",
                    "Connection": "keep-alive",
                    "Cookie": "aicc-auth-type=COLLABORATOR; Hm_lvt_28a17f66627d87f1d046eae152a1c93d=1715589263; uc_login_unique=fd868eb2a76056538c9b2a93f26c91e3; __bce-console-referrer__=https://aicc.bce.baidu.com/; Hm_lpvt_28a17f66627d87f1d046eae152a1c93d=1715601782; SIGNIN_UC=70a2711cf1d3d9b1a82d2f87d633bd8a04656142400AVAmcmZQuQy7YchvHsGTQoxmZE4uQYjeKQS%2B1O%2FsupFKfigzFIoR%2F4CABHaYi98bROdRlOICCFCsQTrTzTWkOsda5QQc1gcBBpb%2BZa2ns543nLtLIHs8lPf95LQizENNwQXOynXuP7ZBpr3TFD9BFO%2FlWTRbT1Tt2t0YBlNb5C2Gdr2C0i8Khyh5%2F5frSomywQXOynXuP7ZBpr3TFD9BFO%2FlWTRbT1Tt2t0YBlNb5C3L2Kxl1xv4mVWlZ6BsBt2fJa8C4dRuWedOgm%2F4vAYkrW2fPKVX4hXM644IdJMDCFuhZ1Bdk5cs0KEYnjaFXy63hZxZ3bZeuprSPOfukm85QsL1%2BfRxeQGU%2BJzICeY9qjg%3D11761773989359425321345716539084; uc_recom_mark=cmVjb21tYXJrXzM0NDk0MzEy; bce-login-type=UC; bce-login-type-detail=UC; x-bce-login-redirect-url=https%3A%2F%2Faicc.bce.baidu.com%2Fconfig%2Frobot%2Fmanage; bce-fe-load-type=self; x-bce-login-redirect-url-console=https%3A%2F%2Faicc.bce.baidu.com%2Fconfig%2Frobot%2Fmanage; __cas__rn__=465614240; __cas__st__285=4827f46ab6a1ab0cda13d01f082a4504ade8fcd00c3c96910b53143a0ef863dc7dadc6cfa11aa7a14b23b50f; __cas__id__285=34494312; bce-auth-type=UC; bce-sessionid=002c5e880454e4546879a087dccc9eff366; bce-ctl-client-cookies=\"__cas__st__285,__cas__id__285,__cas__rn__,SIGNIN_UC,bce-device-cuid,bce-device-token,BAIDUID,ucbearer-clientid,ucbearer-devicecode,ucbearer-token,ucbearer-ucid\"; bce-ctl-client-parameters=castk&brt; bce-ctl-client-headers=\"\"; bce-user-info=\"2024-05-13T20:03:13Z|4493111d47e555b5fbdb7b42314200ca\"; bce-login-display-name=%E6%99%BA%E8%83%BD%E5%A4%96%E5%91%BC%E5%8E%8B%E6%B5%8B; bce-userbind-source=PASSPORT%3BUUAP; bce-session=49437e31081c4a939bb661c2ff9de6977edc9a9cf3da40e39f5938f56f74166f|c8983fcf7b819871289c138c3c35bf06; bce-ctl-sessionmfa-cookie=bce-session; bce-locale=zh-cn; UUAP_P_TOKEN=PT-996889614444429314-NPqljYMCBF-uuap; SECURE_UUAP_P_TOKEN=PT-996889614444429314-NPqljYMCBF-uuap; UUAP_TRACE_TOKEN=0338350554af5c8d98cad5be471888d7; BAIDUID=A273FCE05F3D6D1EE8F6BA6F9D02D257:FG=1; BAIDUID_BFESS=A273FCE05F3D6D1EE8F6BA6F9D02D257:FG=1; RT=\"z=1&dm=baidu.com&si=5ff08384-78e8-4efd-9be2-cd1a23936df1&ss=lw4pcdjm&sl=w&tt=5bx&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=arus5\"",
                    "Host": "aicc.bce.baidu.com",
                    "Referer": "https://aicc.bce.baidu.com/aiob/external/call/event",
                    "Sec-Fetch-Dest": "empty",
                    "Sec-Fetch-Mode": "cors",
                    "Sec-Fetch-Site": "same-origin",
                    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
                    "User-PK": "414",
                    "X-Login-Source": "baidu",
                    "X-Request-By": "ERApplication",
                    "X-Request-Tenantid": "10183",
                    "X-Trace-Id": "edbc0450-112c-11ef-99cf-615b6dabadcf",
                    "enName": "recordNew",
                    "sec-ch-ua": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
                    "sec-ch-ua-mobile": "?0",
                    "sec-ch-ua-platform": "\"macOS\"",
                    "username": "tenantAdmin"
                },
                "param": {
                    "robotType": "2",
                    "t": "1715607013653_393500"
                },
                "body": {}
            },
            "action": [],
            "assert": [
                {
                    "expect": "$BT[var($resp['1'].status_code)]",
                    "actual": 200,
                    "operator": "=="
                }
            ]
        }
    ]
}

2、 case执行逻辑

根据上面的case结构,执行单条case时,我们会依次执行步骤列表中的步骤
每个步骤中会按照请求、动作、断言的方式依次执行
使用者可能会提出一些疑问:
如何处理接口之间的依赖关系?
为什么要执行动作,动作是干什么的,怎么执行动作?
如何对接口的返回结果进行断言?
······
为了解决上述问题,bintest中创建了一种特殊的字符串,名为** bt字符串(bt_str)**

2.1 bt_str

bt_str本质上是一个按照一定格式的可执行字符串,通过**$BT[ ]**包裹,括号中为具体的动作
动作分为4种

  1. 执行函数,通过func(),具体参考2.4章节
  2. 执行动作,通过action(),具体参考2.5章节
  3. 获取变量,通过var(),具体请参考2.2章节
  4. 存储变量,通过set(),具体请参考2.2章节

下面是一些具体的例子

$BT[func(get_user_org_by_id(var($resp['0'].data.userId)))]
# 执行用户自定义函数,实际上是一个python的函数 get_user_org_by_id()
# 函数的参数通过var动作获取,获取的是第0个步骤中响应的data的userId字段
$BT[action(my_sleep(30))]
# 执行用户自定义动作,实际上是一个python的函数 my_sleep()
# 函数的参数是30
$BT[var($resp['4'].text.data.list[0].id)]
# 获取第4步响应(从0开始)中text中的data中的list的第0个元素的id
$BT[set(userId, var($resp['4'].text.data.list[0].id))]
# 将第4步响应(从0开始)中text中的data中的list的第0个元素的id存储在名为userId的变量中

对于bt字符串的编写可能略显复杂,不过不用担心,bintest为您提供了一步生成bt字符串的方法,并将其自动填充进case中,您只需要选择一个合适的bt字符串即可,具体会在第三章节中进行解释

2.2 case过程变量管理

在上面的例子中,我们通过var获取的,或者通过set保留的其实都是在case执行过程中产生的变量
每条case执行过程中会产生3大类变量,分别存放在req_dict,resp_dict,case_var_dict中,都是字典类型

2.2.1 req_dict存放请求相关内容

2.2.1.1 格式说明

保存case执行过程中每个步骤的请求信息,是dict类型
dict的key是步骤编号(从0开始),对应的value是具体的请求信息
value的格式如下:

参数名称 类型 说明
url String 请求url
path String 请求路径
method String String类型的枚举值,是"GET", "POST", "PUT", "DELETE", "HEAD"中的一种
headers dict 请求header,dict类型
param dict 请求queryString,dict类型比如?userId=123 对应 {"userId": "123"}
body dict 请求体

示例如下:

{
  "0": {
    "url": "aicc.bce.baidu.com",
    "path": "/aiob-server/manage/robot/model/scene",
    "method": "GET",
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "Accept-Encoding": "gzip, deflate, br",
      "Accept-Language": "zh-CN,zh;q=0.9",
      "AgentId": "10183",
      "Connection": "keep-alive",
      "Cookie": "aicc-auth-type=COLLABORATOR; BAIDUID=A0DC2A4B49302BBD995B02FAFDE67D7F:FG=1; BAIDUID_BFESS=A0DC2A4B49302BBD995B02FAFDE67D7F:FG=1; uc_login_unique=183b6cb4bf0ca7d7762ce13bd653f5c7; __bce-console-referrer__=https://aicc.bce.baidu.com/; Hm_lvt_28a17f66627d87f1d046eae152a1c93d=1715589263,1715683140; Hm_lpvt_28a17f66627d87f1d046eae152a1c93d=1715683140; SIGNIN_UC=70a2711cf1d3d9b1a82d2f87d633bd8a04656955966FyeO9QcKo7Md82zkTJM13pU58rqwqwpa7lhwb%2B%2B0cajqity4pFMJbp2d4B0pURN%2BtNsYKqkvhB8%2Fz%2BC%2FYkxxWRWD0RtPW3HiG8UCowdyCyvdKx7VzwMRlVBgDVW44T9pEv0VAIGrQ%2FMyucdBgESDx6C776S%2BaSuVWRNsumfRkWq93tvpeLTjvRik%2FYEmsSiqEv0VAIGrQ%2FMyucdBgESDx6C776S%2BaSuVWRNsumfRkWomBIdNsZWINGGSiUbNBNmjzMc3eH72whZdNuxUyciD%2FRJT00zxZavybcJ3YdjV%2FdPScRBqBg6ids7TbDolYOPzHt9fCdQcuyek%2FQnf6RutKrcQy9AytI7T13XkvUrdXuo%3D73060218109505482735945898905983; uc_recom_mark=cmVjb21tYXJrXzM0NDk0MzEy; bce-login-type=UC; bce-login-type-detail=UC; x-bce-login-redirect-url=https%3A%2F%2Faicc.bce.baidu.com%2Faiob%2Ftask; bce-fe-load-type=self; x-bce-login-redirect-url-console=https%3A%2F%2Faicc.bce.baidu.com%2Faiob%2Ftask; __cas__rn__=465695596; __cas__st__285=e8e9ade05f19120472a68e2091c4f4e641332acd5e95793af1c8f543f39866d38a5a6f1dee77928cedda5a5c; __cas__id__285=34494312; bce-auth-type=UC; bce-sessionid=002b499ce229ec442aab41a4fef1499bb6a; bce-ctl-client-cookies=\"__cas__st__285,__cas__id__285,__cas__rn__,SIGNIN_UC,bce-device-cuid,bce-device-token,BAIDUID,ucbearer-clientid,ucbearer-devicecode,ucbearer-token,ucbearer-ucid\"; bce-ctl-client-parameters=castk&brt; bce-ctl-client-headers=\"\"; bce-user-info=\"2024-05-14T18:39:09Z|216d8d514b4534dca9d8a0153bfae99d\"; bce-login-display-name=%E6%99%BA%E8%83%BD%E5%A4%96%E5%91%BC%E5%8E%8B%E6%B5%8B; bce-userbind-source=PASSPORT%3BUUAP; bce-session=49437e31081c4a939bb661c2ff9de697b2e53e3e91404af78a99a9a7e518b1eb|4d9a6cabb823471e303b15994974e2ab; bce-ctl-sessionmfa-cookie=bce-session; bce-locale=zh-cn; RT=\"z=1&dm=baidu.com&si=5ff08384-78e8-4efd-9be2-cd1a23936df1&ss=lw66hlk0&sl=b&tt=418&bcn=https%3A%2F%2Ffclog.baidu.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=4qqth\"",
      "Host": "aicc.bce.baidu.com",
      "Referer": "https://aicc.bce.baidu.com/config/robot/manage",
      "Sec-Fetch-Dest": "empty",
      "Sec-Fetch-Mode": "cors",
      "Sec-Fetch-Site": "same-origin",
      "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
      "User-PK": "414",
      "X-Login-Source": "baidu",
      "X-Request-By": "ERApplication",
      "X-Request-Tenantid": "10183",
      "X-Trace-Id": "31ae1550-11e5-11ef-9eda-b7486b924d94",
      "enName": "dialogue_robot",
      "sec-ch-ua": "\".Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"103\", \"Chromium\";v=\"103\"",
      "sec-ch-ua-mobile": "?0",
      "sec-ch-ua-platform": "\"macOS\"",
      "username": "tenantAdmin"
    },
    "params": {
      "id": "171568615504393167",
      "robotName": "演示机器人"
    },
    "body": {
      
    }
  },
  "1": {
    ······
  }
}

2.2.1.2 如何引用

我们可以通过一种类似jsonpath的方式引用req_dict中的变量
比如上述例子中,请求参数中的robotName为演示机器人,它的jsonpath如下:

$['0'].params.robotName

我们在后续步骤中引用该值,使用类似的方式

$rep['0'].params.robotName

区别在于,前缀从$变为了$rep,表示从请求结果中获取到对应的值

2.2.2 resp_dict存放响应相关内容

2.2.2.1 格式说明

保存case执行过程中每个步骤请求的响应信息,是dict类型
dict的key是步骤编号(从0开始),对应的value是具体请求的响应信息,保存了响应的状态码和返回内容
value的格式如下:

参数名称 类型 说明
status_code int 请求的响应状态码
text dict 请求的返回内容

示例如下:

{
  "0": {
    "status_code": 200,
    "text": {
      "requestId": "31ae1550-11e5-11ef-9eda-b7486b924d94",
      "time": 1715686155083,
      "code": 200,
      "msg": "OK",
      "data": {
        "modelSceneList": [
          {
            "sceneName": "客户回访场景",
            "sceneText": "处于金融行业,用于客户回访,话术要点是询问用户反馈并感谢用户购买,期望使用机器人达到增强客户对企业的信任感和满意度,从而维护良好的客户关系的效果的场景"
          },
          {
            "sceneName": "推广营销场景",
            "sceneText": "处于快消品行业,用于销售推广,话术要点是推荐新产品特性,期望使用机器人达到增加产品曝光率、增加产品销售额、收集市场反馈的效果的场景"
          },
          {
            "sceneName": "预约提醒场景",
            "sceneText": "处于医疗行业,目标在于提高预约准时率,话术要点是提醒患者按时赴约并提供取消或更改预约的选项,期望使用机器人达到提高预约的准时率,减少患者因遗忘或错过预约而造成的不便,提升患者满意度效果的场景"
          }
        ]
      }
    }
  },
  "1": {
    ······
  }
}

2.2.2.2 如何引用

和req_dict类似,基本遵循jsonpath的结构
上述例子中,我们想引用第一个响应结果中data的modelSceneList中的第一个元素的sceneName,它的jsonpath如下:

$['0'].text.data.modelSceneList[0].sceneName

通过bt_jsonpath引用该值

$resp['0'].text.data.modelSceneList[0].sceneName

前缀从$变为了$resp,表示从响应结果中获取到对应的值

2.2.3 case_var_dict存放用户主动存储的变量

2.2.3.1 格式说明

case_var_dict主要用来存放执行当条case时,用户通过set动作主动存放起来的变量
比如

# 假设$resp['4'].text.data.list[0].id 获得的值为171568615504393167
$BT[set(userId, var($resp['4'].text.data.list[0].id))]
$BT[set(username, 张三)]

将会在该条case的名称空间的case_var_dict下存放两个变量,效果类似如下:

{
  "userId": "171568615504393167",
  "username": "张三"
}

2.2.3.2 如何引用

和req_dict、resp_dict类似,基本遵循jsonpath的方式引用,比如我们想引用上述例子的userId变量,它的jsonpath如下:

$.userId

通过bt_jsonpath引用该值

$var.userId

前缀从$变为了$var,表示是从用户变量中获取到值

2.2.4 全局配置变量

2.2.4.1 格式说明

在执行自动化测试的过程中,我们存在这样的情况,有些配置值,在本次测试过程中都是固定的,比如环境的ip,port等内容,并不会根据case不同而有区别,这些值就可以当成配置维护起来
bintest支持将这些全局不变的变量维护在一个ini文件中,执行测试时读取配置文件内容,比如下面例子

[dev]
host = https://www.bintest.dev.com
username = zhangsan
password = 123456Aa?

[test]
host = https://www.bintest.test.com
username = zhangsan
password = 123456Aa?

在执行测试时,先加载指定文件,之后在通过bt_jsonpath去引用对应的变量
可以通过zjbbintest.bintest.conf_load()方法加载配置文件,在case中引用具体变量

from zjbbintest.bintest import conf_load

if __name__ == '__main__':
    # 1. 加载配置
    conf_load('test-conf.ini')

加载后,我们实际会将配置文件的内容存放在一个全局作用的dict中,上面例子进行加载后,将会产生一个全局生效的conf_dict,如下:

{
  "dev": {
    "host": "https://www.bintest.dev.com",
    "username": "zhangsan",
    "password": "123456Aa?"
  },
  "test": {
    "host": "https://www.bintest.test.com",
    "username": "zhangsan",
    "password": "123456Aa?"
  }
}

2.2.4.2 如何引用

和req_dict、resp_dict类似,基本遵循jsonpath的方式引用,比如我们想引用上述例子中dev的host变量,它的jsonpath如下:

$.dev.host

bt_jsonpath如下:

$conf.dev.host

前缀从$变为了$conf,表示是从全局配置中获取到值

注意事项

  1. 过程变量的引用必须依赖bt字符串,即必须使用** $BT[ ] 进行包裹,同时,获取变量是var动作,需使用 var() 进行包裹,综合上述两个条件,必须使用 $BT[var( )] **将其包裹起来,才能被bintest的执行器正确解析,否则将会认为它只是一个普通的字符串

比如某变量的bt_jsonpath是

$resp['0'].text.data.modelSceneList[0].sceneName

case中使用的bt_str应为

$BT[var($resp['0'].text.data.modelSceneList[0].sceneName)]

看似很复杂,其实一点也不简单,但是不要担心,bintest的解析器会帮你自动解析bt_jsonpath,你需要做的就是选择一个合适的bt_jsonpath就可以

  1. 除了全局配置变量,请注意其余变量的引用时机,确保在被引用时变量值已经正确产生,否则将得到null,使程序出现不可预测的错误

比如,在3步骤的请求中引用6步骤响应中的内容,将会得到null,原因是在执行3步骤时,6步骤的响应还无法产生
再比如,在步骤中引用一个完全不存在的用户变量,将会得到null,引用用户变量时请确保该变量已经被使用set正确存储

2.3 执行用户自定义方法-@bt_func()和func_load()

2.3.1 为什么要有

bintest已经通过bt_jsonpath实现了执行过程中变量的引用,但是无法处理这样的需求:请求中需要某个值,该值是通过特殊的计算得到,无法从上下文中进行获取
比如验证登录的case中,我们需要一个密码加密后的字符串,这个加密后的字符串无法在之前的请求中得到,需要通过md5等方式进行计算,上下文中无法获取到加密后的字符串,需要用户自定义方法去,根据密码原文通过特定的加密算法得到加密后的密文
为了满足这些需求,我们引入了自定义方法的概念

2.3.2 如何使用

2.3.2.1 @bt_func()

@bt_func() 是 bintest 中用来注册用户自定义方法的装饰器
强调两点:

  1. @bt_func()装饰器参数个数和其所修饰的方法参数个数一致,表示其所修饰的方法入参都是什么类型
  2. @bt_func()所修饰的方法必须有返回值
@bt_func(str, int)
def user_func(args1, args2):
    pass
    return xxx

上面这段代码表示:

  1. user_func为一个用户自定义方法
  2. user_func方法args1的类型为str,args2的类型为int

2.3.2.2 func_load()

func_load()是用来加载用户自定义方法的方法,其接收参数为一个python模块,表示加载该模块中所有用@bt_func()修饰的python方法为自定义函数
下面给出一个例子

  1. 首先在user_func.py中使用@bt_func()注册用户自定义方法
import zjbbintest.bintest.bt_func as bt_func

@bt_func(str)
def password_md5(password):
    """
    md5加密
    :param password: 
    :return: 
    """
    pass
    return new_password
  1. 在另一个模块dome.py中使用func_load()进行加载
from zjbbintest.bintest import func_load
import user_func

if __name__ == '__main__':
    # 加载用户自定义方法
    func_load(user_func)
  1. 在case中通过bt_str进行使用
$BT[func(password_md5(123456Aa?))]
{
  "caseName": "登录",
  "description": "验证用户登录功能",
  "priority": 0,
  "tags": [
    "线上功能回归",
    "监控"
  ],
  "testSteps": [
    {
      "step": 0,
      "request": {
        ······
        "body": {
          "userName": "zhangsan",
          "password": "$BT[func(password_md5(123456Aa?))]"
        }
        ······
      }
    }
  ]
}

这样就可以在执行过程中自动执行用户的自定义方法了

2.4 执行用户自定义动作-@bt_action()和action_load()

2.4.1 为什么要有

在执行自动化case的过程中,我们会有这样的需求:当执行完某一步骤后,我们需要执行某些动作
比如,执行完case的最后一步时,我们期望将测试数据进行清理,操作删除数据库
再比如,我们的case中有类似训练模型的动作,请求完训练模型的接口后,需要等待5min等模型训练完毕在进行下一步操作
为了满足这些需求,我们引入了自定义动作的概念
可以看到,自定义动作自定义方法的区别是,自定义动作,我们不需要依赖返回的内容,只要执行动作就可以

2.4.2 如何使用

2.4.2.1 @bt_action()

@bt_action() 是 bintest 中用来注册用户自定义动作的装饰器
强调两点:

  1. @bt_action()装饰器参数个数和其所修饰的方法参数个数一致,表示其所修饰的方法入参都是什么类型
  2. @bt_action()所修饰的方法没有返回值
@bt_action(str, int)
def user_action1(args1, args2):
    pass

上面这段代码表示:

  1. user_action为一个用户自定义动作
  2. user_action方法args1的类型为str,args2的类型为int

2.4.2.2 action_load()

action_load()是用来加载用户自定义动作的方法,其接收参数为一个python模块,表示加载该模块中所有用@bt_action()修饰的python方法为自定义动作
下面给出一个例子

  1. 首先在user_action.py中使用@bt_action()注册用户自定义方法
import zjbbintest.bintest.bt_action as bt_action
import time

@bt_action(int)
def sleep_min(time_min):
    """
    程序睡眠time_min分钟
    :param time_min: 
    :return: 
    """
    time.sleep(time_min * 60)
  1. 在另一个模块dome.py中使用action_load()进行加载
from zjbbintest.bintest import action_load
import user_action

if __name__ == '__main__':
    # 加载用户自定义动作
    action_load(user_action)
  1. 在case中通过bt_str进行使用
$BT[action(sleep_min(5))]
{
  "caseName": "训练模型",
  "description": "验证训练模型是否正常",
  "priority": 0,
  "tags": [
    "线上功能回归",
    "监控"
  ],
  "testSteps": [
    {
      ······
      "action": [
        "$BT[action(sleep_min(5))]"
      ]
      ······
    }
  ]
}

这样就可以在执行过程中自动执行用户的自定义动作了

2.4.2.3 自定义方法和自定义动作的区别

  1. 使用目的不同:自定义方法用于得到一个值,在case中继续使用该值,自定义动作用于执行一个动作
  2. 使用字段不同:自定义方法用于testSteps中的request字段,自定义动作用于testSteps中的action字段

2.5 断言

在case的每个步骤,都会有断言的部分,在case的json结构中,断言部分是一个list,list中的每个元素是一个断言项,必须同时满足所有断言项(and逻辑),本次断言才能通过
例如下面代码:

"assert": [
    {
        "expect": 200,
        "actual": "$BT[var($resp['5'].status_code)]",
        "operator": "=="
    },
    {
        "expect": "$BT[var($req['4'].params.robotName)",
        "actual": "$BT[var($resp['5'].text.robotName)]",
        "operator": "=="
    }
]

当 第5步骤(从0开始)的响应状态码==200 并且 第5步骤(从0开始)响应值中的robotName == 第4步骤(从0开始)请求参数中的robotName 本条断言才能通过,否则case将执行失败

2.5.1 字段解释

expect:期望值,支持字面值,也支持bt字符串
actual:实际值,支持字面值,也支持bt字符串
operator:断言运算符,系统默认断言运算符,也支持自定义断言

2.5.1.1 默认断言运算符

bintest中支持默认断言方式共8中,分别如下:

序号 断言方式 断言operator 说明
1 相等断言EQUALS "=", "==", "相等", "等于", "一致", "一样" 判断期望值是否和实际值相等,否则断言失败
2 不等断言NOT_EQUALS "!=", "不等于", "不等", "不一致", "不一样" 判断期望值是否不等于实际值,否则断言失败
3 大于GREATER ">", "大于" 判断期望值是否大于实际值,否则断言失败
4 小于LESS "<", "小于" 判断期望值是否小于实际值,否则断言失败
5 大于等于GREATER_EQUALS ">=", "大于等于" 判断期望值是否是否大于等于实际值,否则断言失败
6 小于等于LESS_EQUALS "<=", "小于等于" 判断期望值是否小于等于实际值,否则断言失败
7 包含IN "in", "包含", "属于" 判断期望值是否包含实际值,否则断言失败
8 不包含NOT_IN "not in", "不包含", "不属于" 判断期望值是否不包含实际值,否则断言失败

2.5.1.2 自定义断言运算符-@bt_assert和assert_load()

上述8中默认断言涵盖了大部分断言的场景,但是bintest也支持自定义断言的方式满足使用者更为特殊的断言需求
先使用装饰器 bt_assert 进行自定义断言方法的注册:
需要注意的是通过@bt_assert修饰的python方法

  1. 入参必须为2个,第一个参数代表期望值,第二个参数代表实际值,否则将会抛出 BTAssertFormatCheckException 异常
  2. 返回值必须为一个boolean类型的值,表示断言成功还是失败,否则将会抛出 BTAssertFormatCheckException 异常

下面给出一个例子:

  1. 首先在user_assert.py中使用@bt_assert注册用户自定义断言方法
import zjbbintest.bintest.bt_action as bt_assert

@bt_assert
def user_assert(expect, actual):
    """
    用户自定义断言方法
    :param expect: 期望值
    :param actual: 实际值
    :return: boolean类型,表示断言成功or失败
    """
    pass
  1. 在另一个模块dome.py中使用assert_load()进行用户自定义断言的注册
from zjbbintest.bintest import assert_load
import user_assert

if __name__ == '__main__':
    # 加载用户自定义断言
    assert_load(user_assert)
  1. 在case中方法名进行使用
{
  ······
  "assert": [
    {
      "expect": "$BT[var($resp['0'].status_code)]",
      "actual": 200,
      "operator": "user_assert"
    }
  ]
  ······
}

3、 bintest框架使用方式

3.1 安装bintest

bintest已经发布在PYPI上,通过pip安装最新包
pypi地址:https://pypi.org/project/zjbbintest/
pip安装bintest: pip install -i https://pypi.org/simple zjbbintest

3.2 抓取har包

chrome浏览器,F12打开开发者模式,选择Network
勾选Preaerve log,开始在页面上执行功能case
image.png
执行完毕后将执行过程中的HAR进行保存,右击任何一个请求,选择Save all as HAR with content,将刚才操作记录进行保存
image.png

3.3 将HAR包解析为满足格式的json

通过bintest提供的 zjbbintest.bintest.rebuild 方法进行解析

rebuild(har_file_path, conf_ini_file_path, case_dir_path='')

rebuild方法共有三个参数
har_file_path:har文件的路径,必填
conf_ini_file_path:全局配置文件的路径,非必填,如果填写,可自动将配置文件中的变量通过bt_str的方式替换到case中,可参考2.2.4章节
case_dir_path:case文件生成的路径,非必填,如果不填写,case文件将会保存在HAR包所在位置
rebuild方法的作用是根据har文件和配置文件解析出case的初步形态

from zjbbintest.bintest import rebuild

if __name__ == '__main__':
    rebuild('新建自定义标签.har', conf_ini_file_path='test-conf.ini')

运行rebuild方法后,可通过交互式操作选择要保留的url,防止非必要内容过多,导致case编写过于复杂,如下:
image.png
通过用户输入 y or n 选择是否需要保留请求
最终会生成一个目录,目录名称为HAR文件的文件名,目录下有三个json文件,分别为:

  1. xxx_case.json
  2. xxx_bt_str.json
  3. xxx_req_resp.json

xxx为HAR包名称
下面进行详细解析

3.2.1 xxx_case.json

case的json文件,是rebuild产生的最重要的文件,可直接通过修改该文件的内容实现case的编写
产生本文件时会将case每个步骤的request的内容进行bt_str的替换,具体替换规则如下:
比如本条case在请求中body中有内容username=张三,并且『张三』在之前的请求或响应或配置文件中已经产生,那么bintest解析器就就会自动计算『张三』的bt_str,并且在本次请求中进行替换,从而实现上下文关系的自动解析
例子:
某条case第3步骤中body原本为:

{
  "name": "张三"
}

但是张三这个值在本case的第2步中已经产生,第2步的返回内容是:

{
  "status_code": 200,
  "text": {
    "code": 200,
    "msg": "success",
    "data": {
      "id": 3565353049423872,
      "name": "张三"
    }
  }
}

那么bintest的解析器就会将内容替换为如下

{
  "name": {
    "value": "张三",
    "type": "str",
    "bt_str": ["$BT[var($resp['2'].text.data.name)]"]
  }
}

替换后的内容是一个dict类型,包含了三部分

  • value:原来的值
  • type:原来值的类型
  • bt_str:所有满足要求的bt_str是一个list

根据替换的内容,保留bt_str中的一个,或者value即可
在解析器进行替换时,request的不同字段替换范围有所不同,具体范围如下:

  • url:只会从全局配置变量中进行替换
  • path:不进行替换
  • method:不进行替换
  • headers:只会从全局配置变量中进行替换
  • param:会从本步骤之前步骤的请求和响应(不包含本步骤)以及全局配置变量中进行替换
  • body:会从本步骤之前步骤的请求和响应(不包含本步骤)以及全局配置变量中进行替换

3.2.2 xxx_bt_str.json

保存变量对应的bt_str的文件,此文件中保存的是配置、请求和响应中所有变量对应的bt_str,用户可以翻阅此文件快速得到某一变量对应的bt_str
格式如下:

{
    "conf":{
        全局配置变量值: 对应的bt_str的list
        ······
    },
    "req": {
        步骤序号: {
            请求中变量值: 对应的bt_str的list
            ······
        }
        ······
    },
    "resp": {
        步骤序号: {
            响应中变量值: 对应的bt_str的list
            ······
        }
        ······
    }
}

例子:

{
  "conf": {
    "https://aicc.bce.baidu.com": [
      "$BT[var($conf.dev.host)]"
    ],
    "0600332f717442ae9198e3f39f22594b": [
      "$BT[var($conf.dev.ak)]"
    ],
    "ed81a69cffb849c98f587a4b367a5151": [
      "$BT[var($conf.dev.sk)]"
    ]
  },
  "req": {
    "0": {
      "1715686155045_393167": [
        "$BT[var($req['0'].params.t)]"
      ]
    },
    "1": {
      "1715686155045_555551": [
        "$BT[var($req['1'].params.t)]"
      ]
    }
  },
  "resp": {
    "0": {
      "200": [
        "$BT[var($resp['0'].status_code)]",
        "$BT[var($resp['0'].text.code)]"
      ],
      "31ae1550-11e5-11ef-9eda-b7486b924d94": [
        "$BT[var($resp['0'].text.requestId)]"
      ],
      "1715686155083": [
        "$BT[var($resp['0'].text.time)]"
      ],
      "OK": [
        "$BT[var($resp['0'].text.msg)]"
      ]
    },
    "1": {
      "200": [
        "$BT[var($resp['1'].status_code)]",
        "$BT[var($resp['1'].text.code)]"
      ],
      "31ae1551-11e5-11ef-9eda-b7486b924d94": [
        "$BT[var($resp['1'].text.requestId)]"
      ],
      "OK": [
        "$BT[var($resp['1'].text.msg)]"
      ],
      "realTime": [
        "$BT[var($resp['1'].text.data.robotModelAuth)]"
      ]
    }
  }
}

3.2.3 xxx_req_resp.json

保存了从HAR包中提取的请求和响应信息,按步骤进行保存,用户可清晰的看到某一步骤对应的请求和响应是什么
格式如下:

{
    步骤序号: {
        "request": {
            "url": xxx,
            "path": xxx,
            "method": xxx,
            "headers": {
                xxx: xxx
                ······
            },
            "param": {
                xxx: xxx
                ······
            },
            "body": {
                xxx: xxx
                ······
            }
        },
        "response": {
            "status_code": 响应状态码,
            "text": 响应非返回结果
        }
    }
    ······
}

例子:

{
  "0": {
    "request": {
      "url": "aicc.bce.baidu.com",
      "path": "/cfg/robot",
      "method": "GET",
      "headers": {
        "Accept": "application/json, text/plain, */*"
      },
      "params": {
        "pn": "1",
        "page": "1",
        "ps": "12",
        "pageSize": "12",
        "robotName": "%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%88%9B%E5%BB%BA%E9%80%9A%E7%9F%A5%E6%9C%BA%E5%99%A8%E4%BA%BA01",
        "robotType": "2",
        "robotScene": "1",
        "t": "1715690240090_917956"
      },
      "body": {}
    },
    "response": {
      "status_code": 200,
      "text": {
        "code": 200,
        "msg": "success",
        "data": {
          "pn": 1,
          "ps": 12,
          "orderBy": null,
          "order": null,
          "total": 1,
          "list": [
            [
              {
                "robotName": "自动化测试创建通知机器人01"
              }
            ]
          ]
        }
      }
    }
  },
  "1": {
    "request": {
      "url": "aicc.bce.baidu.com",
      "path": "/cfg/robot/drawDownList",
      "method": "GET",
      "headers": {
        "Accept": "application/json, text/plain, */*"
      },
      "params": {
        "robotType": "2",
        "tenantId": "10183",
        "pn": "1",
        "ps": "9999",
        "t": "1715690243201_592619"
      },
      "body": {}
    },
    "response": {
      "status_code": 200,
      "text": {
        "code": 200,
        "msg": "success",
        "data": {
          "total": 1,
          "list": [
            {
              "robotName": "自动化测试创建通知机器人01"
            }
          ]
        }
      }
    }
  }
}

3.4 执行case

完成3.3步的解析及其相关内容的替换后,可以将case放置在一个目录下,批量运行某个目录下的case
整体分为6步

  1. 加载全局配置变量(如果有),参考2.2.4章节
  2. 加载用户自定义方法(如果有),参考2.3章节
  3. 加载用户自定义动作(如果有),参考2.4章节
  4. 加载用户自定义断言(如果有),参考2.5.1.2章节
  5. 使用case_load()进行用例加载
  6. 使用run()执行用例

下面给出例子:

from zjbbintest.bintest import conf_load, func_load, action_load, assert_load, case_load, run
import test

if __name__ == '__main__':
    # 1. 加载配置,全局配置变量保存在test-conf.ini中
    conf_load('test-conf.ini')
    # 2. 加载用户自定义方法,将自定义方法写在了test.py中
    func_load(test)
    # 3. 加载用户自定义动作,将自定义动作写在了test.py中
    action_load(test)
    # 4. 加载用户自定义断言,将自定义断言写在了test.py中
    assert_load(test)
    # 5. 加载测试用例,加载aiob_case目录下的所有case
    case_load(["aiob_case"])
    # 6. 执行case,日志记录在./aiob.log下,产生报告的路径为res.html
    run("./aiob.log", "res.html")

执行case后,产生的测试报告如下:
image.png

3.4.1 case_load()

第5步提到的case_load是bintest中自带的加载case的方法,在zjbbintest.bintest中,使用前先进行方法导入

from zjbbintest.bintest import case_load

case_load(path_list, tags=[], priority=[])方法接收3个参数

  • path_list:路径列表,支持多个路径,递归加载路径下的所有case
  • tags:加载指定标签的case,支持多个标签,比如['label1', 'label2']代表加载拥有label1和label2标签的case,非必填,不填默认加载所有标签的case
  • priority:加载指定优先级的case,支持多个优先级,比如[1, 2]代表加载1和2的case,非必填,不填默认加载所有优先级的case

3.4.2 run()

第6步提到的run是bintest中自带的运行case的方法,在zjbbintest.bintest中,使用前先进行方法导入

from zjbbintest.bintest import run

run(log_path, report_path)方法接收2个参数

  • log_path:产生日志的路径
  • report_path:生成测试报告的路径

3.4.3 case运行状态码

case运行状态主要分为4大种,未执行、成功、失败和跳过
每个大类又细分为一些具体的异常场景,下面给出对照表

case运行状态码 状态码对应枚举值 说明 case运行状态
0 NOT_RUNNING 未执行 未执行状态
200 PASS 执行通过 成功状态
401 CASE_FORMAT_CHECK_ERROR case格式检查异常 跳过状态
402 BT_STRING_FORMAT_ERROR case中的bt字符串格式异常 跳过状态
403 BT_FUNC_NOT_FOUND case中bt-func未找到异常 跳过状态
404 BT_ACTION_NOT_FOUND case中bt-action未找到异常 跳过状态
405 BT_ASSERT_NOT_FOUND case中bt-assert未找到异常 跳过状态
406 BT_FUNC_FORMAT_CHECK_ERROR case中bt-func格式检查异常 跳过状态
407 BT_ACTION_FORMAT_CHECK_ERROR case中bt-action格式检查异常 跳过状态
408 BT_ASSERT_FORMAT_CHECK_ERROR case中bt-assert格式检查异常 跳过状态
409 ASSERT_DICT_FORMAT_ERROR case的assert的dict格式存在问题 跳过状态
410 CASE_INFO_IS_EMPTY case信息为空 跳过状态
500 FAIL case断言失败,执行未通过 失败状态
501 REQUEST_EXCEPTION 请求http接口异常 失败状态
999 OTHER_EXCEPTION 其他异常 跳过状态

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zjbbintest-3.0.tar.gz (64.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zjbbintest-3.0-py3-none-any.whl (43.4 kB view details)

Uploaded Python 3

File details

Details for the file zjbbintest-3.0.tar.gz.

File metadata

  • Download URL: zjbbintest-3.0.tar.gz
  • Upload date:
  • Size: 64.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.8.2

File hashes

Hashes for zjbbintest-3.0.tar.gz
Algorithm Hash digest
SHA256 292c7641d9d762cbf35e311d34f4019c8fb977f8c9499dc89f3f8e4ec4bbedd6
MD5 f02221242de84500ce9c2a5670bd18c2
BLAKE2b-256 3bedaead21f5387f60deb612ac3c70d394a8a4594e4ecef7620bc499e8df12d3

See more details on using hashes here.

File details

Details for the file zjbbintest-3.0-py3-none-any.whl.

File metadata

  • Download URL: zjbbintest-3.0-py3-none-any.whl
  • Upload date:
  • Size: 43.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.8.2

File hashes

Hashes for zjbbintest-3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8383a1707ff6e6f70ea718ca3ae075c9117650001a0bd7ae47830ad13c3e4da9
MD5 0800d095c9fd4b481354af1a63ea55a4
BLAKE2b-256 9cf8fa91edf5d232171bf9b9c238558a46898756572ecd31bca2186f91132fef

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page