Python单测框架Pytest教程

简介: The pytest framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries. pytest介绍和教程。pytest 框架使编写小型可读测试变得容易,并且可以扩展以支持应用程序和库的复杂功能测试。

1. What: 单测框架

1.1 什么是单测

单元测试是自动化测试的一种形式--这仅仅意味着测试计划是由一个脚本执行的,而不是由人手动执行的。
它们作为软件测试的第一层,通常以函数的形式编写,验证软件程序中各种功能的行为。

1.2 为什么要写单元测试

以下列举了一些我为什么使用单元测试的好处:

  1. 减少bug:允许你对代码做出任何改变,因为你了解单元测试会在你的预期之中。单元测试可以有效地降低程序出现BUG的机率。
  2. 重构:帮助你更深入地理解代码--因为在写单元测试的时候,你需要明确程序所有的执行流程及对应的执行结果等等; 允许在任何时候代码重构,而不必担心破坏现有的代码。这使得我们编写程序更灵活,确保你的代码的健壮性,因为所有的测试都是通过了的。
  3. 文档记录:单元测试就是一种无价的文档,它是展示函数或类如何使用的最佳文档,这份文档是可编译、可运行的、并且它保持最新,永远与代码同步。
  4. 回归性:自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地地快速运行测试,而不是将代码部署到设备之后,然后再手动地覆盖各种执行路径,这样的行为效率低下,浪费时间。

2. Why: 为什么是Pytest

官网上的pytest:
helps you write better programs.
The pytest framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries.

测试是如此重要,以至于Python带有自己的内置测试框架,称为unittest。但是,在unittest中编写测试可能很复杂,因此近年来,pytest框架已成为标准。

  1. 非常容易開始,因為它的簡單和容易的語法。
  2. 可以並行執行測試。
  3. 可以執行特定測試或測試子集
  4. 自動檢測測試
  5. 跳過測試
  6. 開源
  7. 使用普通的断言语句而不是 unittest 的 assertSomething 方法(例如,assertEquals、assertTrue)
  8. 简化了测试状态的设置和拆卸

3. How:Pytest教程

3.1 安装

pip install -U pytest
pip install -U pytest-html
pip install -U pytest-rerunfailures

3.2 一个简单的例子

# content of test_sample.py
def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 5

执行测试:

$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_sample.py F                                                     [100%]

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================

执行测试的时候,我们只需要在测试文件testsample所在的目录下,运行pytest即可。
pytest会在当前的目录下,寻找以test开头的文件(即测试文件),找到测试文件之后,进入到测试文件中寻找test
开头的测试函数并执行。
通过上面的测试输出,我们可以看到该测试过程中,一个收集到了一个测试函数,测试结果是失败的(标记为F),并且在FAILURES部分输出了详细的错误信息,帮助我们分析测试原因。
我们可以看到”assert func(3) == 5”这条语句出错了,错误的原因是func(3)=4,然后我们断言func(3) 等于 5。

3.3 如何编写pytest测试

3.3.1 规则

3.3.1.1 编写规则

pytest执行单测需要按照下面的规则:

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

3.3.1.2 执行规则

执行测试:

pytest               # 在当前目录下执行所有测试用例
pytest test_mod.py   # 执行指定文件中的所有测试用例
pytest somepath      # 执行指定目录下的所有测试用例
pytest -k stringexpr # 执行匹配到字符串的测试用例
pytest test_mod.py::test_func # 执行指定文件中的指定测试用例

3.3.2 测试函数

# content of test_sample.py
def func(x):
    return x+1

def test_func():
    assert func(3) == 5

3.3.3 测试类

# content of test_class.py
class TestClass:
    def test_one(self):
        x = "this"
        assert 'h' in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, 'check')

执行测试:

$ pytest -q test_class.py
.F
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass object at 0x7fbf54cf5668>
def test_two(self):
x = "hello"
> assert hasattr(x, 'check')
E assert hasattr('hello', 'check')
test_class.py:8: AssertionError
1 failed, 1 passed in 0.01 seconds

pytest -q 可以不在输出版本信息。
该测试共执行了两个测试样例,一个失败一个成功。同样,我们也看到失败样例的详细信息,和执行过程中的中间结果。
pytest中用点号(.)表示一条用例被执行并通过,用F来标识一条用例被执行并失败。

如果想查看详情可以在pytest命令后面加上-v或者–verbose选项:

$ pytest -v test_class.py

collected 2 items

test_class.py::TestClass::test_one PASSED                                                                        [ 50%]
test_class.py::TestClass::test_two FAILED                                                                        [100%]

======================================================= FAILURES =======================================================
__________________________________________________ TestClass.test_two __________________________________________________

self = <test_class.TestClass object at 0x7fbcb0a15030>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, 'check')
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_class.py:10: AssertionError

3.3.4 使用raises在单测中捕获异常

有时候我们在做测试的时候,预期就是抛出一个异常,但如果在正常情况下,抛出异常后pytest或停止该条测试用例的继续执行,所以我们需要一个期望抛出异常的方法,pytest.raises就可以做到这一点,代码如下:

# content of test_sysexit.py
import pytest

def f():
    raise SystemExit(1)

def test_mytest():
    # 此处必须抛出SystemExit的异常,用例才会通过
    with pytest.raises(SystemExit):
        f()

def test_add_raises():
    with pytest.raises(AssertionError):
        # 此处必须抛出AssertionError的异常,用例才会通过
        assert 1 + 1 == 3

3.3.5 使用固件(fixture)

固件(Fixture)是一些函数,pytest 会在执行测试函数之前(或之后)加载运行它们。
固件是为了最大复用代码,以及在固定流程中注入代码使用。

3.3.5.1 使用

在函数声明之前加上“@pytest.fixture”。其他函数要来调用这个Fixture,只用把它当做一个输入的参数即可。

import pytest

@pytest.fixture()
def before():
    print('\nbefore each test')

def test_1(before):
    print('test_1()')

def test_2(before):
    print('test_2()')
    assert 0

除了在函数参数调用外,还可以显示指定调用,下面三种方式都是一样的功能:

import pytest

# 固件定义
@pytest.fixture()
def before():
    print('\nbefore each test')

# 函数前指定
@pytest.mark.usefixtures("before")
def test_1():
    print('test_1()')

@pytest.mark.usefixtures("before")
def test_2():
    print('test_2()')

class Test1:
    # 类的成员函数前指定
    @pytest.mark.usefixtures("before")
    def test_3(self):
        print('test_4()')

    @pytest.mark.usefixtures("before")
    def test_4(self):
        print('test_4()')

# 类的定以前指定
@pytest.mark.usefixtures("before")
class Test2:
    def test_5(self):
        print('test_5()')

    def test_6(self):
        print('test_6()')

除了上述定在一个指定函数外,还可以文件conftest.py集中管理固件(pytest会自动调用)。

# conftest.py
import pytest

@pytest.fixture()
def data():
    return 3

# test_fun.py
# 测试成功
def test_pass(data):
    assert func(data) == 4

Pytest 使用 yield 关键词将固件分为两部分,yield 之前的代码属于预处理,会在测试前执行;yield 之后的代码属于后处理,将在测试完成后执行。

# conftest.py
import pytest

@pytest.fixture()
def db():
    print('opened')
    yield
    print('closed')

3.3.5.2 固件的作用范围

fixture的scope参数用来作用范围,scope参数有四种,’function’,’module’,’class’,’session’,默认为function。

  1. function:每个test都运行,默认是function的scope
  2. class:每个class的所有test只运行一次
  3. module:每个module的所有test只运行一次
  4. session:每个session只运行一次
    ```python

    conftest.py

    import pytest

@pytest.fixture(scope='function', autouse=True)
def func_scope():
print('opened')
yield
print('closed')

@pytest.fixture(scope='module', autouse=True)
def mod_scope():
pass

@pytest.fixture(scope='session')
def sess_scope():
pass

@pytest.fixture(scope='class')
def class_scope():
pass


```python
# test_fun.py 
import pytest

@pytest.mark.usefixtures('sess_scope')
def test_pass(class_scope):
    assert func(3) == 4

输出结果:

# 使用 --setuo-show 选项,显示详细的固件信息
$ pytest --setup-show -k pass test_fun.py
collected 2 items / 1 deselected / 1 selected                                                                                                                            

test_sample.py
SETUP    S sess_scope
    SETUP    M mod_scope
      SETUP    C class_scope
        SETUP    F func_scope
        test_sample.py::test_pass (fixtures used: class_scope, func_scope, mod_scope, sess_scope).
        TEARDOWN F func_scope
      TEARDOWN C class_scope
    TEARDOWN M mod_scope
TEARDOWN S sess_scope

setup和teardown操作:

  1. setup,在测试函数或类之前执行,完成准备工作,例如数据库链接、测试数据、打开文件等
  2. teardown,在测试函数或类之后执行,完成收尾工作,例如断开数据库链接、回收内存资源等
  3. 备注:也可以通过在fixture函数中通过yield实现setup和teardown功能

4. One More Thing

4.1 重复运行

pip install pytest-repeat

# 在执行命令中添加–count=NUM NUM为每条用例需要执行的次数
pytest test_se.py --count=3

4.2 并行运行

pip install pytest-xdist

# pytest-xdist插件的 -n numprocesses 选项可以指定运行测试的处理器进程数,-n auto选项可以自动侦测系统里的CPU数码
pytest -n 3 test_test.py
pytest -n auto test_test.py

4.3 设定执行顺序

pip install pytest-ordering
# 根据order参数从小到大执行

@pytest.mark.run(order=2)
def test_order1():
    print ("first test")
    assert True

@pytest.mark.run(order=1)
def test_order2():
    print ("second test")
    assert True

4.4 测试时间限制

在正常时间下,pytest是没有测试时间限制的,但有时候需要控制测试用例执行执行,可以使用pytest-timeout。

pip install pytest-timeout

# 在命令行中添加 –timeout=X,X为该条命令执行的总时间限制,单位秒
pytest --timeout=3.5 test_xxx.py

4.5 常用执行参数

-m: 用表达式指定多个标记名。 pytest 提供了一个装饰器 @pytest.mark.xxx,用于标记测试并分组(xxx是你定义的分组名),以便你快速选中并运行,各个分组直接用 and、or 来分割。
# 首先给测试用例打标签(mark),在 Class、method 上加上如下装饰器:
# @pytest.mark.xxx

# 同时选中带有这两个标签的所有测试用例运行
pytest -m "mark1 and mark2"
# 选中带有mark1的测试用例,不运行mark2的测试用例
pytest -m "mark1 and not mark2"
# 选中带有mark1或 mark2标签的所有测试用例
pytest -m "mark1 or mark2"

-v: 运行时输出更详细的用例执行信息 不使用 -v 参数,运行时不会显示运行的具体测试用例名称;使用 -v 参数,会在 console 里打印出具体哪条测试用例被运行。

-q: 类似 unittest 里的 verbosity,用来简化运行输出信息。 使用 -q 运行测试用例,仅仅显示很简单的运行信息, 例如:
.s..  [100%]
3 passed, 1 skipped in 9.60s

-k: 可以通过表达式运行指定的测试用例。 它是一种模糊匹配,用 and 或 or 区分各个关键字,匹配范围有文件名、类名、函数名。
# 运行test_se.py下的所有的测试
pytest -k "test_se.py"
# 因为se能匹配上test_se.py,故运行test_se.py下所有的测试
pytest -k "se"
# 因为Baidu能匹配上test_baidu.py里定义的测试类Baidu,故运行Baidu测试类下所有的测试,你也可以写成Bai.
pytest -k "Baidu"


-x: 出现一条测试用例失败就退出测试。 在调试时,这个功能非常有用。当出现测试失败时,停止运行后续的测试。

-s: 显示print内容 在运行测试脚本时,为了调试或打印一些内容,我们会在代码中加一些print内容,但是在运行pytest时,这些内容不会显示出来。如果带上-s,就可以显示了。
pytest test_se.py -s

4.6 忽略测试用例执行

4.6.1 直接忽略测试执行

# 直接忽略可以使用 @pytest.mark.skip 装饰器来实现。
@pytest.mark.skip(reason='skip此测试用例')
def test_get_new_message:
    pass

4.6.2 按条件忽略测试执行—使用’skipif’忽略

# 按skipif条件,当条件符合时便会忽略某条测试用例执行。
# 定义一个flag,用来指示是否要skip一个测试用例
flag = 1
# 此处判断flag的值,为1则忽略,0则不忽略 
@pytest.mark.skipif(flag == 1, reason='by condition')
def test_get_new_message:
    pass

4.6.3 xfail 失败后继续执行

在使用pytest时,有些用例我们预计他可能会失败,这个时候就需要使用xfail了,当测试用例失败时,会被跳过:

import pytest

@pytest.mark.xfail()
def test_1():
    assert 1 == 2

@pytest.mark.xfail()
def test_2():
    assert 1 == 1

if __name__ == '__main__':
    pytest.main()

执行后的结果,会使用大小写的xX来表示,算入预期内的通过和不通过。

4.6.4 按条件忽略测试执行—使用’-m’或者’-k’忽略

# 忽略方法名包含method的测试用例
pytest -k "not method"
# 忽略被装饰器@pytest.mark.slow装饰的所有测试用例
pytest -m "not slow"

5. Author: 作者信息

数字老K
quantgalaxy@outlook.com
欢迎交流

目录
相关文章
|
3天前
|
安全 前端开发 数据库
Python 语言结合 Flask 框架来实现一个基础的代购商品管理、用户下单等功能的简易系统
这是一个使用 Python 和 Flask 框架实现的简易代购系统示例,涵盖商品管理、用户注册登录、订单创建及查看等功能。通过 SQLAlchemy 进行数据库操作,支持添加商品、展示详情、库存管理等。用户可注册登录并下单,系统会检查库存并记录订单。此代码仅为参考,实际应用需进一步完善,如增强安全性、集成支付接口、优化界面等。
|
21天前
|
JSON 数据可视化 测试技术
python+requests接口自动化框架的实现
通过以上步骤,我们构建了一个基本的Python+Requests接口自动化测试框架。这个框架具有良好的扩展性,可以根据实际需求进行功能扩展和优化。它不仅能提高测试效率,还能保证接口的稳定性和可靠性,为软件质量提供有力保障。
51 7
|
18天前
|
分布式计算 大数据 数据处理
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
57 2
|
23天前
|
数据可视化 DataX Python
Seaborn 教程-绘图函数
Seaborn 教程-绘图函数
47 8
|
23天前
Seaborn 教程-主题(Theme)
Seaborn 教程-主题(Theme)
72 7
|
23天前
|
Python
Seaborn 教程-模板(Context)
Seaborn 教程-模板(Context)
47 4
|
23天前
|
数据可视化 Python
Seaborn 教程
Seaborn 教程
45 5
|
1月前
|
敏捷开发 测试技术 持续交付
自动化测试之美:从零开始搭建你的Python测试框架
在软件开发的马拉松赛道上,自动化测试是那个能让你保持节奏、避免跌宕起伏的神奇小助手。本文将带你走进自动化测试的世界,用Python这把钥匙,解锁高效、可靠的测试框架之门。你将学会如何步步为营,构建属于自己的测试庇护所,让代码质量成为晨跑时清新的空气,而不是雾霾中的忧虑。让我们一起摆脱手动测试的繁琐枷锁,拥抱自动化带来的自由吧!
|
2月前
|
缓存 API 数据库
Python哪个框架合适开发速卖通商品详情api?
在跨境电商平台速卖通的商品详情数据获取与整合中,Python 语言及其多种框架(如 Flask、Django、Tornado 和 FastAPI)提供了高效解决方案。Flask 简洁灵活,适合快速开发;Django 功能全面,适用于大型项目;Tornado 性能卓越,擅长处理高并发;FastAPI 结合类型提示和异步编程,开发体验优秀。选择合适的框架需综合考虑项目规模、性能要求和团队技术栈。
31 2
|
2月前
|
安全 API 数据库
Python哪个框架合适开发淘宝商品详情api?
在数字化商业时代,开发淘宝商品详情API成为企业拓展业务的重要手段。Python凭借其强大的框架支持,如Flask、Django、Tornado和FastAPI,为API开发提供了多样化的选择。本文探讨了这些框架的特点、优势及应用场景,帮助开发者根据项目需求选择最合适的工具,确保API的高效、稳定与可扩展性。
31 0