pytest
pytest是一个非常成熟的全功能的Python测试框架,主要特点有以下几点:
简单灵活,容易上手,文档丰富;
支持参数化,可以细粒度地控制要测试的测试用例;
能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/ appnium等自动化测试、接口自动化测试( pytest+requests) ;
pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium (集成selenium) 、pytest-html (完美html测试报告生成)、pytest-rerunfailures (失败case重复执行)、pytest-xdist(多CPU分发)等;
测试用例的skip和xfail处理;
可以很好的和CI工具结合,例如Jenkins
安装pytest
pip install pytest
pytest官网
https://docs.pytest.org/ en/stable/
编写规则
测试文件以test开头(以test结尾也可以)
测试类以Test开头,并且不能带有init方法.
测试函数以test开头
断言使用基本的assert即可
# *_*coding:utf-8 *_* # @Author : zyb import pytest class TestCase(object): def test01(self): print('我是test01') def test02(self): print('我是test02') if __name__ == '__main__': # pytest.main(['-s','-v','test_01.py']) pytest.main(['-sv','test_01.py'])
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\python.exe cachedir: .pytest_cache rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collecting ... collected 2 items test_01.py::TestCase::test01 我是test01 PASSED test_01.py::TestCase::test02 我是test02 PASSED ============================== 2 passed in 0.13s ==============================
Console参数介绍
-v用于显示每个测试函数的执行结果
-q只显示整体测试结果
-s用于显示测试函数中print()函数输出
-x, --exitfirst,在第一-个错误或测试失败时立即退出
-h帮助
执行测试
配置PyCharm执行:
Toals->Python Integrated tools-> Default test runner
main方法
pytest.main(['-s', '-v', '01 -pytest.py'])
命令行
pytest -s -v test.py
Pytest标记
Pytest查找测试策略
默认情况下,pytest 会递归查找当前目录下所有以test开始或结尾的Python脚本
并执行文件内的所有以test开始或结束的函数和方法。
标记测试函数
由于某种原因(如test_ func2 的功能尚未开发完成),我们只想执行指定的测试函数。在pytest中有几种方式可以解决:
第一-种,显式指定函数名,通过::标记
test_ no_ mark.py::test func1
# *_*coding:utf-8 *_* # @Author : zyb import pytest def test01(): print('我是test01') def test02(): print('我是test02')
pytest test_01.py::test01
============================================================ test session starts ============================================================ platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collected 1 item test_01.py . [100%] ============================================================= 1 passed in 0.24s =============================================================
第二种,使用模糊匹配,使用-k选项标识。
pytest -k func1 test_ no_ mark.py
# *_*coding:utf-8 *_* # @Author : zyb import pytest def testaaa01(): print('我是test01') def testbbb02(): print('我是test02') def testaaabbb02(): print('我是test02')
cmd执行:
pytest -k aaa test_01.py
============================================================ test session starts ============================================================ platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collected 3 items / 1 deselected / 2 selected test_01.py .. [100%] ====================================================== 2 passed, 1 deselected in 0.15s ======================================================
第三种,使用pytest.mark在函数.上进行标记
给用例打标签
注册标签名,通过.ini 配置文件,操作如下:
新建pytest.ini 文件
# *_*coding:utf-8 *_* # @Author : zyb import pytest @pytest.mark.do def testaaa01(): print('我是test01') @pytest.mark.undo def testbbb02(): print('我是test02')
cmd执行
pytest -m do test_01.py
============================================================ test session starts ============================================================ platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 rootdir: F:\pyqt_demo, inifile: pytest.ini plugins: allure-pytest-2.8.40, Faker-4.1.0 collected 2 items / 1 deselected / 1 selected test_01.py . [100%] ====================================================== 1 passed, 1 deselected in 0.15s ======================================================
pytest参数化处理
在pytest中,也可以使用参数化测试,即每组参数都独立执行一次测试。
使用的工具就是pytest.mark.parametrize(argnames, argvalues)。
# *_*coding:utf-8 *_* # @Author : zyb import pytest data1 = ['aaa','bbb'] @pytest.mark.parametrize('pwd',data1) def test01(pwd): print(pwd) data2 = [[1,2,3],[6,7,8]] @pytest.mark.parametrize('a,b,c',data1) def test02(a,b,c): print(a,b,c)
============================================================ test session starts ============================================================ platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 rootdir: F:\pyqt_demo, inifile: pytest.ini plugins: allure-pytest-2.8.40, Faker-4.1.0 collected 4 items test_01.py .... [100%] ============================================================= 4 passed in 0.17s =============================================================
Pytest fixture
定义fixture跟定义普通函数差不多,唯-一区别就是在函数.上加个装饰器@pytest.fixture()
fixture命名不要以test开头,跟用例区分开。fixture是有返回值得,没有返回值默认为None。
用例调用fixture的返回值,直接就是把fixture的函数名称当做变量名称。
# *_*coding:utf-8 *_* # @Author : zyb import pytest @pytest.fixture() def init(): print('我是init') return 1 def test01(init): print('我是test01',init) def test02(init): print('我是test02',init) if __name__ == '__main__': pytest.main(['-sv','test_01.py'])
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\python.exe cachedir: .pytest_cache rootdir: F:\pyqt_demo, inifile: pytest.ini plugins: allure-pytest-2.8.40, Faker-4.1.0 collecting ... collected 2 items test_01.py::test01 我是init 我是test01 1 PASSED test_01.py::test02 我是init 我是test02 1 PASSED ============================== 2 passed in 0.25s ==============================
pytest setup和teardown简介
模块级(setup_ module/teardown_ module )开始于模块始末,全局的
# *_*coding:utf-8 *_* # @Author : zyb import pytest def setup_module(): print('我是模块级别的setup_module') def teardown_module(): print('我是模块级别的teardown_module') def test02(): print('我是测试用例test02') def test01(): print('我是测试用例test01') if __name__ == '__main__': pytest.main(['test_01.py','-sv'])
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\python.exe cachedir: .pytest_cache rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collecting ... collected 2 items test_01.py::test02 我是模块级别的setup_module 我是测试用例test02 PASSED test_01.py::test01 我是测试用例test01 PASSED我是模块级别的teardown_module ============================== 2 passed in 0.12s ==============================
函数级(setup_ function/teardown_ function )只对函数用例生效(不在类中)
# *_*coding:utf-8 *_* # @Author : zyb import pytest def setup_function(): print('我是函数级别的setup_function') def teardown_function(): print('我是函数级别的teardown_function') def setup_module(): print('我是模块级别的setup_module') def teardown_module(): print('我是模块级别的teardown_module') def test02(): print('我是测试用例test02') def test01(): print('我是测试用例test01') if __name__ == '__main__': pytest.main(['test_01.py','-sv'])
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\python.exe cachedir: .pytest_cache rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collecting ... collected 2 items test_01.py::test02 我是模块级别的setup_module 我是函数级别的setup_function 我是测试用例test02 PASSED我是函数级别的teardown_function test_01.py::test01 我是函数级别的setup_function 我是测试用例test01 PASSED我是函数级别的teardown_function 我是模块级别的teardown_module ============================== 2 passed in 0.14s ==============================
类级(setup_ class/teardown_ class )只在类中前后运行一次(在类中)
# *_*coding:utf-8 *_* # @Author : zyb import pytest class TestCase(object): @classmethod def setup_class(cls): print('我是类级别的setup_class')
@classmethon def teardown_class(cls): print('我是类级别的teardown_class') def test04(cls): print('我是测试用例test04') def test05(cls): print('我是测试用例test05') if __name__ == '__main__': pytest.main(['test_01.py','-sv'])
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\python.exe cachedir: .pytest_cache rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collecting ... collected 2 items test_01.py::TestCase::test04 我是类级别的setup_class 我是测试用例test04 PASSED test_01.py::TestCase::test05 我是测试用例test05 PASSED我是类级别的teardown_class ============================== 2 passed in 0.15s ==============================
方法级(setup_method/teardown_method)开始于方法始末(在类中)
# *_*coding:utf-8 *_*
# @Author : zyb
import pytest
class TestCase(object):
@classmethod
def setup_class(cls):
print('我是类级别的setup_class')
@classmethod
def teardown_class(cls):
print('我是类级别的teardown_class')
def setup_method(self):
print('我是方法级别的setup_method')
def teardown_method(self):
print('我是方法级别的teardown_method')
def test04(cls):
print('我是测试用例test04')
def test05(cls):
print('我是测试用例test05')
if __name__ == '__main__':
pytest.main(['test_01.py','-sv'])
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\python.exe cachedir: .pytest_cache rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collecting ... collected 2 items test_01.py::TestCase::test04 我是类级别的setup_class 我是方法级别的setup_method 我是测试用例test04 PASSED我是方法级别的teardown_method test_01.py::TestCase::test05 我是方法级别的setup_method 我是测试用例test05 PASSED我是方法级别的teardown_method 我是类级别的teardown_class ============================== 2 passed in 0.16s ==============================
类里面的(setup/teardown) 运行在调用方法的前后
# *_*coding:utf-8 *_* # @Author : zyb import pytest class TestCase(object): def setup(self): print('我是类里面的steup') def teardown(self): print('我是类里面的teardown') def test04(cls): print('我是测试用例test04') def test05(cls): print('我是测试用例test05') if __name__ == '__main__': pytest.main(['test_01.py','-sv'])
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-5.4.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\python.exe cachedir: .pytest_cache rootdir: F:\pyqt_demo plugins: allure-pytest-2.8.40, Faker-4.1.0 collecting ... collected 2 items test_01.py::TestCase::test04 我是类里面的steup 我是测试用例test04 PASSED我是类里面的teardown test_01.py::TestCase::test05 我是类里面的steup 我是测试用例test05 PASSED我是类里面的teardown ============================== 2 passed in 0.13s ==============================
pytest allure生成测试报告
安装
pip install allure-pytest
官方文档
https://docs.qameta.io/
下载地址
https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/
下载之后配置环境变量
allure用例描述
序号 | 使用方法 | 参数值 | 参数说明 | |
1 | @allure.epic() | epic描述 | 一级菜单 | |
2 | @allure.feature() | 模块名称 | 二级菜单 | |
3 | @allure.story() | 用户故事 | 三级菜单 | |
4 | @allure.title() | 用例的标题 | 标题 | |
5 | @allure.step() | 步骤 | 步骤 | |
6 | @allure.severity() |
|
BLOCKER CRITICAL NORMAL MINOR |
|
# *_*coding:utf-8 *_* # @Author : zyb import pytest import allure @pytest.fixture(scope="session") def login(): print("首先是登录的测试用例") @allure.step("步骤1:点击XXX") def step_01(): print("我是第一个步骤") @allure.step("我是第二个步骤") def step_02(): print("我是第二个步骤") @allure.feature("编辑界面") class TestCase(): @allure.story("我是第一个测试用例") def test01(self,login): step_01() step_02() print("我是第一个测试用例的代码位置") @allure.story("我是第二个测试用例") def test02(self,login): print("我是第二个测试用例执行的位置") if __name__ == '__main__': pytest.main(["--alluredir","./repports",'test_01.py'])
执行完成之后会生成一个repports目录,再次运行
allure serve ./repports test_01.py
前置后置:
'''conftest.py文件是作用域的管理,文件名称是固定写法,
可以利用他的性质,来给测试用例做前置后置处理的操作。--前置后置处理器'''
import pytest
#session整个回话只调用一次 package每个项目一次 module每个模块一次 class每个类一次 function每个函数一个
#注意 autouse=True 的话 ,不用每个函数都写前置函数名称 都可以调用。如果不写 就要调用,不调用不执行
# @pytest.fixture(scope='function',autouse=True)
代码:
import pytest
@pytest.fixture(scope='session', autouse=True)
def realize_session():
print('session之前')
yield
print('session之后')
调用代码:
def test_add_cart(self):
print('test_add_cart')
#测试自定义调用fixture函数方式1
@pytest.mark.usefixtures('aa')
def test_learn(self):
print('装饰器方式调用fixtures函数')
#测试自定义调用fixture函数方式1
def test_learn2(self,aa):
print('传参方式调用fixtures函数')
参数化:
# *_*coding:utf-8 *_* # @Author : zyb import pytest @pytest.mark.parametrize('uesrname',['aa','bb','cc']) @pytest.mark.parametrize('password',['aaa','bbb','ccc']) def test_can_01(uesrname,password): print(uesrname,password) @pytest.mark.parametrize(['uesrname','password'],[['aa','aaa'],['bb','bbb'],['cc','ccc']]) def test_can_02(uesrname,password): print(uesrname, password)