pytest框架
pytest规则
1、pytest文件要以test_开头或_test结尾
2、测试类必须以Test开头,且不能有init方法
3、测试用例必须以test_开头
4、断言使用python原生的assert
安装pytest
在pycharm中的terminal,或者setting——project interpreter中安装
pytest参数(通过命令执行测试用例)
以下述代码讲解pytest的参数
# test_pytest_m.py,简单的测试用例编写 from time import sleep from config.driver_config import DriverConfig class TestPytestMClass: def test_open_bing(self): driver = DriverConfig().driver_config() driver.get('https://cn.bing.com') sleep(3) driver.quit() def test_open_baidu(self): driver = DriverConfig().driver_config() driver.get('https://www.baidu.com') sleep(3) driver.quit()
之前运行测试用例时,用鼠标右键run,现可以在terminal中输入命令运行
pytest testcases/test_pytest_m.py
也可以指定类和方法
# 运行类中所有的方法 pytest testcases/test_pytest_m.py::TestPytestMClass # 运行类的指定方法 pytest testcases/test_pytest_m.py::TestPytestMClass::test_open_baidu
使用pytest命令执行
1、-m:只运行被标记的测试用例
2、-k:模糊匹配文件名、类名、方法名,执行匹配到的方法
3、-s:在终端中打印调试信息
4、-v:显示执行详细信息
5、-q:显示简洁的执行信息
6、--collect-only:只收集用例,不执行
pytest -m
1、只运行baidu标记(只运行test_open_baidu()方法):pytest -m baidu
问题:不知道为何,还是会执行非baidu标记的其他文件用例
执行其他用例的发现解决:
2、运行标记为bing或baidu的(运行test_open_bing()、test_open_baidu()方法):pytest -m "bing or baidu"
from time import sleep import pytest from config.driver_config import DriverConfig class TestPytestMClass: @pytest.mark.bing def test_open_bing(self): driver = DriverConfig().driver_config() driver.get('https://cn.bing.com') sleep(3) driver.quit() @pytest.mark.baidu def test_open_baidu(self): driver = DriverConfig().driver_config() driver.get('https://www.baidu.com') sleep(3) driver.quit()
3、运行同时标记为bing和baidu的(只运行test_open_baidu()方法):pytest -m "baidu and bing"
from time import sleep import pytest from config.driver_config import DriverConfig class TestPytestMClass: @pytest.mark.bing def test_open_bing(self): driver = DriverConfig().driver_config() driver.get('https://cn.bing.com') sleep(3) driver.quit() @pytest.mark.bing @pytest.mark.baidu def test_open_baidu(self): driver = DriverConfig().driver_config() driver.get('https://www.baidu.com') sleep(3) driver.quit()
4、运行标记为baidu,标记不为bing,其他标记忽略的(只运行test_open_baidu()方法):pytest test_pytest_m.py -m "baidu and not bing"
from time import sleep import pytest from config.driver_config import DriverConfig class TestPytestMClass: @pytest.mark.bing def test_open_bing(self): driver = DriverConfig().driver_config() driver.get('https://cn.bing.com') sleep(3) driver.quit() @pytest.mark.baidu def test_open_baidu(self): driver = DriverConfig().driver_config() driver.get('https://www.baidu.com') sleep(3) driver.quit() @pytest.mark.runoob def test_open_runoob(self): driver = DriverConfig().driver_config() driver.get('https://www.runoob.com/python3/python3-os-file-methods.html') sleep(3) driver.quit()
pytest -k
-k:模糊匹配文件名、类名、方法名,执行匹配到的方法
1、pytest -k pytest:文件名中包含pytest,即会运行test_pytest_m文件
2、pytest -k class:类名中包含class,即会运行TestPytestMClass类
3、pytest -k open:函数名中包含open,即会运行包含open的3个方法
pytest -s
-s:在终端中打印调试信息
pytest -v
-v:显示执行详细信息(打印日志相对其他的更详细一些),一般是调试时使用
pytest -q
-q:显示简洁的执行信息(打印日志相对简洁一些),Jenkins集成执行时一般用-q
pytest --collect-only
--collect-only:只收集用例,不执行
fixture的使用和作用范围
fixture的用途:包裹测试用例
定义fixture:@pytest.fixture()
使用fixture:fixture的函数名作为用例的参数
fixture的作用范围:
- session
- class:每个类执行前只会执行一次fixture
- module:模块里所有用例执行前只执行一次fixture
- function(默认):@pytest.fixture(scope="function"),每个用例执行前都会执行一次fixture
from time import sleep import pytest from config.driver_config import DriverConfig class TestPytestMClass: @pytest.fixture(scope="class") # 一个类中只执行一次,即使所有的函数都调用该方法 def scope_class(self): print('我是class级别,我只执行一次') @pytest.fixture(scope="function") # 调用该方法的每个function都会执行 def driver(self): """ 下方的每个用例都初始化了一次driver,为了简化代码,可以定义一个driver方法, 后续要用到时,将fixture的函数名作为参数传递给测试用例 :return: """ get_driver = DriverConfig().driver_config() return get_driver @pytest.mark.bing def test_open_bing(self, driver, scope_class): # driver = DriverConfig().driver_config() driver.get('https://cn.bing.com') sleep(1) driver.quit() @pytest.mark.baidu def test_open_baidu(self, driver, scope_class): # driver = DriverConfig().driver_config() print("*****************************") driver.get('https://www.baidu.com') sleep(1) driver.quit() @pytest.mark.runoob def test_open_runoob(self, driver, scope_class): # driver = DriverConfig().driver_config() driver.get('https://www.runoob.com/python3/python3-os-file-methods.html') sleep(1) driver.quit()
使用场景1: 测试⽤例执⾏时,有的⽤例需要登陆才能执⾏,有些⽤例不需要登陆。setup 和 teardown ⽆法满⾜,fixture 可以。默认 scope(范围)function。
步骤:
1.导⼊ pytest
2.在登陆的函数上⾯加@pytest.fixture()
3.在要使⽤的测试⽅法中传⼊(登陆函数名称),就先登陆
4.不传⼊的就不登陆直接执⾏测试⽅法。
""" pytest之fixture """ import pytest @pytest.fixture() def login(): print("登录成功") return "token" # 第一种引用方式:直接将函数名当成参数传递 def test_fix(login): print("接口返回token:", login) # 第二种引用方式:使用pytest.mark.usefixtures @pytest.mark.usefixtures("login") def test_two(): print(login) def test_thr(): print("test 3")
fixture使用到项目中
添加商品的测试用例使用fixture实现:
1、使用fixture之前
# 使用fixture前 from time import sleep from config.driver_config import DriverConfig from page.login_page import LoginPage from page.left_menu_page import LeftMenuPage from page.goods_page import GoodsPage class TestAddGoods: def test_add_goods_001(self): driver = DriverConfig().driver_config() print("start test_add_goods_001 ______") LoginPage().login(driver, 'william') LeftMenuPage().click_level_one_menu(driver, '产品') sleep(1) LeftMenuPage().click_level_two_menu(driver, '新增二手商品') sleep(1) GoodsPage().add_new_goods(driver, '新增商品测试William', '新增商品详情测试William', 1, ['商品图片一.jpg'], 123, '上架', '提交') sleep(3) print("end test_add_goods_001 ____________") driver.quit()
2、使用fixture的实现流程
3、使用fixture:
# 使用fixture from time import sleep import pytest from config.driver_config import DriverConfig from page.login_page import LoginPage from page.left_menu_page import LeftMenuPage from page.goods_page import GoodsPage class TestAddGoods: @pytest.fixture() def driver(self): """ @pytest.fixture():默认为 @pytest.fixture(scope="function") 稍后可以在测试用例的传参中引用fixture函数的名称,以便在运行测试之前调用它 用例的执行过程: 1.先执行 get_driver 的实例化,返回 get_driver 2.然后将get_driver的返回值传到 test_add_goods_001 用例中,执行测试操作 3.测试用例执行完成后,返回driver函数中,继续执行 yield get_driver后面的代码 :return: """ print("start driver!!!!!!!!!!!!") get_driver = DriverConfig().driver_config() yield get_driver print("mid driver!!!!!!!!!!!!") get_driver.quit() print("end driver!!!!!!!!!!!!") def test_add_goods_001(self, driver): """ 在test_pytest_m.py文件解释了fixture的使用: 定义一个fixture的driver方法,后续要用到时,将fixture的函数名(driver)作为参数传递给测试用例 :return: """ # driver = DriverConfig().driver_config() print("start test_add_goods_001 ______") LoginPage().login(driver, 'william') LeftMenuPage().click_level_one_menu(driver, '产品') sleep(1) LeftMenuPage().click_level_two_menu(driver, '新增二手商品') sleep(1) GoodsPage().add_new_goods(driver, '新增商品测试William', '新增商品详情测试William', 1, ['商品图片一.jpg'], 123, '上架', '提交') sleep(3) print("end test_add_goods_001 ____________") # driver.quit()
conftest的使用
在上述 添加商品的测试用例 fixture使用中,fixture的driver函数在每个页面对象中都需要实现一遍,这样太过麻烦。所以引出conftest文件的使用,将driver函数提到conftest文件中封装。
conftest的使用原则:
- conftest可以跨文件调用(在testcases目录下的文件都可以调用)。
- conftest的文件名称是固定的。
- 就近原则(同级用例先找同级的conftest.py文件,如果同级没有再找其他层级的。比如说在testcases目录下有一个conftest.py文件,再在testcases目录下新建一个my_conftest目录,my_conftest目录下也有一个conftest.py文件。此时my_conftest目录下的用例执行my_conftest目录下的conftest.py文件;testcases目录下的用例执行testcases目录下的conftest.py文件)。
- conftest不能被其他文件导入。
- conftest可以设置多个pytest内置的钩子方法。
执行流程:
1、driver函数提出到conftest文件:
import pytest from config.driver_config import DriverConfig @pytest.fixture() def driver(): """ @pytest.fixture():默认为 @pytest.fixture(scope="function") 可以在测试用例的传参中引用fixture函数的名称(driver),以便在运行测试之前调用它。conftest文件中的方法,在调用时不需要导入文件和方法,直接使用driver进行传参 用例的执行过程: 1.发现测试用例入参有driver,进入到conftest中执行driver 2.先执行 get_driver 的实例化,yield返回 get_driver 3.然后将get_driver的返回值传到 测试用例中,执行测试操作 4.测试用例执行完成后,返回到conftest的driver函数中,继续执行 yield get_driver后面的代码 :return: """ print("start driver!!!!!!!!!!!!") get_driver = DriverConfig().driver_config() yield get_driver get_driver.quit() print("end driver!!!!!!!!!!!!")
2、test_add_goods.py文件
from time import sleep from page.login_page import LoginPage from page.left_menu_page import LeftMenuPage from page.goods_page import GoodsPage class TestAddGoods: def test_add_goods_001(self, driver): """ 在test_pytest_m.py文件解释了fixture的使用: 定义一个fixture的driver方法,后续要用到时,将fixture的函数名(driver)作为参数传递给测试用例 :return: """ print("start test_add_goods_001 ______") LoginPage().login(driver, 'william') LeftMenuPage().click_level_one_menu(driver, '产品') sleep(1) LeftMenuPage().click_level_two_menu(driver, '新增二手商品') sleep(1) GoodsPage().add_new_goods(driver, '新增商品测试William', '新增商品详情测试William', 1, ['商品图片一.jpg'], 123, '上架', '提交') sleep(3) print("end test_add_goods_001 ____________")
参数化
实战:上述的conftest.py文件中,用例固定了新增商品的填写信息。现在通过参数化@pytest.mark.parametrize("goods_info", goods_info_list),在用例的入参中传入goods_info,循环遍历goods_info_list中的商品信息,实现新增多个商品。
from time import sleep import pytest from page.login_page import LoginPage from page.left_menu_page import LeftMenuPage from page.goods_page import GoodsPage goods_info_list = [ { 'goods_title': '新增批量商品测试1', 'goods_details': '新增批量商品详情测试1', 'goods_num': 1, 'goods_pic_list': ['商品图片一.jpg'], 'goods_price': 100, 'goods_status': '上架', 'goods_button_name': '提交' }, { 'goods_title': '新增批量商品测试2', 'goods_details': '新增批量商品详情测试2', 'goods_num': 2, 'goods_pic_list': ['商品图片一.jpg'], 'goods_price': 200, 'goods_status': '上架', 'goods_button_name': '提交' } ] class TestAddGoods: @pytest.mark.parametrize("goods_info", goods_info_list) def test_add_goods(self, driver, goods_info): """ 直接调用conftest.py文件中的driver方法作为入参, 使用@pytest.mark.parametrize()对test_add_goods_01.py文件进行了参数化优化调整,循环遍历goods_info_list的元素 """ print("start test_add_goods_001 ______") LoginPage().login(driver, 'william') LeftMenuPage().click_level_one_menu(driver, '产品') sleep(1) LeftMenuPage().click_level_two_menu(driver, '新增二手商品') sleep(1) GoodsPage().add_new_goods( driver, goods_info['goods_title'], goods_info['goods_details'], goods_info['goods_num'], goods_info['goods_pic_list'], goods_info['goods_price'], goods_info['goods_status'], goods_info['goods_button_name'] ) sleep(3) print("end test_add_goods_001 ____________")
执行 test_add_goods用例时,会先新增商品标题为“新增批量商品测试1”的商品,走一个完成的测试用例流程(打开浏览器,执行用例,关闭浏览器),后再新增商品标题为“新增批量商品测试2”的商品。
分布式运行用例插件pytest-xdist
通过多线程的方式跑用例,会提高测试的效率。
安装插件pytest-xdist
执行命令
1、pytest -n 3(3表示同时打开3个浏览器运行,也可以设置为其他值)
2、pytest -n auto(auto表示根据CPU内核自动打开对应个数的浏览器)
3、pytest -n auto --dist=loadscope(表示同一个文件的测试用例,在一个浏览器进程中执行。在测试用例有执行的先后顺序时使用,但是除非迫不得已尽量不要写有先后关联的测试用例)
测试用例失败重跑插件pytest-rerunfailures
1、安装pytest-rerunfailures插件
2、浅试一下失败重跑:
import random class TestRerun: def test_rerun(self): num = random.randint(0, 3) print('num:', num) if num != 1: print("失败") raise Exception("出错了") else: print("成功")
3、执行重跑(一般时在命令行中对全局用例进行设置)
3.1、通过命令行执行重跑:
pytest -s testcases/test_rerun.py --reruns 5(此处最多重跑5次)
pytest -s testcases/test_rerun.py --reruns 5 --reruns-delay 1(重跑一次,延时1秒,下方的执行时间明显慢3秒)
3.2、在用例里面设置重跑:
import random import pytest class TestRerun: @pytest.mark.flaky(reruns=5, reruns_delay=1) # 设置重跑 def test_rerun(self): num = random.randint(0, 3) print('num:', num) if num != 1: print("失败") raise Exception("出错了") else: print("成功")
pytest报告插件pytest-html
1、安装插件:pip install pytest-html
2、执行生成报告的命令:pytest -s -q test_switch_window_handle.py --html=report.html
在下方路径中生成了测试报告和assets样式目录,如果没有assets,报告样式无法显示。
3、生成的报告内容:
删掉assets目录后,样式丢失
4、为解决步骤2中,删除assets目录导致样式丢失的问题,在执行命令中直接指定只包含html,这样只会生成report.html文件,样式直接包含在html文件中:
pytest -s -q test_switch_window_handle.py --html=report.html --self-contained-html
pytest配置文件pytest.ini
pytest.ini 文件一般放在项目的根目录下
[pytest] addopts = -s -q -n auto --html=report.html testpaths = testcases python_files = test*.py python_classes = Test* python_functions = test* log_cli = True log_data_format = %Y-%m-%d %H-%M-%S log_level = debug log_format = %(asctime)s - %(filename)s - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s log_file = test.log
log_cli:在控制台中也输出日志