Package: yamlapi
yamlapiyamlapi接口测试框架QQ群529063263工程主页readthedocs：https://yamlapi-docs.readthedocs.io/zh_CN/latest/pypi：https://pypi.org/project/yamlapi/github：https://github.com/yjlch1016/yamlapiyamlapi即为yaml文件+api测试的缩写可看作是一个脚手架工具可快速生成项目的各个目录与文件支持unittest与pytest两种运行模式支持MySQL、PgSQL、MongoDB、Redis等数据库的增删改查支持Jenkins、GitLab Runner等CI/CD工具支持飞书、钉钉、企业微信等机器人只需维护一份或者多份yaml（或者json）文件即可pip install yamlapi安装yamlapi -h（或yamlapi --help）查看参数信息yamlapi -v（或yamlapi --version）查看版本号pip install -U yamlapi安装最新版yamlapi create --p=项目名称创建项目例如在某个路径下执行命令：yamlapi create --p=demo_projectyamlapi run --c=环境缩写运行项目例如在项目的根目录下面执行命令：yamlapi run --c=testyamlapi clean清理测试报告与日志目录下的所有文件类似于mvn cleanpip uninstall yamlapi卸载一、思路1、采用requests+unittest+ddt+PyMySQL+DBUtils+psycopg2-binary+pymongo+redis+influxdb+BeautifulReport+demjson+loguru+
PyYAML+ruamel.yaml+pytest+pytest-html+allure-pytest+pytest-reportlog+pytest-assume+pytest-rerunfailures+pytest-instafail+pytest-sugar+pytest-timeout+pytest-parallel+tablib2、requests是发起HTTP请求的第三方库3、unittest是Python自带的单元测试工具4、ddt是数据驱动的第三方库5、PyMySQL是连接MySQL的第三方库6、DBUtils是数据库连接池的第三方库7、psycopg2-binary是连接PgSQL的第三方库8、pymongo是连接Mongo的第三方库9、redis是连接Redis的第三方库10、influxdb是连接influxDB的第三方库11、BeautifulReport是生成html测试报告的第三方库12、demjson是解析非标格式json的第三方库13、loguru是记录日志的第三方库14、PyYAML与ruamel.yaml是读写yaml文件的第三方库15、pytest是单元测试的第三方库16、pytest-html是生成html测试报告的插件17、allure-pytest是生成allure测试报告的插件18、pytest-reportlog是替换--resultlog选项的插件19、pytest-assume是多重断言的插件20、pytest-rerunfailures是失败重跑的插件21、pytest-instafail是实时显示错误信息的插件22、pytest-sugar是显示进度的插件23、pytest-timeout是设置超时时间的插件24、pytest-parallel是多线程的插件25、tablib是导出多种格式数据的第三方库二、目录结构1、case是测试用例包2、report_log是测试报告和日志的目录3、resource是yaml文件的目录4、setting是工程的配置文件包5、tool是常用方法的封装包6、.dockerignore是在传递给docker引擎时需要忽略掉的文件7、.gitignore是.ignore插件需要排除的文件8、conftest.py是全局钩子文件9、Dockerfile是构建镜像的文件10、Jenkinsfile是Jenkins Pipeline文件11、pytest.ini是pytest的配置文件12、requirements.txt是第三方依赖库三、yaml、json文件说明yaml文件-case_name:用例名称step:-step_name:步骤名称mysql:---pgsql:---mongo:---redis:---request_mode:POSTapi:/api/testfile:---body:{"key_1":"value_1","key_2":"value_2"}headers:{"Content-Type":"application/json"}query_string:{"key_3":"value_3","key_4":"value_4"}expected_time:3expected_code:200expected_result:{"code":1,"message":"成功"}regular:variable:-name_1-name_2expression:-'"response_1":"(.+?)"'-'"response_2":"(.+?)"'json文件[{"case_name":"用例名称","step":[{"step_name":"步骤名称","mysql":[],"pgsql":[],"mongo":[],"redis":[],"request_mode":"POST","api":"/api/test","file":[],"body":"{\"key_1\":\"value_1\",\"key_2\":\"value_2\"}","headers":"{'Content-Type': 'application/json'}","query_string":"{'key_3':'value_3','key_4':'value_4'}","expected_time":3,"expected_code":200,"expected_result":"{\"code\":1,\"message\":\"成功\"}","regular":{"variable":["name_1","name_2"],"expression":["\"response_1\":\"(.+?)\"","\"response_2\":\"(.+?)\""]}}]}]1、外层有2个字段，内层有17个字段命名和格式不可修改，顺序可以修改字段中文名称是否必填格式注解case_name用例名称是step步骤是-列表格式1条用例可以有1个或者N个步骤，全部的步骤通过，本条用例才算通过step_name步骤名称是mysqlMySQL语句否-列表格式顺序不可修改pgsqlPgSQL语句否-列表格式顺序不可修改mongoMongo语句否-列表格式顺序不可修改redisRedis语句否-列表格式顺序不可修改request_mode请求方式是api接口路径是file文件否-列表格式顺序不可修改body请求体否缩进字典格式或者json格式headers请求头否缩进字典格式或者json格式query_string请求参数否缩进字典格式或者json格式expected_time预期的响应时间否expected_code预期的响应代码是expected_result预期的响应结果是-列表格式、缩进字典格式或者json格式regular正则否缩进字典格式variable变量名否-列表格式expression表达式否-列表格式2、mysql字段说明mysql： MySQL语句，-列表格式，顺序不可修改，选填位置索引作用是否必填格式注解第一行mysql[0]增删改否字符串增、删、改语句第二行mysql[1]查否字符串查语句（动态传参）第三行mysql[2]查否字符串查语句（数据库双重断言）第一行：mysql[0]第二行：mysql[1]第三行：mysql[2]第一行为增、删、改语句，第二行为查语句（动态传参），第三行为查语句（数据库双重断言）第一行是发起请求之前的动作，没有返回结果第二行是发起请求之前的动作，有返回结果，是为了动态传参第三行是发起请求之后的动作，有返回结果，但是不可用于动态传参，是为了断言实际的响应结果当不需要增删改查和双重断言时，可以不写mysql字段，或者三行都为空当只需要增删改时，第一行为增删改语句，第二行为空，第三行为空当只需要查时，第一行为空，第二行为查语句，第三行为空当只需要双重断言时，第一行为空，第二行为空，第三行为查语句3、pgsql字段说明pgsql： PgSQL语句，-列表格式，顺序不可修改，选填位置索引作用是否必填格式注解第一行pgsql[0]增删改否字符串增、删、改语句第二行pgsql[1]查否字符串查语句（动态传参）第三行pgsql[2]查否字符串查语句（数据库双重断言）第一行：pgsql[0]第二行：pgsql[1]第三行：pgsql[2]第一行为增、删、改语句，第二行为查语句（动态传参），第三行为查语句（数据库双重断言）第一行是发起请求之前的动作，没有返回结果第二行是发起请求之前的动作，有返回结果，是为了动态传参第三行是发起请求之后的动作，有返回结果，但是不可用于动态传参，是为了断言实际的响应结果当不需要增删改查和双重断言时，可以不写pgsql字段，或者三行都为空当只需要增删改时，第一行为增删改语句，第二行为空，第三行为空当只需要查时，第一行为空，第二行为查语句，第三行为空当只需要双重断言时，第一行为空，第二行为空，第三行为查语句4、mongo字段说明（参考mysql字段）mongo： Mongo语句，-列表格式，顺序不可修改，选填位置索引作用是否必填格式注解第一行mongo[0]增删改否-列表格式第一个元素为集合名，第二个元素为增删改，第三个元素为增删改参数第二行mongo[1]查否-列表格式第一个元素为集合名，第二个元素为查参数第三行mongo[2]查否-列表格式第一个元素为集合名，第二个元素为查参数第一行：mongo[0]第二行：mongo[1]第三行：mongo[2]第一行为增、删、改，第二行为查（动态传参），第三行为查（数据库双重断言）第一行是发起请求之前的动作，没有返回结果第二行是发起请求之前的动作，有返回结果，是为了动态传参第三行是发起请求之后的动作，有返回结果，但是不可用于动态传参，是为了断言实际的响应结果当不需要增删改查和双重断言时，可以不写mongo字段，或者三行都为空当只需要增删改时，第一行为增、删、改，第二行为空，第三行为空当只需要查时，第一行为空，第二行为查，第三行为空当只需要双重断言时，第一行为空，第二行为空，第三行为查5、redis字段说明（参考mysql字段）redis： Redis语句，-列表格式，顺序不可修改，选填位置索引作用是否必填格式注解第一行redis[0]增删改否-列表格式第一个元素为增删改，第二个元素为增删改参数第二行redis[1]查否-字符串格式查语句（动态传参）第三行redis[2]查否-字符串格式查语句（数据库双重断言）第一行：redis[0]第二行：redis[1]第三行：redis[2]第一行为增、删、改，第二行为查（动态传参），第三行为查（数据库双重断言）第一行是发起请求之前的动作，没有返回结果第二行是发起请求之前的动作，有返回结果，是为了动态传参第三行是发起请求之后的动作，有返回结果，但是不可用于动态传参，是为了断言实际的响应结果当不需要增删改查和双重断言时，可以不写redis字段，或者三行都为空当只需要增删改时，第一行为增、删、改，第二行为空，第三行为空当只需要查时，第一行为空，第二行为查，第三行为空当只需要双重断言时，第一行为空，第二行为空，第三行为查6、file字段说明file： 文件参数，-列表格式，顺序不可修改，选填位置类型是否必填格式注解第一行文件类型否字符串例如：file第二行文件名称否字符串例如：demo_excel.xlsx第三行MIME类型否字符串例如：application/octet-stream7、函数助手函数名称写法作用域数量限制正则表达式提取的结果${变量名}全局不限MySQL查询语句返回的结果{__SQL索引}本条用例不限PgSQL查询语句返回的结果{__PGSQL索引}本条用例不限Mongo查询语句返回的结果{__MONGO索引}本条用例不限Redis查询语句返回的结果{__REDIS索引}本条用例不限随机数字{__RN位数}本条用例不限随机英文字母{__RL位数}本条用例不限随机手机号码{__MP}本条用例不限随机日期时间字符串{__RD开始年份,结束年份}本条用例不限正则表达式提取的结果用${变量名}匹配，一条用例里面可以有多个MySQL查询语句返回的结果，即第二行mysql[1]返回的结果，用{__SQL索引}匹配即{__SQL0}、{__SQL1}、{__SQL2}、{__SQL3}。。。。。。一条用例里面可以有多个PgSQL查询语句返回的结果，即第二行pgsql[1]返回的结果，用{__PGSQL索引}匹配即{__PGSQL0}、{__PGSQL1}、{__PGSQL2}、{__PGSQL3}。。。。。。一条用例里面可以有多个Mongo查询语句返回的结果，即第二行mongo[1]返回的结果，用{__MONGO索引}匹配即{__MONGO0}、{__MONGO1}、{__MONGO2}、{__MONGO3}。。。。。。一条用例里面可以有多个Redis查询语句返回的结果，即第二行redis[1]返回的结果，用{__MONGO索引}匹配即{__REDIS0}、{__REDIS1}、{__REDIS2}、{__REDIS3}。。。。。。一条用例里面可以有多个随机数字用{__RN位数}，如{__RN15}，一条用例里面可以有多个随机英文字母用{__RL位数}，如{__RL10}，一条用例里面可以有多个随机手机号码用{__MP}，一条用例里面可以有多个随机日期时间字符串用{__RD开始年份,结束年份}，如{__RD2019,2020}，一条用例里面可以有多个以上9种类型在一条用例里面可以混合使用${变量名}的作用域是全局的，其它8种的作用域仅限该条用例四、用例demo1、包含mysql语句的demo：-case_name:【进项发票列表高级搜索】根据主键id查询step:-step_name:根据主键id查询mysql:--SELECT id FROM invoice_purchaser_main WHERE purchaser_tenant_id=${tenantId} AND purchaser_org_id=${orgId} AND purchaser_company_id=${companyId} AND paper_drew_date BETWEEN '2020-01-01 00:00:00' AND '2020-08-01 00:00:00' ORDER BY id DESC LIMIT 1;-SELECT id FROM invoice_purchaser_main WHERE purchaser_tenant_id=${tenantId} AND purchaser_org_id=${orgId} AND purchaser_company_id=${companyId} AND paper_drew_date BETWEEN '2020-01-01 00:00:00' AND '2020-08-01 00:00:00' ORDER BY id DESC LIMIT 1;request_mode:POSTapi:/${tenantId}/invoice/v1/pool/input/invoices/advance-querybody:{"and":[{"fieldName":"id","fieldValue":"{__SQL0}","operationType":"EQUAL"}],"sorts":[{"fieldName":"id","sortNo":0,"sortType":"DESC"}],"orgIds":["${orgId}"],"companyIds":["${companyId}"]}headers:Content-Type:application/json;charset=UTF-8xforce-saas-token:${xforce-saas-token}userId:${userId}query_string:appId:${appId}startPaperDrewDate:2020/01/01 00:00:00endPaperDrewDate:2020/08/01 00:00:00businessFlag:'true'pageNo:1pageSize:20expected_code:200expected_result:{"code":"INVOICE0200","message":"请求成功","id":"{__SQL0}"}2、包含pgsql语句的demo：-case_name:【查询物品列表】精确查询的用例step:-step_name:步骤一pgsql:--SELECT goods_name, salesbill_no, invoice_code, invoice_no, receiver_company_name, receiver_name, receiver_tel, receiver_addr, sender_company_name FROM goods WHERE tenant_id = '${tenantId}' ORDER BY create_time DESC LIMIT 1;-request_mode:GETapi:/${tenantId}/enterprise/v1/logistics/goodsheaders:Content-Type:application/json;charset=UTF-8xforce-saas-token:${xforce-saas-token}query_string:{"appId":"${tenantId}","orderNum":"","goodsName":"{__SQL0}","businessNo":"{__SQL1}","invoiceCode":"{__SQL2}","invoiceNo":"{__SQL3}","receiverCompanyName":"{__SQL4}","receiverName":"{__SQL5}","receiverTel":"{__SQL6}","receiverAddress":"{__SQL7}","senderCompanyName":"{__SQL8}","page":"1","size":"20"}expected_code:200expected_result:{"code":"LGSTZZ0200","message":"请求成功"}3、包含mongo语句的demo：-case_name:熊猫清理-OPPO-牛数-广告激活次留的用例step:-step_name:第一步：广告mongo:----ad_qili--{"imei":"{__FA0}"}-{"product_name":1,"_id":0}request_mode:GETapi:/oppo/common/apisquery_string:{"adid":"__ADID__","cid":"__CID__","imei":"{__RN38}","mac":"__MAC__","android_id":"{__RN39}","os":"__OS__","timestamp":"__TS__","campaign_id":"__CAMPAIGN_ID__","aid_name":"__AID_NAME__","csite":"__CSITE__","ctype":"__CTYPE__","product_name":"1812","market_name":"oppo_01","pkg":"com.geek.jk.weather"}expected_code:200expected_result:{"ret":0,"msg":"success"}mongo_result:["1812"]-step_name:第二步：埋点mongo:----data_qili--{"unique_id":"{__FA1}"}-{"unique_id":1,"active_match_status":1,"active_source":1,"active_status":1,"ad_market":1,"active_day":1,"active_error_msg":1,"is_actual":1,"_id":0}request_mode:POSTapi:/calculatebody:{"common":{"upload_time":"2020-09-0810:39:59.250","mpc":"中国联通","screen_width":"720","screen_height":"1440","os_system":"1","os_version":"7.1.1","imei":"{__FA0}","idfa":"","model":"OPPOA83","message_id":"1","sdk":0,"android_id":"{__FA1}","uuid":"{__RN40}","product_name":"1812"},"events":[{"gender":"未知","app_version":"0.0.9","page_type":"android","latitude":"1111.2222","ip":"172.16.90.44","receive_time":"{__RD2022,2025}","market_name":"oppo-01","event_type":"custom","cv":"1.2.3","user_id":"60066122","event_code":"imei","event_name":"激活","phone_num":"13653363989","network_type":"WIFI","age":"未知","oaid":"","longitude":"1111.1111","province":"provincetest","city":"test","ts":"2020-09-0810:39:59.250"}]}headers:{"Content-Type":"application/json;charset=UTF-8"}expected_code:200expected_result:{"ret":0,"msg":"success"}mongo_result:["{__FA1}","1","oppo","1","oppo_01","{__TODAY}","上报成功","1"]-step_name:第三步：次留mongo:--data_qili-update--{"unique_id":"{__FA1}"}-{"$set":{"active_day":"{__YESTERDAY}"}}---data_qili--{"unique_id":"{__FA1}"}-{"unique_id":1,"active_match_status":1,"active_source":1,"active_status":1,"ad_market":1,"active_day":1,"active_error_msg":1,"is_actual":1,"next_day_retain_status":1,"next_day_error_msg":1,"next_day_retain_day":1,"_id":0}request_mode:POSTapi:/calculatebody:{"common":{"upload_time":"2020-09-0810:39:59.250","mpc":"中国联通","screen_width":"720","screen_height":"1440","os_system":"1","os_version":"7.1.1","imei":"{__FA0}","idfa":"","model":"OPPOA83","message_id":"1","sdk":0,"android_id":"{__FA1}","uuid":"{__FA2}","product_name":"1812"},"events":[{"gender":"未知","app_version":"0.0.9","page_type":"android","latitude":"1111.2222","ip":"172.16.90.44","receive_time":"{__RD2022,2025}","market_name":"oppo-01","event_type":"cold_start","cv":"1.2.3","user_id":"60066122","event_code":"cold_start","event_name":"激活","phone_num":"13653363989","network_type":"WIFI","age":"未知","oaid":"","longitude":"1111.1111","province":"provincetest","city":"test","ts":"2020-09-0810:39:59.250"}]}headers:{"Content-Type":"application/json;charset=UTF-8"}expected_code:200expected_result:{"ret":0,"msg":"success"}mongo_result:["{__FA1}","1","oppo","1","oppo_01","{__YESTERDAY}","上报成功","1","1","上报成功","{__TODAY}"]五、Apollo配置文件demo（选用，非必须）test_scenario=测试场景：XXX接口测试test_story=测试故事：XXX接口测试test_case_priority=criticaltest_case_address=http://www.testcase.comtest_case_address_title=XXX接口测试用例地址# allure配置beautiful_filename=xxx_reportbeautiful_description=XXX接口测试报告# BeautifulReport配置html_report_title=XXX接口测试报告project_name=XXX接口自动化测试swagger_address=http://www.swagger.com/swagger-ui.htmltest_department=测试部门：tester=测试人员：# conftest配置test_case_format=yaml# 测试用例的格式：yaml或者json# 不可混用，只能选取一种格式first_test_case_file=demo_one.yaml# 第一个测试用例文件robot=feishu# 机器人：feishu、dingtalk或者为空# 不可混用，只能选取一种feishu_webhook=https://open.feishu.cn/open-apis/bot/v2/hook/XXXXXX# 飞书机器人webhookfeishu_secret=abcdefghij1234567890# 飞书机器人密钥card_header_title_content=飞书消息卡片标题# 飞书消息卡片标题card_elements_actions_text_content=飞书消息卡片跳转链接文字# 飞书消息卡片跳转链接文字card_elements_actions_url=https://demo.fesihu.com# 飞书消息卡片跳转链接dingtalk_webhook=https://oapi.dingtalk.com/robot/send?access_token=XXXXXX# 钉钉机器人webhookdingtalk_secret=1234567890abcdefghij# 钉钉机器人密钥wechat_webhook=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=XXXXXX# 企业微信机器人webhookinfluxdb_switch=true# 是否插入到influxDB, true或者为空# 不可混用，只能选取一种influxdb_host=www.influxdb.cominfluxdb_port=8086influxdb_database=influxdb_databaseinfluxdb_user=rootinfluxdb_password=123456influxdb_measurement=influxdb_measurement# InfluxDB数据库配置db_host=mysql.test.comdb_port=3306db_user=rootdb_password=123456db_database=# MySQL数据库配置pgsql_host=pgsql.test.compgsql_port=5432pgsql_user=rootpgsql_password=123456pgsql_database=pgsql_db_1# PgSQL数据库配置mongo_host=mongo.test.commongo_port=27017mongo_database=mongo_db_1mongo_user=rootmongo_password=123456# Mongo数据库配置redis_host=redis.test.comredis_port=6379redis_password=123456redis_database=0# Redis数据库配置六、运行1、unittest模式：python+测试文件名+环境缩写python case/demo_test.py dev开发环境python case/demo_test.py test测试环境python case/demo_test.py pre预生产环境python case/demo_test.py formal生产环境2、pytest模式：pytest+--cmd=环境缩写pytest --cmd=dev开发环境pytest --cmd=test测试环境pytest --cmd=pre预生产环境pytest --cmd=formal生产环境3、yamlapi模式：yamlapi+run+--c=环境缩写yamlapi run --c=dev开发环境yamlapi run --c=test测试环境yamlapi run --c=pre预生产环境yamlapi run --c=formal生产环境4、运行结果：会在report_log目录下生成以下文件allure-reportlog年月日.logreport.htmlreport.xmltest_case.csvtest_case.htmltest_case.jsontest_case.xlsxtest_case.yaml七、打包镜像，运行容器docker pull registry.cn-hangzhou.aliyuncs.com/yangjianliang/yamlapi:0.0.8从阿里云镜像仓库拉取yamlapi镜像docker build -t demo_image .docker build -t 镜像名称 .本地打包，demo_image为镜像名称，随便取docker run -e cmd="环境缩写" 镜像名称:latestdocker run -e cmd="dev" demo_image:latest开发环境docker run -e cmd="test" demo_image:latest测试环境docker run -e cmd="pre" demo_image:latest预生产环境docker run -e cmd="formal" demo_image:latest生产环境