Pytest框架教程

Pytest框架教程

本文参考了官方文档和一些乐于分享的大佬的博客,结合自己的理解完成。学习pytest框架的小白,需要按照教程自己敲一遍,配置一遍,摸索一下整个框架的运行逻辑,数据流的走向,文字虽多,请细细看完,有问题欢迎在群里提出,相互学习,互相指正。希望大家有所收获,学有所得。(群:自动化测试-夜行者:816489363)

--成都-阿木木

框架说明

官方文档:https://docs.pytest.org/en/latest/contents.html

感谢慕城南风的博客:https://blog.csdn.net/lovedingd/article/details/98952868

Pytest支持的插件库:https://plugincompat.herokuapp.com/

第三方插件库:https://docs.pytest.org/en/latest/plugins.html

pytest兼容unittest

pytest兼容以前的unittest,只需要少量的更改代码即可,下面就大家熟悉的setup、teardown以及html报告进行说明

  1. setup和teardown

setup和teardown主要分为:类级、函数级。

运行于测试方法前后:

  1. #!/user/bin/env python  
  2. # -*- coding: utf-8 -*-  
  3.   
  4. """  
  5. ------------------------------------  
  6. @Project : pyqt5_study  
  7. @Time    : 2020/8/4 9:33  
  8. @Auth    : chineseluo  
  9. @Email   : 848257135@qq.com  
  10. @File    : test_setup_teardown.py  
  11. @IDE     : PyCharm  
  12. ------------------------------------  
  13. """  
  14. import pytest  
  15.   
  16.   
  17. class TestSetupTeardown():  
  18.     def setup(self):  
  19.         print("运行于测试方法之前")  
  20.   
  21.     def teardown(self):  
  22.         print("运行于方法之后")  
  23.   
  24.     def test_01(self):  
  25.         print("这是第一个方法")  
  26.   
  27.     def test_02(self):  
  28.         print("这是第二个方法")  
  29.   
  30.   
  31. if __name__ == '__main__':  
  32.     pytest.main("-s test_setup_teardown.py")  

运行结果:

  1. test_setup_teardown.py::TestSetupTeardown::test_01   
  2. 运行于测试方法之前  
  3. PASSED                [ 50%]这是第一个方法  
  4. 运行于方法之后  
  5.   
  6. test_setup_teardown.py::TestSetupTeardown::test_02   
  7. 运行于测试方法之前  
  8. PASSED                [100%]这是第二个方法  
  9. 运行于方法之后  

运行于测试类的始末:

  1. #!/user/bin/env python  
  2. # -*- coding: utf-8 -*-  
  3.   
  4. """  
  5. ------------------------------------  
  6. @Project : pyqt5_study  
  7. @Time    : 2020/8/4 9:33  
  8. @Auth    : chineseluo  
  9. @Email   : 848257135@qq.com  
  10. @File    : test_setup_teardown.py  
  11. @IDE     : PyCharm  
  12. ------------------------------------  
  13. """  
  14. import pytest  
  15.   
  16.   
  17. class TestSetupTeardown():  
  18.     @classmethod  
  19.     def setup_class(self):  
  20.         print("运行于测试类之前")  
  21.   
  22.     @classmethod  
  23.     def teardown_class(self):  
  24.         print("运行于测试类之后")  
  25.   
  26.     def test_01(self):  
  27.         print("这是第一个方法")  
  28.   
  29.     def test_02(self):  
  30.         print("这是第二个方法")  
  31.   
  32.   
  33. if __name__ == '__main__':  
  34.     pytest.main(["-s","test_setup_teardown.py"])  
  1. html测试报告

使用pytest的测试报告插件可以替换unittest本身的HTMLTestRunner报告

安装:pip install pytest-html

使用方式:命令行格式:pytest --html=用户路径/report.html

pytest框架使用约束

所有的单测文件名都需要满足test_*.py格式或*_test.py格式。

在单测文件中,测试类以Test开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)

在单测类中,可以包含一个或多个test_开头的函数。

此时,在执行pytest命令时,会自动从当前目录及子目录中寻找符合上述约束的测试函数来执行。可以在pytest.ini中修改测试目录、测试模块、测试类、测试方法扫描进行默认修改。

Pytest Exit Code含义清单

程序运行成功结束控制台输出:Process finished with exit code 0  

Exit code 0 所有用例执行完毕,全部通过

Exit code 1 所有用例执行完毕,存在Failed的测试用例

Exit code 2 用户中断了测试的执行

Exit code 3 测试执行过程发生了内部错误

Exit code 4 pytest 命令行使用错误

Exit code 5 未采集到可用测试用例文件

pytest之fixture

  1. Fixture作用

fixture修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作。

fixture是在测试函数前后运行,由pytest执行的外壳函数;代码可以定制,满足多变的测试需求,包括定义传入测试中的数据集,配置测试前系统的初始工作,为批量测试提供数据源等等,fixture是pytest用于将测试前后进行预备,清理工作的代码分离出核心测试逻辑的一种机制

  1. 说明

@pytest.fixture()装饰器用于申明函数是一个fixture,如果测试函数的参数列表中包含fixture,那么pytest会检测到,检测顺序是,优先搜索该测试所在的模块,然后搜索conftest.py,并在测试函数运行之前执行该fixture,fixture可以完成测试任务,也可以返回测试数据给测试函数

scope:被标记方法的作用域

function" (default):作用于每个测试方法,每个test都运行一次

"class":作用于整个类,每个class的所有test只运行一次

"module":作用于整个模块,每个module的所有test只运行一次

"session:作用于整个session(慎用),每个session只运行一次

params:(list类型)提供参数数据,供调用标记方法的函数使用

autouse:是否自动运行,默认为False不运行,设置为True自动运行

pytest --setup-show test_example1.py(可以看到执行过程顺序)

3、测试数据返回(参数化)

A.返回测试数据

  1. # coding:utf-8  
  2. import pytest  
  3.   
  4.   
  5. # @pytest.fixture()不传参,默认是function级别的,也就是只在test开头函数前后执行  
  6. @pytest.fixture()  
  7. def fixture_test():  
  8.     print("方法执行前执行")  
  9.     yield  
  10.     print("方法执行后执行")  
  11.   
  12. def test_data(fixture_test):  
  13.     assert 2 == 2  
  1. 返回测试数据
  1. # coding:utf-8  
  2. import pytest  
  3.   
  4.   
  5. # @pytest.fixture()不传参,默认是function级别的,也就是只在test开头函数前后执行;也可以使用fixture返回数据  
  6. @pytest.fixture()  
  7. def fixture_test():  
  8.     return [1, 2, 3, 4]  
  9.   
  10. def test_data(fixture_test):  
  11.     print(fixture_test[1])  
  12.     assert 2 == fixture_test[1]  

4、Fixture函数存放位置

  1. 单个测试模块文件内,只有该模块文件的类和方法可以访问到该fixture函数
  2. 如果希望多个测试文件共享fixtrue,可以在某个公共目录下新建一个fixture,将fixture放在里面

  1. Fixture作用范围
  1. function级别作用域

function每个函数或方法都会调用(有两种写法,不传递参数,默认就是function,也可以指定scope="function",来进行作用域的指定

  1. @pytest.fixture()  
  2. def fixture_function():  
  3.     print("fixturetest测试1")  
  4.     return 1  
  5.   
  6. @pytest.fixture(scope="function"):  
  7. def fixture_function():  
  8.     print("fixture测试2")  
  9.     return 2  
  10.   
  11. def test_fixture(fixture_function1):  
  12.     assert 1 == fixture_function1  
  1. Class级别作用域
  1. # coding:utf-8  
  2.   
  3. import pytest  
  4.   
  5.   
  6. # @pytest.fixture(scope="class")只在类的前后执行一次  
  7. @pytest.fixture(scope="class")  
  8. def fixture_class():  
  9.     print("类前执行一次")  
  10.     yield  
  11.     print("类后执行一次")  
  12.   
  13.   
  14. class TestCase:  
  15.     def test_1(self, fixture_class):  
  16.         print("类方法")  
  1. Module级别作用域
  1. # coding:utf-8  
  2. import pytest  
  3.   
  4.   
  5. @pytest.fixture(scope="module")  
  6. def fixture_module():  
  7.     print("模块执行前执行")  
  8.     yield  
  9.     print("模块执行后执行")  
  10.   
  11.   
  12. def test_1(fixture_module):  
  13.     print(" 测试方法")  
  14.   
  15.   
  16. class TestCase:  
  17.     def test_2(self, fixture_module):  
  18.         print(" 类方法")  
  1. Session级别作用域

session是多个文件调用一次,可以跨越.py文件调用,每个.py文件都是module

当我们有多个.py文件的用例时,如果多个用例只需要调用一次fixture,可以设置scope="session",并且写入到conftest。py文件里面

  1. import pytest  
  2.   
  3.   
  4. @pytest.fixture(scope="session")  
  5. def fixture_session():  
  6.     print("全局前执行一次")  
  7.     yield  
  8.     print("全局后执行一次")  
  9.   
  10. # coding:utf-8  
  11. import pytest  
  12.   
  13.   
  14. def test_1(fixture_session):  
  15.     print("方法")  
  16.   
  17.   
  18. class TestCase:  
  19.     def test_2(self, fixture_session):  
  20.         print("类方法")  

pytest之配置文件

  1. pytest非测试文件介绍

1、pytest.ini:pytest的主配置文件,可以改变pytest的默认行为,其中有很多可以配置的选项,包含日志,命令行的一些参数,控制台输出的信息等等

2、conftest.py:是本地的插件库,其中的hook函数和fixture将作用于该文件所在目录以及所有子目录

  1. 如何查看pytest.ini选项

使用pytest --help查看pytest.ini所有设置选项

  1. 如何更改默认命令行选项

pytest -v --verbose 可以输出详细信息

[pytest]

addops = -v --alluredir ./allure-result(addopts增加默认执行的操作步骤,简化命令行参数)(allure测试报告默认在json文件目录下生成,可以使用allure generate jsonpathdir -o allurepathdir更改)

ps:

如何使用allure生成测试报告

1、brew install allure

2、安装allure-pytest

3、运行case时增加命令行选项pytest -v --allure ./allure-results test.py

4、生成测试报告allure generate allure-results -o allure

有哪些常用的命令行选项呢?

-v:输出详细信息,显示具体执行了那些测试用例

--collect-only 展示在给定的配置下那些测试用例会被执行,仅用于展示,不执行

-k 允许使用表达式指定希望运行的测试用例

exp:pytest -v -k 'baidu' test.py(在pytest中查找含有baidu关键字的case执行)

-m marker用于标记测试并分组

--strict 遇到mark拼写错误会检查,与mark配合使用

  1. 注册标记防范拼写错误

自定义标记可以简化测试工作,但是标记容易拼写错误,默认情况下不会引起错误,pytest以为这是另外一个标记,为了避免拼写错误,可以在pytest.ini文件里进行注册

markers = data_file:a test_data get_and_format marker

通过命令查看:pytest --help(或者pytest --marks)没有注册的标记不会出现在markers列表里面,如果使用--strict选项,遇到拼写错误的标记或者未注册的标记会报错

如果自己增加一个测试函数的标记呢?

@pytest.mark.smoke

pytest -m 'smoke' test.py

  1. 执行pytest的最低版本号设置

minversion = 6.0

minversion选项可以指定运行测试用例的pytest的最低版本

  1. 指定pytest忽略某些目录

norecursedirs = .*data config utils

可以使用norecursedirs缩小pytest的搜索范围

指定访问目录

testpath = testsdir

  1. 配置日志

通过将log_cli配置选项设置为true,pytest将在直接将日志记录发送到控制台时输出日志记录。

您可以指定传递的级别,以将等于或更高级别的日志记录打印到控制台的日志记录级别--log-cli-level。此设置接受python文档中显示的日志记录级别名称,或者接受整数作为日志记录级别num。

此外,您还可以指定--log-cli-format和 --log-cli-date-format哪个镜和默认--log-format和 --log-date-format如果没有提供,但只被应用到控制台日志处理程序。

还可以在配置INI文件中设置所有CLI日志选项。选项名称为:

log_cli_level

log_cli_format

log_cli_date_format

如果您需要将整个测试套件的日志记录记录到一个文件中,则可以传递 --log-file=/path/to/log/file。该日志文件以写模式打开,这意味着它将在每个运行测试会话中被覆盖。

您还可以通过传递日志文件的日志记录级别 --log-file-level。此设置接受python文档中所见的日志记录级别名称(即大写的名称),或者接受整数作为日志记录级别num。

此外,您还可以指定--log-file-format和 --log-file-date-format,它们等于--log-format和 --log-date-format但应用于日志文件日志处理程序。

还可以在配置INI文件中设置所有日志文件选项。选项名称为:

log_file

log_file_level

log_file_format

log_file_date_format

您可以调用set_log_path()以动态自定义log_file路径。此功能被认为是实验性的。

  1. [pytest]  
  2. testpaths = TestCases  
  3. log_format = %(asctime)s %(levelname)s %(message)s  
  4. log_level = INFO  
  5. log_file_level = debug  
  6. log_file_date_format = %Y-%m-%d %H:%M:%S  
  7. log_file_format = %(asctime)s %(levelname)s %(message)s  
  8. ;log_file = ../../Logs/log.log  
  9. log_cli = True  
  10. log_cli_level = INFO  
  11. log_cli_format = %(asctime)s [%(levelname)1s] %(message)s (%(filename)s:%(lineno)s)  
  12. log_cli_date_format=%Y-%m-%d %H:%M:%S  

8、配置例子

#配置pytest命令行运行参数

[pytest]

addopts = -s ... # 空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数配置测试搜索的路径

testpaths = ./scripts # 当前目录下的scripts文件夹 -可自定义

#配置测试搜索的文件名称

python_files = test*.py

#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件 -可自定义

配置测试搜索的测试类名

python_classes = Test_*

#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类 -可自定义

配置测试搜索的测试函数名

python_functions = test_*

#当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类内,以test_开头的方法 -可自定义

pytest之数据驱动

  1. Fixture
  1. import pytest  
  2. @pytest.fixture(params=[1, 2, 3])  
  3. def need_data(request): # 传入参数request 系统封装参数  
  4.     return request.param # 取列表中单个值,默认的取值方式  
  5. class Test_ABC:  
  6.    
  7.     def test_a(self,need_data):  
  8.         print("------->test_a")  
  9.         assert need_data != 3 # 断言need_data不等于3  
  10.    
  11. if __name__ == '__main__':  
  12.     pytest.main("-s  test_abc.py")  
  1. parametrize装饰器

@pytest.mark.parametrize(argnames,argvalues)装饰器可以达到批量传送参数的目的,argvalues里面传递的是元组或者列表里面嵌套元组的方式

pytest插件与hook函数

  1. 简介

pytest可以通过添加插件可以扩展功能,pytest的代码结构适合定制和扩展插件,可以借助hook函数来实现。把fixture函数或者hook函数添加到conftest文件里,这种方式,就已经创建了一个本地的conftest插件!!!

  1. pytest plugin加载的几种方式

1、内置plugins:从代码内部的_pytest目录加载;

2、外部插件(第三方插件):通过setuptools entry points机制发现的第三方插件模块;

推荐使用的第三方的pytest插件:https://docs.pytest.org/en/latest/plugins.html

3、conftest.py形式的本地插件:测试目录下的自动模块发现机制

通过pytest --trace-config命令可以查看当前pytest中所有的plugin

在pytest中,所谓的plugin其实就是能被pytest发现的一些带有pytest hook方法的文件或者对象

  1. 什么是hook方法(钩子函数)

使用的框架提供公用的规则,其他开发者使用这个规则编写的文件或者代码可以被框架识别,框架进行初始化时,会收集满足这个规则的所有代码(文件),然后将这些代码加入到框架中来,在执行时,一并进行初始化。所有这一规则下可以被框架收集到的方法就是hook方法。

  1. 编写自己的插件

插件可以改变pytest行为,可用的hook函数很多,详细的定义:

http://doc.pytest.org/en/latest/_modules/_pytest/hookspec.html

1、pytest_addoption,基本每个pytest plugin都会有这个hook方法,它的作用是为pytest命令添加自定义的参数

parser:用户命令行参数与ini文件值的解析器

def pytest_addoption(parser):

parser.addoption("--env",##注册一个命令行选项

default="test",#默认值为test

dest="env",

help="set test run env")#说明

pytest_addoption:Hook function,这里创建了一个argparser的group,通过addoption方法添加option,使得显示help信息时相关option显示在一个group下面,更加友好,使用pytest --help可以查看

  1. def pytest_addoption(parser):  
  2.     group = parser.getgroup("chinese auto test")  
  3.     group.addoption("--env", default="ggg", dest="env", help="test env")  
  4.     group.addoption("--env2", default="ggg", dest="env", help="test env")  
  5.   
  6. @pytest.fixture(scope="session")  
  7. def cmdopt(request):  
  8.     print("获取不同环境变量的配置")  
  9.     return request.config.getoption("--env")  

2、pytest_collection_modifyitems,是一个完成所有测试项的收集后,pytest调用的钩子

def pytest_collection_modifyitems(items):

pass

测试用例收集完成后,将收集到的item的name和nodeid的中文显示在控制台上,所有的测试用例收集完毕后调用,可以再次过滤或者对它们重新排序

items(收集的测试项目列表)

  1. def pytest_collection_modifyitems(items):  
  2.     print("test hook 函数")  
  3.     for item in items:  
  4.         item.name = item.name.encode("utf-8").decode("unicode_escape")  
  5.         item._nodeid = item._nodeid.encode("utf-8").decode("unicode_escape")  

Pytest高级用法

1、跳过测试函数

根据特定的条件,不执行标识的测试函数.

方法:

skipif(condition, reason=None)

参数:

condition:跳过的条件,必传参数

reason:标注原因,必传参数

使用方法:

@pytest.mark.skipif(condition, reason="xxx")

  1. import pytest  
  2. class Test_ABC:  
  3.     def setup_class(self):  
  4.         print("------->setup_class")  
  5.     def teardown_class(self):  
  6.         print("------->teardown_class")  
  7.     def test_a(self):  
  8.         print("------->test_a")  
  9.         assert 1  
  10.     @pytest.mark.skipif(condition=2>1,reason = "跳过该函数") # 跳过测试函数test_b  
  11.     def test_b(self):  
  12.         print("------->test_b")  
  13.             assert 0  

2、标记为预期失败的函数

标记测试函数为失败函数

方法:

xfail(condition=None, reason=None, raises=None, run=True, strict=False)

常用参数:

condition:预期失败的条件,必传参数

reason:失败的原因,必传参数

使用方法:

@pytest.mark.xfail(condition, reason="xx")

  1. import pytest  
  2. class Test_ABC:  
  3.     def setup_class(self):  
  4.         print("------->setup_class")  
  5.     def teardown_class(self):  
  6.         print("------->teardown_class")  
  7.     def test_a(self):  
  8.         print("------->test_a")  
  9.         assert 1  
  10.     @pytest.mark.xfail(2 > 1, reason="标注为预期失败") # 标记为预期失败函数test_b  
  11.        def test_b(self):  
  12.            print("------->test_b")  
  13.           assert 0  
  1. 函数参数化

方便测试函数对测试属于的获取。

方法:

parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)

常用参数:

argnames:参数名

argvalues:参数对应值,类型必须为list

当参数为一个时格式:[value]

当参数个数大于一个时,格式为:[(param_value1,param_value2.....),(param_value1,param_value2.....)]

使用方法:

@pytest.mark.parametrize(argnames,argvalues)

️ 参数值为N个,测试方法就会运行N次

在函数参数化中还可以传递函数,进行参数化

  1. import pytest  
  2. def return_test_data():  
  3.     return [(1,2),(0,3)]  
  4. class Test_ABC:  
  5.     def setup_class(self):  
  6.         print("------->setup_class")  
  7.     def teardown_class(self):  
  8.             print("------->teardown_class")  
  9. @pytest.mark.parametrize("a,b",return_test_data()) # 使用函数返回值的形式传入参数值  
  10. def test_a(self,a,b):  
  11.     print("test data:a=%d,b=%d"%(a,b))  
  12.     assert a+b == 3  

4、修改python traceback输出

pytest --showlocals # show local variables in tracebacks

pytest -l # show local variables (shortcut)

pytest --tb=auto # (default) 'long' tracebacks for the first and last

# entry, but 'short' style for the other entries

pytest --tb=long # exhaustive, informative traceback formatting

pytest --tb=short # shorter traceback format

pytest --tb=line # only one line per failure

pytest --tb=native # Python standard library formatting

pytest --tb=no # no traceback at all

python --full-trace 参数会打印更多的错误输出信息,比参数 --tb=long 还多,即使是 Ctrl+C 触发的错误,也会打印出来

5、获取用例执行的性能数据

获取最慢的10个用例的执行耗时

pytest --durations=10

Pytest-xdist进程级并发插件讲解

参考夜行者自动化测试群(群号:816489363)文件:pytest-xdist进程级并发参数化说明--成都-阿木木

Pytest常用插件介绍

  1. pytest-assume

多重校验插件,可以执行完所有的断言,常规assert断言执行失败后下面的断言便会停止,不在执行,assume插件可以执行完所有断言

安装命令:pip install pytest-assume

使用方式:

  1. def test_add_case(self):  
  2.     pytest.assume(add(1,2)==3)  
  3.     pytest.assume(add(1,4)==3)  
  4.     pytest.assume(add(2,2)==4)  
  1. pytest-ording

Pytest用例默认执行顺序是和collect的顺序一致,用例收集是按照测试目录开始,由上到下,在测试模块中,测试用例收集也是从上到下,需要调整测试函数的执行顺序可以通过pytest_collection_modifyitems这个hook函数(钩子)进行插件编写。在pytest的第三方插件中,已经有人实现了这个功能,下面介绍pytest-ordering这个插件。

安装命令:pip install pytest-ordering

  1. @pytest.mark.run(order=2)  
  2. def test_order1():  
  3.     print ("first test")  
  4.     assert True  
  5. @pytest.mark.run(order=1)  
  6. def test_order2():  
  7.     print ("second test")  
  8.     assert True  
  1. pytest-rerunfailures

失败重跑插件,使用比较简单,在脚本运行过程中,可能某些原因导致用例执行失败,可能是网络加载等,可以使用该插件,对于失败的用例进行重跑,提高报告的准确性。

安装命令:pip install pytest-rerunfailures

命令行指定:

Pytest -s test_xxx.py --reruns 5 #表示失败用例运行五次

Pytest -s test_xxx.py --reruns-delay 2 #表示失败用例等待2S后在执行

在装饰器中指定:

  1. @pytest.mark.flaky(reruns=6, reruns_delay=2)  
  2.     def test_example(self):  
  3.         print(3)  
  4.         assert random.choice([True, False])  
  1. pytest-sugar

显示进度条,控制台显示比较好看

显示效果如下:

安装命令:pip install pytest-sugar

(群:自动化测试-夜行者:816489363)

原文地址:https://www.cnblogs.com/chineseluo/p/13703921.html