pytest简介
pytest命令行参数
setup和teardown调用顺序
pytest测试用例的执行顺序控制:pytest-ordering插件

pytes概述


pytest 与 Python 自带的 Unittest 测试框架类似,但 pytest 使用起来更灵活和高效,并且兼容 unittest 框架。pytest 有以下实用特性:

  • pytest 支持简单的单元测试和复杂的功能测试
  • 支持参数化,数据驱动,避免冗余代码
  • pytest 可自动重试失败的测试用例,分级测试用例快速迭代
  • 可以结合 Requests 实现接口测试
  • 结合 Selenium、Appium 实现自动化功能测试
  • 使用 pytest 结合 Allure 集成到 Jenkins 中可以实现持续集成,生成测试报告
  • pytest 有丰富的第三方插件:pytest插件

官方文档


pytest 官方文档

pytest安装和基本使用


pytest安装

pip install -U pytest

pytest命令行

在包含test_*.py文件所在目录执行pytest命令,pytest会找当前目录以及递查找子目录下所有的 test_*.py (或*_test.py) 的文件,把其当作测试文件。在这些文件里,pytest会收集符合编写规范的函数,类以及方法,当作测试用例并且执行

命令行参数:

  1. 无参数:执行pytest或者py.test,读取路径下所有符合规则的文件,类,方法,函数全部执行
  2. 运行模式:pytest 提供了多种运行模式,让开发和调试更得心应手。 在编写测试用例的时候,经常会单独调试某个类,或者某个方法。也可以使用 Pycharm 里面自带的调试方式,点击用例方法名前面的绿色按钮,也可以使用命令行的方式单独运行某个用例
 pytest 文件名.py   # 单独执行某个Python文件
 pytest 文件名.py::类名   # 单独执行某个文件中的类 
 pytest 文件名.py::类名::方法名   # 单独执行类中的某个方法 |
  1. 其他
参数 说明 使用方法 应用场景
-v 打印详细运行日志信息 pytest -v 一般在调试的时候加上这个参数,终端会打印出每条用例的详细日志信息,方便定位问题。
-s 带控制台输出结果 pytest -s 当你的代码里面有 print 输出语句,如果想在运行结果中打印 print 输出的代码,在运行的时候可以添加 -s 参数,一般在调试的时候使用
-k 跳过运行某个或者某些用例 pytest -k '类名'
pytest -k '方法名'
pytest -k '类名 and not 方法名' # 运行类里所有的方法,不包含某个方法
在测试场景中,开发人员有一部分功能代码还没实现,测试人员已经将测试用例设计出来,或者测试人员发现了某功能上的 bug 需要开发人员修复之后再测试这部分有缺陷的测试用例,可以将这部分测试用例在运行的时候暂时跳过,等功能实现或者 bug 解决之后再加入运行。
-x 遇到用例失败立即停止运行 pytest -x 在回归测试过程中,假如一共有10条基础用例,当开发人员打完包提交测试的时候,需要先运行这10条基础用例,全部通过才能提交给测试人员正式测试。如果有一条用例失败,都将这个版本打回给开发人员。这时就可以添加 -x 参数,一旦发现有失败的用例即中止运行。
--maxfail 用例失败个数达到阀值停止运行 pytest --maxfail=[num] 在回归测试过程中,假如一共有10条基础用例,当开发人员打完包提交测试的时候,需要先运行这10条基础用例,全部通过才能提交给测试人员正式测试。如果运行过程中有 [num] 条用例失败,即中止运行,后面测试用例都放弃执行,直接退出。这时可以使用 --maxfail 参数。
-m 运行有 @pytest.mark.[标记名] 这个标记的测试用例 pytest -m [标记名] 在自动化测试过程中可以将测试用例添加标签进行分类,比如登录功能、搜索功能、购物车功能、订单结算功能等,在运行的时候可以只运行某个功能的所有的测试用例,比如这个版本只想验证登录功能,那就在所有登录功能的测试用例方法上面加上装饰符 @pytest.mark.login ,运行的时候使用命令添加一个 -m 参数,例如执行 pytest -m login 命令就可以只执行登录功能这部分的测试用例。

在pycharm中基本使用

打开 Pycharm -> Preferences-> Tools -> Python Integrated Tools -> Testing: pytest
如下例子test_add.py文件:

def add(x, y):
    return x + y

def test_add():
    assert add(1, 2) == 3
    assert add(100, 1) == 101

class TestCase:
    def test_in(self):
        assert 'a' in 'baidu'

    def test_hasattr(self):
        a = '1000'
        assert isinstance(a, (int,))

用例编写规范:

  1. 测试文件以 test_ 开头(常用),以 _test 结尾也可以
  2. 测试以 Test 开头,并且不能带有 init 方法
  3. 测试函数以 test_ 开头
  4. 断言使用基本的 assert 即可

pytest 框架结构和执行顺序


pytest框架结构

与 unittest 类似,执行用例前后会执行 setup,teardown 来增加用例的前置和后置条件。
pytest 框架中使用 setup,teardown 更灵活,按照用例运行级别可以分为以下几类:

  • 模块级(setup_module/teardown_module)在模块始末调用
  • 函数级(setup_function/teardown_function)在函数始末调用(在类外部)
  • 类级(setup_class/teardown_class)在类始末调用(在类内)
  • 方法级(setup_method/teardown_methond)在方法始末调用(在类内)
  • 方法级(setup/teardown)在方法始末调用(在类内)

setup和teardown调用顺序

setup_module > setup_class >setup_method > setup > teardown > teardown_method > teardown_class > teardown_module

参考如下例子验证调用顺序:

def setup_module():
    print("\n【start】:模块级setup_module,只执行一次,当有多个测试类的时候使用")

def teardown_module():
    print("\n【end】teardown_module,只执行一次,当有多个测试类的时候使用")

class TestPytest1:
    @classmethod
    def setup_class(cls):
        print("\n【1】:类1中的类级别setup_class1,只执行一次")

    @classmethod
    def teardown_class(cls):
        print("\n【5】类1中的类级别teardown_class1,只执行一次")

    def setup_method(self):
        print("\n【2-1】【2-2】:类1中的方法级setup_method1,每个测试方法都执行一次")

    def teardown_method(self):
        print("【4-1】【4-2】:类1中的方法级teardown_method1,每个测试方法都执行一次")

    def test_one(self):
        print("【3-1】类1中的测试用例1:test_one_in_class1,测试用例1")

    def test_two(self):
        print("【3-2】类1中的测试用例2:test_two_in_class1,测试用例2")

class TestPytest2:
    @classmethod
    def setup_class(cls):
        print("\n【a】类2中的类级别setup_class2,只执行一次")

    @classmethod
    def teardown_class(cls):
        print("\n【e】类2中的类级teardown_class2,只执行一次")

    def setup_method(self):
        print("\n【b-1】【b-2】类2中的方法级setup_method2,每个测试方法都执行一次")

    def teardown_method(self):
        print("【d-1】【d-2】类2中的方法级teardown_method2,每个测试方法都执行一次")

    def test_one(self):
        print("【c-1】类2中的测试用例1:test_one,测试用例1")

    def test_two(self):
        print("【c-2】类2中的测试用例2:test_two,测试用例2")

执行结果如下:

============================= test session starts ==============================
platform darwin -- Python 3.8.5, pytest-6.1.0, py-1.9.0, pluggy-0.13.1 -- /Users/tina/venv/test/bin/python3.8
cachedir: .pytest_cache
rootdir: /Users/tina/PycharmProjects/pytest_test
collecting ... collected 4 items

test_order.py::TestPytest1::test_one 
【start】:模块级setup_module,只执行一次,当有多个测试类的时候使用
【1】:类1中的类级别setup_class1,只执行一次
【2-1】【2-2】:类1中的方法级setup_method1,每个测试方法都执行一次
PASSED                              [ 25%]【3-1】类1中的测试用例1:test_one_in_class1,测试用例1
【4-1】【4-2】:类1中的方法级teardown_method1,每个测试方法都执行一次
test_order.py::TestPytest1::test_two 
【2-1】【2-2】:类1中的方法级setup_method1,每个测试方法都执行一次
PASSED                              [ 50%]【3-2】类1中的测试用例2:test_two_in_class1,测试用例2
【4-1】【4-2】:类1中的方法级teardown_method1,每个测试方法都执行一次
【5】类1中的类级别teardown_class1,只执行一次

test_order.py::TestPytest2::test_one 
【a】类2中的类级别setup_class2,只执行一次
【b-1】【b-2】类2中的方法级setup_method2,每个测试方法都执行一次
PASSED                              [ 75%]【c-1】类2中的测试用例1:test_one,测试用例1
【d-1】【d-2】类2中的方法级teardown_method2,每个测试方法都执行一次
test_order.py::TestPytest2::test_two 
【b-1】【b-2】类2中的方法级setup_method2,每个测试方法都执行一次
PASSED                              [100%]【c-2】类2中的测试用例2:test_two,测试用例2
【d-1】【d-2】类2中的方法级teardown_method2,每个测试方法都执行一次
【e】类2中的类级teardown_class2,只执行一次
【end】teardown_module,只执行一次,当有多个测试类的时候使用
============================== 4 passed in 0.02s ===============================
Process finished with exit code 0

测试用例的执行顺序控制

pytest 加载所有的测试用例是乱序的。
在运行测试用例时,如有些场景需要先登陆才能执行后续流程,因此需要指定测试用例顺序,这里可以通过pytest-ordering插件来完成。只需要在测试用例的方法前面加上装饰器 @pytest.mark.run(order=[num]) 设置order的对应的num值,它就可以按照 num 的大小顺序来执行。

  1. 安装插件
pip install pytest-ordering
  1. 验证示例
import pytest
class TestPytest(object):
    @pytest.mark.run(order=-1)
    def test_two(self):
        print("【-1】test_two测试用例")

    @pytest.mark.run(order=3)
    def test_one(self):
        print("【3】test_one测试用例")

    @pytest.mark.run(order=1)
    def test_three(self):
        print("【1】test_three测试用例")

    @pytest.mark.run(order=-4)
    def test_four(self):
        print("【-4】test_four测试用例")

    @pytest.mark.run(order=0)
    def test_five(self):
        print("【0】test_five测试用例")

执行结果:

test_pytest_order.py::TestPytest::test_five PASSED                       [ 20%]【0】test_five测试用例

test_pytest_order.py::TestPytest::test_three PASSED                      [ 40%]【1】test_three测试用例

test_pytest_order.py::TestPytest::test_one PASSED                        [ 60%]【3】test_one测试用例

test_pytest_order.py::TestPytest::test_four PASSED                       [ 80%]【-4】test_four测试用例

test_pytest_order.py::TestPytest::test_two PASSED                        [100%]【-1】test_two测试用例

执行时以 order 的顺序执行:order=0 -> 1 -> 3 -> -4 -> -1 (正数小到大 -> 负数小到大)

参考