前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >pytest框架介绍(二)

pytest框架介绍(二)

作者头像
赵云龙龙
发布2021-04-23 11:14:33
8200
发布2021-04-23 11:14:33
举报
文章被收录于专栏:python爱好部落python爱好部落

前面简单介绍了如何使用pytest, 感觉介绍得太泛泛了。个人感觉,pytest的精髓在fixture. 学pytest就不得不说fixture,fixture是pytest的精髓所在,就像unittest中的setup和teardown一样,如果不学fixture那么使用pytest和使用unittest是没什么区别的(个人理解)。

fixture用途

1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现

2.测试用例的前置条件可以使用fixture实现

3.支持经典的xunit fixture ,像unittest使用的setup和teardown

4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题。

下面,我们来看看它的功能: 有时候仅仅使用一组数据是无法充分测试函数功能的,参数化测试允许传递多组数据。

代码语言:javascript
复制
import pytest
@pytest.mark.parametrize("task",
                         [Task("sleep", done=True),
                         Task("wake", "brian"),
                         Task("breathe", "BRIAN", True),
                         Task("exercise", "BrIaN", "False")])
def test_add_2(task):
    """Demonstrate paramertrize with one parameter"""
    task_id = task.add(task)
    t_from_db = tasks.get(task_id)
    assert equivalent(t_from_db, task)
  @pytest.mark.parametrize() 的第一个参数是用逗号分隔的字符串列表;第二个参数是一个值列表。

  pytest会轮流对每个task做测试,并分别报告每一个测试用例的结果。

  以下是 多组键值对情况

import pytest

@pytest.mark.parametrize("str",
                         ["abc","def","twq","tre"])
def test_add_2(str):
    """Demonstrate paramertrize with one parameter"""
    str2 = "abc"
    assert str == str2
执行如下:

(venv) C:\Users\admin\Desktop\ch2>pytest test_add_variety.py -v
=============================== test session starts ===============================
platform win32 -- Python 3.6.8, pytest-6.0.1, py-1.9.0, pluggy-0.13.1 -- c:\users\admin\desktop\ch2\venv\scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\admin\Desktop\ch2
collected 4 items                                                                   

test_add_variety.py::test_add_2[abc] PASSED            [ 25%]
test_add_variety.py::test_add_2[def] FAILED            [ 50%]
test_add_variety.py::test_add_2[twq] FAILED            [ 75%]
test_add_variety.py::test_add_2[tre] FAILED            [100%]
通过conftest.py共享fixture

fixture 可以放在单独的测试文件里。此时只有这个测试文件能够使用相关的fixture。

如果希望多个测试文件共享 fixture,可以在某个公共目录下新建一个 conftest.py 文件,将 fixture 放在其中。(作用域根据所放的文件夹决定,最上层文件夹的话整个项目共用,子文件夹的话,子文件夹里面的测试共用。)

尽管 conftest.py 是Python 模块,但它不能被测试文件导入。import conftest 的用法是不允许出现的。conftest.py 被 pytest 视作一个本地插件库。可以把 tests/conftest.py 看成是一个供 tests 目录下所有测试使用的 fixture仓库。

fixture执行的逻辑

fixture 函数会在测试函数之前运行,但如果 fixture 函数包含 yield,那么系统会在 yield 处停止,转而运行测试函数,等测试函数执行完毕后再回到 fixture,继续执行 yield 之后的代码。

可以将 yield 之前的代码视为 配置(setup)过程,将yield 之后的代码视为清理(teardown)过程。

无论测试过程中发生了说明,yield之后的代码都会被执行

使用--setup-show回溯执行顺序

直接运行测试,则不会看到fisture的执行过程。

如果希望看到测试过程中执行的是什么,以及执行的先后顺序。pytest 提供的 --setup-show 选项可以实现这个功能。

pytest --setup-show test_add.py

传递数据

fixture 非常适合存放测试数据,并且它可以返回任何数据。

代码语言:javascript
复制
import pytest


@pytest.fixture()
def a_tuple():
    return (1, "foo", None, {"bar": 23})


def test_a_tuple(a_tuple):
    assert a_tuple[3]["bar"] == 32
  yeild 返回数据

import pytest


@pytest.fixture()
def a_tuple():
    print("1111")
    yield (1, "foo", None, {"bar": 23})
    print("2222")


def test_a_tuple(a_tuple):
    assert a_tuple[3]["bar"] == 32
  明显23不等于32,所以会失败。
使用多个fixture

fixture互相调用

代码语言:javascript
复制
import pytest

@pytest.fixture()
def a_tuple():
    return (1,2,3)

@pytest.fixture()
def two_tuple(a_tuple):
    if a_tuple[2] == 3:
        return (1, "foo", None, {"bar": 23})
    return False

def test_a_tuple(two_tuple):
    if two_tuple:
        assert two_tuple[3]["bar"] == 23

用例中传入多个fixture

import pytest

@pytest.fixture()
def a_tuple():
    return 1

@pytest.fixture()
def two_tuple():
    return 2

def test_a_tuple(a_tuple, two_tuple):
    assert a_tuple == 1
    assert two_tuple == 2

fixture的范围 fixture 包含一个叫 scope(作用范围)的可选参数,用于控制 fixture 执行配置和销毁逻辑的频率。@pytest.fixture() 的 scope 参数有四个待选值:

function class module session(默认值) 以下是对各个 scope 的概述

scope=“function”

函数级别的 fixture 每个测试函数只需要运行一次。配置代码在测试用例运行之前运行,销毁代码在测试用例运行之后运行。是默认值

scope=“class”

类级别的fixture 每个测试类只需要运行一次,无论测试类里面有多少类方法都可以共享这个fixture

scope="module"

模块级别的fixture每个模块只需要运行一次,无论模块里有多少个测试函数、类方法或其他fixture 都可以共享这个fixture

scope=“session”

会话级别的 fixture 每次会话只需要运行一次。一次 pytest 会话中所有测试函数、方法都可以共享这个 fixture。

fixture的auto属性

之前用到的 fixture 都是根据测试本身来命名的(或者针对示例的测试类使用 usefixtures)。我们可以通过制定 autouse=True选项,使作用域内的测试函数都自动运行 fixture

fixture_params参数

@pytest.fixture(params=None) ,参数params的值是一个list。每个值都会自动遍历一次

代码语言:javascript
复制
import pytest


@pytest.fixture(params=['admin', 'zhangsan', 'lisi'])
def username(request):
    return request.param


@pytest.fixture(params=['1234567', '12345678', '123456789'])
def password(request):
    return request.param


def test_check_regist(username, password):
    print(username, '==>', password)


if __name__ == '__main__':
    pytest.main(['-s', 'test_2_scope.py'])

打印输出结果

test_2_scope.py admin ==> 1234567
.admin ==> 12345678
.admin ==> 123456789
.zhangsan ==> 1234567
.zhangsan ==> 12345678
.zhangsan ==> 123456789
.lisi ==> 1234567
.lisi ==> 12345678
.lisi ==> 123456789

pytest.mark 除了使用fixture装饰器,我们还可以使用mark标记,用法类似,都是使用装饰器

装饰器 说明 pytest.mark.xfail() 标记为预期失败 pytest.mark.skip() 无条件跳过执行 pytest.mark.skipif() 有条件跳过执行 pytest.mark.parametrize() 参数化Fixture方法 pytest.mark.usefixtures() 使用类、模块或项目中的Fixture方法 xfail 失败 如果你在测试某个功能的时候,就断定它是失败的(比如说开发未完成),那我们可以使用xfail进行标记(输出标记符号为x)

代码语言:javascript
复制
xfail(condition=True, reason=None, raises=None, run=True, strict=False)

import pytest

class Test_ABC:
    def test_a(self):
        print("test_a")
    # 如果条件为 False,那么这个标记无意义
    @pytest.mark.xfail(condition=False, reason="预期失败")
    def test_b(self):
        print("test_b")
        assert 0

    @pytest.mark.xfail(condition=True, reason="预期失败")
    def test_c(self):
        print("test_c")
        assert 0

if __name__ == '__main__':
    pytest.main(['-s', 'test_3_mark.py'])

打印输出:

代码语言:javascript
复制
test_3_mark.py test_a
.test_b
Ftest_c
x

skip、skipif 跳过 如果是因为测试流程需要,测试的时候不想执行某个测试用例,我们可以通过skip标记来跳过(输出标记符号为s)

代码语言:javascript
复制
skip(reason=None)
skipif(condition, reason=None)

import pytest

class Test_ABC:
    def test_a(self):
        print("test_a")

    # 开启跳过标记
    @pytest.mark.skip(reason="无条件跳过不执行,就是任性顽皮")
    def test_b(self):
        print("test_b")

    @pytest.mark.skipif(condition=1, reason="有条件跳过不执行,依旧任性顽皮")
    def test_c(self):
        print("test_c")

if __name__ == '__main__':
    pytest.main(['-s', 'test_3_mark.py'])

打印输出:

代码语言:javascript
复制
test_3_mark.py test_a
.ss

parametrize 参数化 如果需要给测试用例传输参数的话可以使用parametrize标记,注意key和value的位置 parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)

代码语言:javascript
复制
import pytest

class Test_ABC:
    def test_a(self):
        print("test_a")
        
    @pytest.mark.parametrize("a", [3, 6])
    def test_b(self, a):
        print("test_b data:a=%s" % a)

    @pytest.mark.parametrize(["a", "b"], [(1, 2), (3, 4)])
    def test_c(self, a, b):
        print("test_c a: %s; b: %s" % (a, b))

if __name__ == '__main__':
    pytest.main(['-s', 'test_3_mark.py'])

打印输出:

代码语言:javascript
复制
test_3_mark.py test_a
.test_b data:a=3
.test_b data:a=6
.test_c a: 1; b: 2
.test_c a: 3; b: 4
.

配置文件 pytest.ini pytest是可以使用配置文件执行的,该配置文件名固定是pytest.ini,把它放到运行路径下即可

举个栗子

代码语言:javascript
复制
[pytest]
addopts = -s test_12.py test_13.py 
testpaths = ./scripts
python_files = test_*.py 
python_classes = Test_*
python_functions = test_*

;在ini文件中注释语句是以分号开始的, 所有的注释语句不管多长都是独占一行直到结束的

addopts是指命令行参数 testpathsv是测试搜索的路径,一般是当前文件夹 python_files是测试搜索的文件,即以test开头的py文件 python_classes与python_functions意思同上,分别作用类和方法

执行方式 如果在pycharm直接右键运行,它可能会执行两次(配置文件也会执行一次),所以建议使用命令行执行,直接在配置文件执行pytest即可,它会自动找pytest.ini文件执行测试

常用插件 pytest的强大原因之一,就是它支持插件,有插件的支持让它变得强大和好用

pytest-html 测试报告 如果你想要在测试之后生成测试报告,可以使用pytest-html插件

代码语言:javascript
复制
pip install pytest-html

使用方法很简单,只需要在命令行执行的时候加上--html=./report.html参数,或直接写在配置文件里

代码语言:javascript
复制
pytest.ini

addopts = -s test_14.py --html=./report.html

pytest-ordering 执行顺序 我们知道测试用例的执行顺序是按照用例名按ASCII码顺序排序的,如果想要实现自定义顺序,我们可以使用

代码语言:javascript
复制
pip install pytest-ordering
代码语言:javascript
复制
import pytest

class Test_ABC:
    def setup_class(self): print("setup_class")
    def teardown_class(self): print("teardown_class")

    @pytest.mark.run(order=2)  # order=2 后运行
    def test_a(self):
        print("test_a")
        assert 1

    @pytest.mark.run(order=1)  # order=1 先运行
    def test_b(self):
        print("test_b")
        assert 1

if __name__ == '__main__':
    pytest.main(["-s", "test_4_plugins.py"])

打印输出:

代码语言:javascript
复制
test_4_plugins.py setup_class
test_b
.test_a
.teardown_class

pytest-rerunfailures 失败重试 如果在执行某个测试用例遇到的失败想要重试,我们可以使用pytest-rerunfailures插件指定重试次数

代码语言:javascript
复制
pip install pytest-rerunfailures

该插件的使用很简单,就是在命令行加上--reruns指定重试次数,也可以在配置文件里写

代码语言:javascript
复制
addopts = -s test_16.py --html=./report.html --reruns 2

补充:如果不想要某些插件生效,可以使用-p no:命令

代码语言:javascript
复制
[pytest]
addopts = -s test_16.py -p no:ordering -p no:html -p no:rerunfailures
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 python粉丝团 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • fixture用途
  • 通过conftest.py共享fixture
  • fixture执行的逻辑
  • 使用--setup-show回溯执行顺序
  • 传递数据
  • 使用多个fixture
  • fixture的auto属性
  • fixture_params参数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档