pytest框架心得
参考地址
1.https://www.cnblogs.com/sundawei7/p/11956618.html#_label0_0
2.https://www.cnblogs.com/poloyy/tag/Pytest/
--------------------------------------------------------------------------------------------------------------------------------------------------------
pytest和unittest区别
unittest | pytest | |||
---|---|---|---|---|
用例编写规则 |
测试文件必须先import unittest |
测试文件名必须以“test_”开头或者"_test"结尾(如:test_ab.py) |
||
测试类必须继承unittest.TestCase |
测试方法必须以“test_”开头 |
|||
测试方法必须以“test_”开头 |
测试类命名以"Test"开头 |
|||
测试类必须要有unittest.main()方法 |
||||
用例前置和后置 |
unittest提供了setUp/tearDown,只能针对所有用例 |
pytest提供了模块级、函数级、类级、方法级的setup/teardown,比unittest的setUp/tearDown更灵活 模块级(setup_module/teardown_module)开始于模块始末,全局的 函数级(setup_function/teardown_function)只对函数用例生效(不在类中) 类级(setup_class/teardown_class)只在类中前后运行一次(在类中) 方法级(setup_method/teardown_method)开始于方法始末(在类中) 类里面的(setup/teardown)运行在调用方法的前后 |
||
断言 |
unittest提供了assertEqual、assertIn、assertTrue、assertFalse |
pytest直接使用assert 表达式 |
||
测试报告 |
unittest使用HTMLTestRunnerNew库 |
pytest有pytest-HTML、allure插件 |
||
失败重跑 |
unittest无此功能 |
pytest支持用例执行失败重跑,pytest-rerunfailures插件 |
||
参数化 |
unittest需依赖ddt库 |
pytest直接使用 |
||
用例执行 |
unittest默认执行全部用例,也可以通过加载testsuit,执行部分用例 |
pytest可以通过@pytest.mark来标记类和方法,pytest.main加入参数("-m")可以只运行标记的类和方法 |
用到的第三方库
1.pip install pytest
2.pip install pytest-html
3.pip install allure-pytest
4.pip install pytest-ordering
5.pip install pytest-rerunfailures
整体框架
1 E:\py_tests\ 2 ├─config 3 │ ├─env.ini #存放域名test_url、product_url与数据库链接所需的dbname、host、port、user、password 4 ├─logs 5 6 │ ├─log_error.log 7 8 │ ├─log_info.log 9 10 ├─main 11 12 │ ├─runAll.py #执行此文件,可执行所有的测试用例(集成Jenkins) 13 14 ├─report #该目录下存放执行用例后自动生成的报告 15 16 ├─test_case #测试用例目录 17 18 │ ├─project #测试项目目录 19 20 │ │ ├─interface #测试接口目录 21 22 │ │ │ ├─test_01_demo1.py 测试用例(注意命名) 23 24 │ │ │ ├─test_02_demo2.py 25 26 ├─utils #文件下是框架用到的公共类方法 27 28 ├─pytest.ini #必须是GBK格式的文件,该文件是pytest的配置文件
pytest.ini配置文件
1.执行用例时,控制台输出的结果的级别、生成报告的路径、错误用例自动执行的设置
addopts = -s -v --alluredir ../report/result --reruns=3
2.测试用例的目录与命名规则
testpaths = ./test_case/rmcoupon/
python_files = test_*.py
python_classes = Test_*
python_functions = test_*
[pytest] # pytest 执行的相关参数 addopts = -s -v --alluredir ../report/result --reruns=3 # 测试用例的目录 testpaths = ./test_case/project/ python_files = test_*.py python_classes = Test_* python_functions = test_* # 对@pytest.mark.xfail中预期与实际不一致的,标记为失败 xfail_strict=true
测试用例设计
test_01_demo1.py
# coding=utf-8 """ @File: test_01_demo1.py @Desc: xxxx @Time: 2020/9/02 10:26 @Author: mo """ import sys import allure import pytest @allure.feature('获取菜单列表接口') class Test_Demo1(object): # 类级,只调用 1 次 def setup_class(self): log_info.info('begin') self.url = "XXXXXXXX" # 类级,只调用 1 次 def teardown_class(self): log_info.info('end') # 函数级,每个函数(即每条用例)执行前调用 1 次 def setup_method(self): log_info.info('{} begin'.format(self.__class__.__name__)) # 函数级,每个函数(即每条用例)执行后调用 1 次 def teardown_method(self): log_info.info('{} end'.format(self.__class__.__name__)) @allure.story('菜单列表') @allure.title('验证菜单列表接口正常') @allure.description('输入正常值,验证 xxx接口 返回是否正确') @allure.severity(allure.severity_level.BLOCKER) @pytest.mark.parametrize('clientVersion, tabType', [('', ''), ('', '0')]) def test_01_demo1(self, clientVersion, tabType): log_info.info('{} begin'.format(sys._getframe().f_code.co_name)) body = { "clientVersion": clientVersion, "tabType": tabType } res = req_get(url=self.url, param=body) #此处为个人分装的方法 self.assert_.assert_code(res[2], 200) log_info.info('{} end'.format(sys._getframe().f_code.co_name)) if __name__ == '__main__': pytest.main(["-s", "test_01_demo1.py"])
skipif使用
condition字段的取值,必须是从导入的内容中拿到,比如,不同的py文件(测试用例文件)有依赖关系,A.py中接口返回的字段,需要在B.py中使用,首先我们需先将A中获取到的字段存储在map中,然后B文件引用map,从map中获取使用的字段
B.py
from utils import GlobalMap #如果未获取到id,则跳过此用例
@pytest.mark.skipif(condition='GlobalMap().get(\'id\') == "Null_"', reason='XXXXXXX')
def test_01_getinteractivedetal(self):
log_info.info('{} begin'.format(sys._getframe().f_code.co_name))
self.url = 'XXXXXXXXXXXXXXXXXXX'
res = req_get(url=self.url)
self.assert_.assert_code(res[2], 200)
self.assert_.assert_code(res[0]['code'], 0)
log_info.info('{} end'.format(sys._getframe().f_code.co_name))
GlobalMap.py代码如下:
import json import logging """ @File: GlobalMap.py @Desc: 实现全局变量 @Time: 2020/7/21 16:42 @Author: """ class GlobalMap: map = {} def set_map(self, key, value): if isinstance(value, dict): value = json.dumps(value) self.map[key] = value def set(self, **keys): try: for key_, value_ in keys.items(): self.map[key_] = str(value_) logging.debug(key_ + ":" + str(value_)) except BaseException as msg: logging.error(msg) raise msg def del_map(self, key): try: del self.map[key] return self.map except KeyError: logging.error("key:'" + str(key) + "' 不存在") def get(self, *args): key = None try: dic = {} for key in args: if len(args) == 1: dic = self.map[key] logging.debug(key + ":" + str(dic)) elif len(args) == 1 and args[0] == 'all': dic = self.map else: dic[key] = self.map[key] return dic except KeyError: logging.warning("key:'" + str(key) + "' 不存在") return 'Null_'
runAll.py
# coding=utf-8 import datetime import os import pytest import sys from utils.log import log_info, log_error from utils.shellUtil import Shell from utils.DeleteDir import setDir sys.path.append(r'../') # 待执行的用例地址 # project_name = sys.argv[1] project_name = 'project' # 执行接口用的网络环境 # execute_env = sys.argv[2] execute_env = 'test_url' if __name__ == '__main__': # 1、设置待执行用例的目录 current_directory = os.path.dirname(os.path.abspath(__file__)) root_path = os.path.abspath(os.path.dirname(current_directory) + os.path.sep + ".") # 定义执行用例所在的目录 cases = root_path + '/test_case/' + project_name try: log_info.info('自动化用例执行开始...')
#执行命令前,先删除之前执行后的测试报告 #setDir(root_path+'/report/') pytest.main(['-s', cases, '-v', '--alluredir', '../report/result']) log_info.info('自动化用例执行完毕!') except Exception as e: log_error.error('自动化用例执行失败:{}'.format(e)) # 生成 allure 报告 shell = Shell() cmd = 'allure generate %s -o %s --clean' % ('../report/result', '../report/allure_html') try: log_info.info("开始生成测试报告...") shell.invoke(cmd) log_info.info("结束生成测试报告!") except Exception as e: log_error.error("测试报告生成失败:{}".format(e)) raise
utils中的shellUtil.py是用来实现在cmd中执行pytest命令的
import subprocess class Shell: @staticmethod def invoke(cmd): output, errors = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() o = output.decode("unicode_escape") return o