文章目录
- 前言
- 一、hrun目前的版本
- 二、环境准备 & httprunner的基本操作
-
- 1.安装python、Httprunner、allure-pytest:
- 2.环境验证,查看httprunner版本:
- 3.查看httprunner帮助:
- 4.生成脚手架(快速的生成一个hrun框架):
- 5.脚手架内的yml文件转换pytest文件(根目录下执行 ):
- 6.运行httprunner,单个文件(进入要运行文件的根目录下)
- 7.批量运行httprunner,运行文件集下的所有用例
- 8. 查看.har生成脚本帮助
- 三、httprunner的二次封装
-
- 1. 框架内的各文件解释
- 2. config文件内容
- 3. common文件内容
- 4.login文件内容
- 5.api_util文件内容
- 6.databases文件内容
- 7.databases_config_dev.cfg文件内容
- 8.api_testing文件内容
- 四、运行文件方法
-
- 1.右击文件来运行
- 2.点击方法左侧绿三角来执行单条用例
- 3.使用命令运行单个脚本
- 4.使用命令运行某个文件集下的所有用例
- 五、本地生成allure报告
- 六、jenkins配置pytest+httprunner
-
- 1.在jenkins上安装allure插件
- 2.配置job
- 3.进入job配置
- 4.运行Jenkins
- 5.详细的jenkins配置(转)
前言
Httprunner作者是debugtalk,是一个资深的开发者,目前开发httprunner已有5个版本一、hrun目前的版本
Httprunner用法比较简单,并且符合工作中的多种场景:
1、做完接口自动化,要求继续做此接口的性能测试(hrun内置locust模块,可以直接做压测)
2、需要做的接口过多,而且只需要替换部分的参数,接口自动化的工作就会完成(charles录制并导出.har文件,通过hrun命令转成.py文件)
3、request库的每一步的接口响应,都需要加断言,而hrun会自动生成断言方法
4、hrun集成的第三方库:loguru、jmespath、pytest
文章图片
二、环境准备 & httprunner的基本操作 1.安装python、Httprunner、allure-pytest:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple httprunner
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple allure-pytest
2.环境验证,查看httprunner版本:
httprunner -V
文章图片
3.查看httprunner帮助:
httprunner --help
文章图片
4.生成脚手架(快速的生成一个hrun框架):
httprunner startprojectPartsService
文章图片
文章图片
5.脚手架内的yml文件转换pytest文件(根目录下执行 ):
cdPartsService
hmake testcases
文章图片
6.运行httprunner,单个文件(进入要运行文件的根目录下)
方式一:pytest [filename]
方式二:hrun [filename]
方式三:httprunner run[filename]
7.批量运行httprunner,运行文件集下的所有用例
方式一:pytest [dirname]
方式二:hrun [dirname]
方式三:httprunner run[dirname]
8. 查看.har生成脚本帮助
har2case -h
三、httprunner的二次封装
文章图片
1. 框架内的各文件解释 allure:存放allure的报告结果集
api_data:存放接口的请求数据(可以是json文件、xlsx文件、yaml文件)
api_testing:存放api测试用例
api_util:便于api测试的封装方法
common:公共方法,比如读取数据库文件
config:存放全局配置文件
databases:存放数据库文件
login:登录解耦
2. config文件内容
文章图片
[base]
# the url domain you wants to use in UI testing
backend_url =https://wwww.baidu.cn/[report]
###pytest xxxx --alluredir ./report
###allure serve report###hrun test_post_api.yml --alluredir=allure
###allure generate ./allure/ -o ./reports --clean
backend_url为项目地址,如果有多套环境,可以放多个url来进行切换
3. common文件内容
文章图片
import configparser
import osclass ConfigReader:
def __init__(self, file_name):
self.config = configparser.ConfigParser()
config_path = os.path.split(os.path.realpath(__file__))[0] + os.sep + file_name
self.config.read(config_path, "utf-8")def get_config(self, config_item):
sections = self.config.sections()
for section in sections:
if self.config.has_option(section, config_item):
return self.config.get(section, config_item)def get_config_by_section(self, config_item, section_name):
return self.config.get(section_name, config_item)
这里只写了一个读取Config文件方法,便于切换多套数据库环境,后续有需要可以做补充
4.login文件内容
文章图片
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase# todo:导入httprunner模块
def get_base_url():# todo:封装project_host方法,为项目全局的API路径
from common import config_reader# todo: config_reader 引用 config_reader方法
return config_reader.ConfigReader('../config/base_config.cfg').get_config('backend_url')class TestCaseLogin(HttpRunner): # todo: 定义类名要以Test开头config = Config("testcase description")\
.verify(False)\# todo:忽略Https证书
.variables(**{
"Accept":"application/json, text/plain, */*",
"Cookie":"SIAM_IMAGE_CODE=762410696575033344;
SIAMTGT=TGT-892-4xraUmB3mrsA0MtWv11ENoZmq1t4CiBYgSGPJjG9NQrHpTqczc-SIAM;
LtpaToken=AAECAzYxRjBCN0Y5NjFGMTYwQjlDTj1iaWVzb25nbGluL089Zm90b25UaDZzgyb50ZEjaw/jBYfTHXONKQ==",
"userName": " xxxx",
"password": "xxxx",
"validCode": "xxx",
"brandId":"x",
})# TODO: 设置全局变量,下面的Step方法都可以使用到。这里设置的Accept、Cookie、userName、password、validCode、brandIdteststeps = [
Step(
RunRequest("登录")
.post(get_base_url()+"API路径")#todo:切换自己的api路径
.with_cookies(**{"aaf875be9aad4feca52ccece8eade2df": "WyIzMTE4NjIwNDQ3Il0"}) # todo:API的cookies
.with_json({"userName": "$userName", "password": "$password", "validCode": "$validCode"}) # todo: 引用定义好的全局变量
.extract()
.with_jmespath('body.data.token', 'token') # todo:从response获取响应值,最外层是固定的body
.with_jmespath('headers.Server', 'cookies')# todo: 后面为变量名取出 body的data下的token,所以前面指定了body,同理,也可以取header的data(有需要的话)
.validate()
.assert_equal("status_code", 200)
.assert_equal('headers."Content-Type"', "application/json")
.assert_equal("body.code", 200)
.assert_equal("body.message", "success")
),
]if __name__ == "__main__":
TestCaseLogin().test_start()
写入登录用例,方便后续的hrun文件call,实现login解耦
5.api_util文件内容
import time
from httprunner import HttpRunner,Config,Step,RunTestCase,RunRequest
from login.get_token import TestCaseLogin as GetToken# todo:引入login文件
def get_base_url():
from common import config_reader
return config_reader.ConfigReader('../config/base_config.cfg').get_config('backend_url')class APIRequestConstructor(HttpRunner):# todo:创建基础类,便于后面清除hrun的token信息和读取token信息
config = (
Config("API测试执行")
.base_url(get_base_url())
.verify(False))
teststeps = [
Step(
RunTestCase("通过登录API获取token")
.call(GetToken)
.export(*["token",]))]def reset_request_step(token=GetToken): # todo:基于类封装方法,默认不传则使用GetToken
APIRequestConstructor.teststeps.clear() # todo:属于单元测试框架内的teardown,给每个用例一个干净的执行环境
APIRequestConstructor.teststeps.append(# todo:添加step动作
Step(
RunTestCase("通过登录API获取token")
.call(token)
.export(*["token",])
))headers = {# todo:根据项目需要,写入headers数据
"Host": "xxx",
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json;
charset=utf-8",
"brandId": "3",
"Authorization": "${token}",
"Cookie": "xxxx",
}
cookies = {"aaf875be9aad4feca52ccece8eade2df": "WyIxNjUxMTM1OTE1Il0"} # todo:根据项目需要,写入cookies数据def create_run_request(request_name, url, request_json, body="body.data", headers=headers, cookies=cookies):# todo:封装用例步骤,放置API的名字、请求地址、请求参数
if request_json is not None: # todo:如果请求参数json格式不为空,则执行post请求
return RunRequest(request_name).post(url).with_headers(**headers).with_cookies(**cookies).with_json(
request_json
).extract().with_jmespath(body, "res_data")
else: # todo:如果请求参数json为空,则执行post请求
return RunRequest(request_name).get(url).with_headers(**headers).with_cookies(**cookies) \
.extract().with_jmespath(body, "res_data")def contruct_request_step(run_request_obj):
return Step(
run_request_obj
)def perform_requests_and_get_last_response(steps):# todo: 放入用例的执行步骤
APIRequestConstructor.teststeps.extend(steps)# todo:读取teststeps的步骤
obj = APIRequestConstructor()# todo:实例化对象
obj.test_start()
time.sleep(1)
res = obj.with_export(["res_data", "token"]).get_export_variables()
return res
对httprunner的二次封装,减少冗余代码
6.databases文件内容
import copy
import configparser
import os
class ConfigReader:
def __init__(self, file_name):
self.config = configparser.ConfigParser()
config_path = os.path.split(os.path.realpath(__file__))[0] + os.sep + file_name
self.config.read(config_path, "utf-8")def get_config(self, config_item):
sections = self.config.sections()
for section in sections:
if self.config.has_option(section, config_item):
return self.config.get(section, config_item)
class DB():
def __init__(self, database_name):
cf = ConfigReader("database_config_dev.cfg")# todo:连接的哪个数据库,如果有多套环境,记得这里也要切换下数据库配置
self.host = cf.get_config(database_name + "_host")
self.port = cf.get_config(database_name + "_port")
self.user = cf.get_config(database_name + "_user")
self.password = cf.get_config(database_name + "_password")
self.database_name = database_name
def connect(self):# todo:创建连接数据库方法
import pymysql
# 创建数据库连接
self.db = pymysql.connect(host=self.host, user=self.user, password=
self.password, database=self.database_name)
# 创建游标
self.cursor = self.db.cursor()def get_one(self, sql):# todo:返回一条数据
result = 0
try:
self.connect()
self.cursor.execute(sql)
result = self.cursor.fetchone()
self.close()
except Exception as e:
print('select error', e)
return resultdef get_all(self, sql):# todo:返回全部符合条件的查询结果
result = 0
try:
self.connect()
self.cursor.execute(sql)
result = self.cursor.fetchall()
self.close()
except Exception as e:
print("select error", e)
return resultdef __edit(self, sql):# 创建主函数
result = 1# 设置结果集,用于调用的时候做判断
try:# 这里是使用的try语句来尝试进行操作
self.connect()
self.cursor.execute(sql)
self.db.commit()# 注意的是,如果是对数据库做了修改、删除、增加的操作,那么一定要commit提交,查询和创建表不需要提交
self.close()
except Exception as e:# 如果操作失败,报出操作异常,且游标进行回滚
print('error :', e)
result = 0
self.db.rollback()
return resultdef insert(self, sql):
# 插入语句,以下三个都是一样的,只是调用的时候,我们看起来更加清晰而已
return self.__edit(sql)# 通过主函数的处理,来去执行sql语句def delete(self, sql):# 删除语句
return self.__edit(sql)def update(self, sql):# 修改语句
return self.__edit(sql)def close(self):# 关闭方法
self.cursor.close()# 关闭游标
self.db.close()# 关闭数据库
7.databases_config_dev.cfg文件内容
文章图片
[PMSClaimH3H51]
PMSClaimH3H51_host=xxx
PMSClaimH3H51_port=xxx
PMSClaimH3H51_user=xxx
PMSClaimH3H51_password=xxxx[PMSCommonH3H5]
PMSCommonH3H5_host=xxx
PMSCommonH3H5_port=xxxx
PMSCommonH3H5_user=xxx
PMSCommonH3H5_password=xx
文章图片
像是这里,有两套数据库环境,用这个方法封装就比较好用
8.api_testing文件内容
文章图片
import allure,pytest
from loguru import logger
from api_util.util import *@allure.feature("配件信息管理查询")
class TestSearChParts():
@pytest.fixture(scope="class", autouse=True)
@allure.feature("数据清除")
def teardown_(self):
yield
logger.debug("Implementation of the use case will end.")
@allure.story("分公司:配件信息管理查询")
def test_Search_Parts_GetToken(self):
reset_request_step()# todo: 登录文件引用,默认不传是GetToken
# todo: 创建请求方法create_search_parts 可自定义命名。调用create_run_request方法,传入API名称、API地址、request body
create_search_parts = \
create_run_request(
"配件信息管理", "api/common/partsInfo/sparepart/v1/getSparePartByPage", {"status":1,"pageSize":10,"pageNum":1,"loading":"false"}) \
.with_jmespath("body.data.list[0].id","ids") \
.validate() \
.assert_equal("status_code", 200).assert_equal("body.message", "success")
# todo:contruct_request_step放入执行步骤,如果有多个,需要逗号隔开
create_out_of_warranty_payment_step = contruct_request_step(
create_search_parts)
# todo:perform_requests_and_get_last_response放入需要执行用例,同样,如果有多个,需要逗号隔开
res = perform_requests_and_get_last_response(
[create_out_of_warranty_payment_step])
id = res['res_data']['list'][0]['id'] # todo: 取出接口response响应值,拿到依赖接口使用。因为封装的方法,response的body外部是res_data,所以这里需要加上
print(id)
@allure.story("服务站:配件信息管理查询")
def test_Search_Parts_Service_Station(self):
reset_request_step(Service_Station)
create_out_of_warranty_payment_run_request = \
create_run_request(
"配件信息管理", "api/common/partsInfo/sparepart/v1/getSparePartByPage",
{"status": 1, "pageSize": 10, "pageNum": 1, "loading": "false"}) \
.validate() \
.assert_equal("status_code", 200).assert_equal("body.message", "success")
create_out_of_warranty_payment_step = contruct_request_step(
create_out_of_warranty_payment_run_request)
res = perform_requests_and_get_last_response(
[create_out_of_warranty_payment_step])
四、运行文件方法 1.右击文件来运行
文章图片
2.点击方法左侧绿三角来执行单条用例
文章图片
3.使用命令运行单个脚本
文章图片
pytest [filename]
文章图片
也可以使用 hrun [filename]
文章图片
4.使用命令运行某个文件集下的所有用例 如果要运行整个文件集下的所有用例,可以直接pytest [dirname]
五、本地生成allure报告
pytest xxxx --alluredir ./report# todo: xxx为hrun文件名字,也可以是文件夹名字。生成allure结果集
文章图片
allure serve report# todo:本地查看allure报告(控制台会给出url地址)
文章图片
allure generate ./report/ -o ./reports --clean# todo:清除历史allure结果集,在reports文件夹下新增allure报告
文章图片
六、jenkins配置pytest+httprunner 1.在jenkins上安装allure插件 Jenkins系统管理——全局工具配置——Allure Commandline添加配置
勾选自动安装,选择from maven central,版本选择最新版本,目前是2.7
文章图片
2.配置job 新建任务:自由风格的软件项目
文章图片
3.进入job配置
文章图片
配置git
Repository URL : git地址,记得后面加上.git
Credentials : 拉取git代码凭证
文章图片
点击【添加】按钮,进入jenkins添加凭证弹窗内
文章图片
**Branches to build:**分支填写 */master,master分支,如果有需要,可以填写其它分支
文章图片
cron定时配置
文章图片
点问号,看帮助即可。这里设置的是每天凌晨6点构建一次job
shell构建
这里选择Execute shell,使用shell命令执行。这里的因为Jenkins拉取代码会在固定的.jenkins/workspace这个目录,${WORKSPACE}变量指向了这个地址。
最后保存就可以了!
文章图片
Post-build Actions:构建后动作,点击add post-build action选项,选择allure,Results的path中填写的名称要和${WORKSPACE}后的名称,不然报告内容为空。
文章图片
文章图片
4.运行Jenkins 在首页可以查看新的任务,点击最右方小图标就可以构建了。也可以点击name进入任务中,点击开始构建。点击Allure Report就可以查看本次运行的测试结果。
文章图片
文章图片
5.详细的jenkins配置(转) 【Httprunner|Httprunner系列(十)(企业级接口自动化测试框架定制)】https://blog.csdn.net/weixin_42923777/article/details/102551493
推荐阅读
- 测试|【自研】接口自动化测试平台
- #|疫情让我使用V-rep仿真(结合pythonAPI)实现机器人视觉巡线+pid调速,
- python|基于python+vue+elementUI邯郸家乡网红旅游景点美食导游平台(前后端分离)#毕业设计
- 面试|为什么我们从 Python 切换到 Go
- python 自定义异常/raise关键字抛出异常
- Java|权限系统控制到按钮级别开源推荐 Spring Boot-Shiro-Vue
- 心得|记录下最近七天一点研究
- SQL|这道 Mysql 的解题思想,值得学习!
- Python学习|GitHub原生AI代码生成工具Copilot的试用记录