版权说明
本框架系本人结合一些实践经验和开源框架设计思想, 在家基于兴趣爱好独立完成的代码开发.
源码只保存在私人电脑, 办公电脑上无. GitHub 开源与公司无关, 先把关系撇清, 不涉及侵权.
嘘.
框架定位
首先最重要的就是学习交流, 无商业用途. 其次本框架有一定实用价值, 可作为工作辅助工具, 解决现有技术无法处理的问题. 最后可以优化改造投入生产实用(若有更好的 idea 请务必告诉我, 求知若渴).
设计思想
技术栈
说明文字为本框架中用途.
python: 脚本语言.
requests:http 请求库.
allure: 测试报告
numpy: 数据格式兼容.
pandas:MySQL 返回数据处理.
PyMySQL: 连接 MySQL.
SQLAlchemy:MySQL 连接引擎, 支持 ORM.
texttable: 日志打印 sql 查询结果表格.
目录结构
用例组织方式
模板代码使用 code_auto.py 自动生成.
- self.api_dir = os.path.join(os.path.join(self.base_dir, 'api'), 'bu') # 1
- self.case_dir = os.path.join(os.path.join(self.base_dir, 'case'), 'sprint') # 2
- self.uri = '/api/post' # 3
- self.description = 'Demo auto code' # 4
- # 5
- self.body = """{}
- """
1 输入 API 子目录名称. 接口是按业务部门来区分的, 子目录名称建议按业务部门 (bu==Business Unit) 来设置.
2 输入测试用例子目录名称. 现在流行敏捷开发, 建议按迭代 sprint 或独立功能模块命名.
3 接口 uri. 需要注意的是, 开头要有 1 个斜杠 /.
4 接口描述. 如名称, 作用.
5 请求体.
执行后在 API 和 case 目录生成测试初始化代码.
域名, Headers/Cookie 涉及到环境变量, 在 data/env 设置.
- class _GldExp:
- x = 1
- headers = {"Content-Type": "application/json"}
- dao_x = Dao('host:port',
- 'username',
- 'password')
- test_url = 'https://x'
- class _Gld:
- x = 2
- headers = {"Content-Type": "application/json"}
- dao_x = Dao('host:port',
- "username",
- "password")
- test_url = 'https://x'
- def uuid_list(n):
- """Uuid list
- @param n: Number
- @return: A uuid list
- """ return [str(uuid.uuid4()).replace('-','') for i in range(n)]
- # Set environment name
- vars_ = _GldExp
2 个内部类_GldExp 和_Gld, 定义参数化环境变量.
在 env 文件中可以定义一些业务相关函数. 公共函数需要放到 common/func, 建议不要轻易把框架无关的函数放到公共函数里面.
import env 后, 使用 vars_引用来调用具体的环境变量, 如 vars_.test_url.
测试代码编写方式
API/bu 目录下, 每个接口 ==1 个 py 文件.
- class ApiPost(API):
- def __init__(self):
- super().__init__()
- self.url = vars_.test_url + "/api/post"
- def load(self):
- self.body = {}
- return self
- def send(self):
- self.res = self.req.post(url=self.url, headers=vars_.headers, JSON=self.body)
- self.set_content()
- return self.res
API 继承了基类 API. 根据不同环境初始化 vars_.test_url,load()方法用于加载参数, send()方法用于发送请求(视不同 method 修改对应的请求方法 & 参数, 如 get, 可以在 common/request.py 中找到相关定义).
测试代码完全面向对象.
- def test_default():
- x = ApiPost()
- x.load().send()
这样能很方便的在接口之间传递参数, 以及做参数化的工作.
比如, 在接口. py 中, 需要参数化 body 的 name:
- def load(self):
- self.body = {
- "name": self.name
- }
PyCharm 会提示此属性未定义, 忽略它.
在测试代码中写参数化就很简单:
- x.name = 'dongfanger'
- x.load().send()
JMeter 参数化方式
本框架参数化借鉴了 JMeter 的参数化方式. 也就是, 在接口发请求后, 对参数赋值; 在接口收到相应后, 提取参数. 这也是测试代码要完全面向对象的原因.
面向对象能较好的组织测试代码, 使代码逻辑清晰, 阅读易于理解.
比如, 先定义 2 个接口, 苹果树和商店:
- class AppleTree(API):
- def __init__(self):
- super().__init__()
- self.url = vars_.test_url + "/api/post/apple/tree"
- def load(self):
- self.body = {}
- return self
- def send(self):
- self.res = self.req.post(url=self.url, headers=vars_.headers, JSON=self.body)
- self.set_content()
- return self.res
- class ShopSale(API):
- def __init__(self):
- super().__init__()
- self.url = vars_.test_url + "/api/post/shop/sale"
- def load(self):
- self.body = {
- "apple": self.apple
- }
- return self
- def send(self):
- self.res = self.req.post(url=self.url, headers=vars_.headers, JSON=self.body)
- self.set_content()
- return self.res
测试代码编写, 苹果树先生产苹果, 再运输到商店, 商店卖苹果:
- def test_apple_to_shop():
- apple_tree = AppleTree()
- apple_tree.load().send() # 生产苹果
- good_apple = apple_tree.content['good_apple'] # content 在 API 基类中定义
- shop_sale = ShopSale()
- shop_sale.apple = good_apple # 传递参数
- shop_sale.load().send()
- print(shop_sale.content)
content 在 API 基类中定义:
- def set_content(self):
- """After request, assert status and set content
- """
- status_ok(self.res)
- res_json = self.res.JSON()
- assert 1000 == res_json.get('status')
- try:
- self.content = res_json['content']
- except KeyError:
- logger.info(f"{'*'* 26}\n"
- f"Response no content\n"
- f"{'*'* 26}\n")
先断言返回状态 ok, 再取响应 JSON 里面 key 为 content 的 value. 不同公司 JSON 规范不一样, 需要做调整.
批量执行用例生成测试报告
pytest_allure.py 批量执行测试用例.
- # Input the directory to run pytest
- run_dir = os.path.join(base_dir, 'case')
默认执行 case 目录下 test_开头或结尾的文件(pytest 规则). 测试方法需要以 test_开头.
可以指定目录, 如:
- # Input the directory to run pytest
- run_dir = os.path.join(os.path.join(base_dir, 'case'), 'sprint0001')
本框架借助 pytest_sessionfinishhook 函数实现了生成测试报告并自动打开浏览器.
- def pytest_sessionfinish(session):
- allure_report_dir_test = session.config.getoption('allure_report_dir')
- if allure_report_dir_test:
- html_dir = os.path.join(allure_report_dir_test, 'html')
- os.system(f'mkdir {html_dir}')
- os.system(f"allure generate {allure_report_dir_test} -o {html_dir}")
- os.system(f"allure open {html_dir}")
MySQL 支持
MySQL 主要用于: 一提供参数化赋值; 二数据库比对断言.
commons/dao.py 实现了相关功能. 在 data/env.py 中根据环境定义好连接后, 通过 vars_使用.
- dao_x = Dao('host:port',
- 'username',
- 'password')
- sql_result = vars_.dao_x.select('select id, name from new_table;')
dao 实现采用了 pandas+sqlalchemy, 对返回结果取值就可以按 dataframe 来, 如 sql_result['name'][0].
借助 texttable 会打印表格日志, 观察数据.
- [2020-03-22 18:14:13]Running sql
- select id, name from new_table;
- [2020-03-22 18:14:14]Sql result:
- +----+------+
- | id | name |
- +====+======+
- | 1 | w |
- +----+------+
- | 2 | g |
- +----+------+
值得说明的是, 为了数据校验方便, 默认会把无小数的 float 转换为 int, 如 5.0->5.
- @staticmethod
- def _convert(x):
- """Convert logic code
- @param x: Single cell data
- @return: Converted single cell data
- """
- # float to int
- if isinstance(x, float) and x % 1 == 0:
- return int(x)
- return x
结语
开源使我快乐.
分享才能收获更多.
我在 GitHub 等你.
https://github.com/dongfanger/pyface
版权申明: 本文为博主原创文章, 转载请保留原文链接及作者.
来源: https://www.cnblogs.com/df888/p/12592716.html