pytest框架入门

一、pytest基本使用

1.1 安装

使用pip命令安装

pip install pytest

1.2 运行方式

方式一:命令行运行(推荐使用)

  • 示例代码:
# jbsy.py
class TestSample(object):
    """测试类"""
    def test_f1(self):
        # 测试方法,必须以“test”开头
        print("---- in test_f1 ----")
        assert 1

    def test_f2(self):
        # 测试方法,必须以“test”开头
        print("---- in test_f2 ----")
        assert 1
  • 在terminal中输入命令运行文件:pytest -s jbsy.py
  • 运行结果:

方式二:mian方法运行

  • 示例代码:
# jbsy.py
import pytest


class TestSample(object):
    """测试类"""
    def test_f1(self):
        # 测试方法
        print("---- in test_f1 ----")
        assert 1

    def test_f2(self):
        # 测试方法
        print("---- in test_f2 ----")
        assert 1


if __name__ == "__main__":
    pytest.main(["-s", "jbsy.py"])  # 使用main方法,传入执行的参数以及需要执行的py文件
  • 直接运行py文件,查看结果:

1.3 setup和teardown

应用场景
在pytest运行自动化脚本前会执行setup方法,在执行完脚本后会运行teardown方法。例如在运行脚本前先连接到数据库,在执行完脚本后关闭数据库连接。

函数级别方法

运行每个测试方法的始末运行一次setup和teardown。

  • 示例代码:
# jbsy.py
import pytest


class TestSample(object):
    """测试类"""

    def setup(self):
        print("---setup---")

    def teardown(self):
        print("---teardown---")

    def test_f1(self):
        # 测试方法
        print("---- in test_f1 ----")
        assert 1

    def test_f2(self):
        # 测试方法
        print("---- in test_f2 ----")
        assert 1
  • 运行查看结果:pytest -s jbsy.py

类级别方法

在每个测试类的始末运行一次setup_class和teardown_class。

  • 示例代码:
# jbsy.py
import pytest


class TestSample(object):
    """测试类"""

    def setup_class(self):
        print("---setup_class---")

    def teardown_class(self):
        print("---teardown_class---")

    def test_f1(self):
        # 测试方法
        print("---- in test_f1 ----")
        assert 1

    def test_f2(self):
        # 测试方法
        print("---- in test_f2 ----")
        assert 1
  • 运行结果:

1.4 配置文件

使用场景

使用配置文件,可以快速的使用配置项来决定测试哪些脚本,更集中、灵活的管理。

使用方法

1、在项目根目录创建名为 pytest.ini 的文件
2、文件第一行内容为 [pytest]
3、命令行运行时会使用该文件中的相关配置

示例代码:

文件目录:

例如:指定运行scripts目录下,文件名以“test_g”开头、类名以“TestG”开头、方法命名以“test_a”开头的测试脚本

# test_goods.py
class TestGoods(object):
    def test_add(self):
        print("test_add")
        assert 1

    def test_add1(self):
        print("test_add1")
        assert 1

    def test_del(self):
        print("test_del")
        assert 1


class TestEmployee(object):
    def test_modify(self):
        print("test_modify")
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

# 指定运行某些模块
python_files = test_g*.py

# 指定运行的类
python_classes = TestG*

# 执行运行的方法
python_functions = test_a*

在terminal输入pytest,运行结果,只执行了满足条件的test_add和test_add1两个方法:

二、pytest常用插件

2.1 测试报告

应用场景

在测试脚本执行完成后,可以通过测试报告来体现执行情况。

安装

pip install pytest-html

使用

在 pytest.ini 文件的命令行参数配置中新增 --html=存储路径/report.html

[pytest]

# 添加命令行参数
addopts = -s --html=report/report.html

# 文件搜索路径
testpaths = ./scripts

测试脚本:

# test_login.py


class TestLogin(object):
    """测试类"""

    def test_f1(self):
        # 测试方法
        print("---- in test_f1 ----")
        assert 1

    def test_f2(self):
        # 测试方法
        print("---- in test_f2 ----")
        assert 1


在terminal运行 pytest

在report目录下生成了一个html文件:

在浏览器中打开:

2.2 控制函数执行顺序

应用场景

默认情况下pytest脚本的执行顺序是由上到下顺序执行,通过插件可以控制脚本的执行顺序,比如支付功能,需要先登录才能执行。

安装

pip install pytest-ordering

使用

1、给被测试函数加上装饰器 @pytest.mark.run(order=x)
2、根据order传入的参数决定执行顺序
3、order值全为正数或全为负数时,按照由小到大的顺序执行

例1: 不使用插件

# test_login.py


class TestLogin(object):
    """测试类"""

    def test_f2(self):
        # 测试方法
        print("---- in test_f2 ----")
        assert 1

    def test_f1(self):
        # 测试方法
        print("---- in test_f1 ----")
        assert 1

    def test_f3(self):
        # 测试方法
        print("---- in test_f3 ----")
        assert 1

运行 pytest -s test_login.py,按照脚本书写顺序,从上到下执行:

例2: 使用插件

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""

    @pytest.mark.run(order=200)
    def test_f2(self):
        # 测试方法
        print("---- in test_f2  200 ----")
        assert 1

    @pytest.mark.run(order=100)
    def test_f1(self):
        # 测试方法
        print("---- in test_f1  100 ----")
        assert 1

    @pytest.mark.run(order=300)
    def test_f3(self):
        # 测试方法
        print("---- in test_f3  300 ----")
        assert 1

在terminal执行 pytest -s test_login.py,按照数字由小到大依次执行:

例3: 各种情况的优先级顺序

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""

    @pytest.mark.run(order=200)
    def test_f2(self):
        # 测试方法
        print("---- 200 ----")
        assert 1

    @pytest.mark.run(order=100)
    def test_f1(self):
        # 测试方法
        print("---- 100 ----")
        assert 1

    @pytest.mark.run(order=101.5)
    def test_f3(self):
        # 测试方法
        print("---- 101.5 ----")
        assert 1

    @pytest.mark.run(order=0)
    def test_f4(self):
        # 测试方法
        print("---- 0 ----")
        assert 1

    @pytest.mark.run(order=-200)
    def test_f5(self):
        # 测试方法
        print("---- -200 ----")
        assert 1

    @pytest.mark.run(order=-100)
    def test_f6(self):
        # 测试方法
        print("---- -100 ----")
        assert 1

    @pytest.mark.run(order=-101.5)
    def test_f7(self):
        # 测试方法
        print("---- -101.5 ----")
        assert 1

    def test_f8(self):
        # 测试方法
        print("---- 不使用插件 ----")
        assert 1

运行 pytest -s test_login.py ,结果:

优先级:0 > 正数 > 不使用插件 > 负数

2.3 失败重试

应用场景

一般在运行脚本失败后久不会再次执行该脚本,使用失败重试插件,可以在特殊情况下(如可能存在网络问题导致脚本执行失败时)对失败的脚本进行多次执行

安装

pip install pytest-rerunfailures

使用

在 pytest.ini 文件的命令行参数配置中添加 --reruns n (n表示重新执行的次数)

例1: 当发生失败后,重试3次

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""

    def test_f2(self):
        # 测试方法
        print("---- f2 ----")
        assert 1

    def test_f1(self):
        # 测试方法
        print("---- f1 ----")
        assert 0

    def test_f3(self):
        # 测试方法
        print("---- f3 ----")
        assert 1


[pytest]

# 添加命令行参数
addopts = -s --html=report/report.html --reruns 3

# 文件搜索路径
testpaths = ./scripts

在terminal中执行 pytest,查看结果:


三、pytest高级用法

3.1 跳过测试函数

应用场景

在某些特定情况下,不需要执行的脚本,可以进行跳过。如有些功能是给更高版本的系统使用的,当在测试低版本的功能时,我们就可以跳过这些低版本不具备的功能。

使用

给需要执行跳过的方法添加装饰器 @pytest.mark.skipif(condition, reason)
condition 是是否跳过的条件,当condition=True时,跳过;当condition=False时,不跳过
reason 表示跳过的原因,传入字符串

例:当版本低于4.0时,不执行某个方法

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""
    VERSION = 3.0

    def test_f2(self):
        # 测试方法
        print("---- f2 ----")
        assert 1

    @pytest.mark.skipif(VERSION < 4.0, reason="123") # 当版本低于 4.0 时不执行此方法
    def test_f1(self):
        # 测试方法
        print("---- f1 ----")
        assert 1

    def test_f3(self):
        # 测试方法
        print("---- f3 ----")
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

执行 pytest ,查看运行结果:

3.2 预期失败

应用场景

应用于反向测试,如注册时,要求用户名为6-8个字符,实际输入的是10个字符。

使用

在预期失败的方法上加上装饰器 @pytest.mark.xfail(condition, reason)
condition=True 表示预期失败;condition=False 表示预期成功
reason 表示原因,传入字符串

例:

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""

    @pytest.mark.xfail(condition=False, reason="")
    def test_f1(self):
        # 预期成功,实际成功
        print("---- f1 ----")
        assert 1

    @pytest.mark.xfail(condition=False, reason="")
    def test_f2(self):
        # 预期成功,实际失败
        print("---- f2 ----")
        assert 0

    @pytest.mark.xfail(condition=True, reason="")
    def test_f3(self):
        # 预期失败,实际成功
        print("---- f3 ----")
        assert 1

    @pytest.mark.xfail(condition=True, reason="")
    def test_f4(self):
        # 预期失败,实际失败
        print("---- f4 ----")
        assert 0

[pytest]

# 添加命令行参数
addopts = -s --html=report/report.html

# 文件搜索路径
testpaths = ./scripts


执行 pytest, 查看结果:

测试报告:

3.3 数据参数化

应用场景

对于流程相同,只是数据不同的测试用例,可以使用参数化的方法,简化代码编写。

使用

给需要参数化的方法加装饰器 @pytest.mark.parametrize(key, value)

  • 传入单个参数:key 表示参数名,字符串格式;value 表示参数值,列表格式;
  • 传入多个参数:key 表示参数名,元组格式或字符串格式(参数名之间用逗号分割);value 表示参数值,列表格式,每组值用一个元组表示;

例1: 传入单个参数

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""

    @pytest.mark.parametrize("num", [17246538765, 17987654321])
    def test_f1(self, num):
        print("---- 输入手机号为 {} ----".format(num))
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

执行 pytest , 查看结果:

例2: 传入多个参数(方式一)

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""
    
    # 用元组分割参数名
    @pytest.mark.parametrize(("username", "pwd", "re_pwd"), [("zhangsan", "123", "123"),
                                                             ("wanger", "212", "212"),
                                                             ("lisi", "222", "222")])
    def test_f1(self, username, pwd, re_pwd):
        print("
---- 用户名: {}, 密码:{}, 确认密码:{}----".format(username, pwd, re_pwd))
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest,查看结果:

例3: 传入多个参数(方式二)

# test_login.py
import pytest


class TestLogin(object):
    """测试类"""

    # 用逗号分割参数名
    @pytest.mark.parametrize("username,pwd,re_pwd", [("zhangsan", "123", "123"),
                                                             ("wanger", "212", "212"),
                                                             ("lisi", "222", "222")])
    def test_f1(self, username, pwd, re_pwd):
        print("
---- 用户名: {}, 密码:{}, 确认密码:{}----".format(username, pwd, re_pwd))
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest,查看结果:

四、pytest-fixture

应用场景

fixture是一种自定义的工厂函数,用于完成预置处理或者可重复操作,如动态的获取数据等。如果某个测试方法引用了fixture函数,则它会在这个测试函数执行之前做一些相关的准备工作。
如调用新增人员接口,前提是要先登录获取到token,新增人员接口需要携带token才能进行新增操作,此时可以定义一个fixture,专门用于登录获取token的操作。

4.1 使用方式

4.1.1 通过参数引用(即 直接在方法中当作参数使用)
# test_employee.py
import pytest


class TestEmployee(object):
    """测试类"""

    # 定义fixture
    @pytest.fixture()
    def login(self):
        print("--- login ---")

    def test_add(self, login):
        print("--- test_add ---")
        assert 1

    def test_del(self, login):
        print("--- test_del ---")
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest, 查看结果:

4.1.2 通过函数引用(即 使用装饰器)
# test_employee.py
import pytest


class TestEmployee(object):
    """测试类"""

    # 定义fixture
    @pytest.fixture()
    def login(self):
        print("--- login ---")

    @pytest.mark.usefixtures("login")
    def test_add(self):
        print("--- test_add ---")
        assert 1

    @pytest.mark.usefixtures("login")
    def test_del(self):
        print("--- test_del ---")
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest, 查看结果:

使用fixture的好处是,只要引用就可以使用它,如果想不用的话就不引用,比setup灵活。

4.2 参数

4.2.1 默认运行

只需给装饰器添加autouse=True,即使不显示的应用,也可在脚本运行时,使每个测试用例运行之前都运行一次fixture

# test_employee.py
import pytest


class TestEmployee(object):
    """测试类"""

    # 定义fixture
    @pytest.fixture(autouse=True)
    def login(self):
        print("--- login ---")

    def test_add(self):
        print("--- test_add ---")
        assert 1

    def test_del(self):
        print("--- test_del ---")
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest ,查看结果:

此时类似于使用setup。当fixture与setup同时使用时,优先级:fixture > setup

# test_employee.py
import pytest


class TestEmployee(object):
    """测试类"""

    def setup(self):
        print("--- setup ---")

    # 定义fixture
    @pytest.fixture(autouse=True)
    def login(self):
        print("--- login ---")

    def test_add(self):
        print("--- test_add ---")
        assert 1

    def test_del(self):
        print("--- test_del ---")
        assert 1

4.2.2 作用域

默认作用域为函数级别

# test_employee.py
import pytest


# 定义fixture
@pytest.fixture(autouse=True, scope="function")  # 默认就是function级别
def login():
    print("--- login ---")


class TestEmployee(object):
    """测试类"""

    def test_add(self):
        print("--- test_add ---")
        assert 1

    def test_del(self):
        print("--- test_del ---")
        assert 1


class Test001(object):
    def test_f1(self):
        print("--- test_f1 ---")
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest, 查看结果:

类级别的fixture

# test_employee.py
import pytest


# 定义fixture
@pytest.fixture(autouse=True, scope="class")  # 定义为类级别
def login():
    print("--- login ---")


class TestEmployee(object):
    """测试类"""

    def test_add(self):
        print("--- test_add ---")
        assert 1

    def test_del(self):
        print("--- test_del ---")
        assert 1


class Test001(object):
    def test_f1(self):
        print("--- test_f1 ---")
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest, 查看结果:

setup_class 和 类级别的fixture的优先级:

# test_employee.py
import pytest


# 定义fixture
@pytest.fixture(autouse=True, scope="class")  # 定义为class级别
def login():
    print("--- login ---")


class TestEmployee(object):
    """测试类"""

    def setup_class(self):
        print("--- setup_class ---")

    def test_add(self):
        print("--- test_add ---")
        assert 1

    def test_del(self):
        print("--- test_del ---")
        assert 1

运行 pytest, 查看结果:

4.2.3 参数化

@pytest.fixture(params=None)
params为列表,列表中有多少个元素,脚本就会执行多少次。
如果想要获取params中的数据,需要在 fixture 中加 request 参数,这个参数名必须叫 request , 通过这个参数的 .param 属性获取值。

例:

# test_employee.py
import pytest


# 定义fixture
@pytest.fixture(autouse=True, params=[3, 11])
def add(request):
    # 将参数统一加 5
    print("--- 处理后的数值: ---", request.param + 5)


class TestEmployee(object):
    """测试类"""

    def test_add(self):
        print("--- test_add ---")
        assert 1

    def test_del(self):
        print("--- test_del ---")
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest, 查看结果:

4.3 返回值

使用参数的形式引用fixture,可以直接使用fixture的返回值。

例:

# test_employee.py
import pytest


# 定义fixture
@pytest.fixture(params=[3, 11])
def add(request):
    # 将参数统一加 5
    return request.param + 5


class TestEmployee(object):
    """测试类"""

    def test_add(self, add):  # 引用fixture
        print(add)  # 打印fixture返回的结果
        assert 1

[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest, 查看结果:

即 使用fixture 的名称就相当于是使用fixture的返回值。

当fixture和测试用例脚本同时使用参数时,脚本执行次数 = fixture的参数个数 * 测试脚本的参数个数

# test_employee.py
import pytest


# 定义fixture
@pytest.fixture(params=[1, 2])
def add(request):
    return request.param


class TestEmployee(object):
    """测试类"""

    @pytest.mark.parametrize("num", ["a", "b"])
    def test_add(self, add, num):  # 引用fixture
        print(add, num)  # 打印fixture返回的结果
        assert 1
[pytest]

# 添加命令行参数
addopts = -s

# 文件搜索路径
testpaths = ./scripts

运行 pytest,结果:

原文地址:https://www.cnblogs.com/yanlin-10/p/14461014.html