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
欢迎交流

目录
相关文章
|
9天前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
48 6
|
8天前
|
存储 Python
SciPy 教程 之 SciPy 稀疏矩阵 4
SciPy 教程之 SciPy 稀疏矩阵 4:介绍稀疏矩阵的概念、类型及其在科学计算中的应用。SciPy 的 `scipy.sparse` 模块提供了处理稀疏矩阵的工具,重点讲解了 CSC 和 CSR 两种格式,并通过示例演示了如何创建和操作 CSR 矩阵。
30 3
|
3天前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
20 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
2天前
|
机器学习/深度学习 数据处理 Python
SciPy 教程 之 SciPy 空间数据 4
本教程介绍了SciPy的空间数据处理功能,主要通过scipy.spatial模块实现。内容涵盖空间数据的基本概念、距离矩阵的定义及其在生物信息学中的应用,以及如何计算欧几里得距离。示例代码展示了如何使用SciPy计算两点间的欧几里得距离。
15 5
|
1天前
|
机器学习/深度学习 Python
SciPy 教程 之 SciPy 空间数据 6
本教程介绍了SciPy处理空间数据的方法,包括使用scipy.spatial模块进行点位置判断、最近点计算等内容。还详细讲解了距离矩阵的概念及其应用,如在生物信息学中表示蛋白质结构等。最后,通过实例演示了如何计算两点间的余弦距离。
8 3
|
4天前
|
Python
SciPy 教程 之 SciPy 图结构 7
《SciPy 教程 之 SciPy 图结构 7》介绍了 SciPy 中处理图结构的方法。图是由节点和边组成的集合,用于表示对象及其之间的关系。scipy.sparse.csgraph 模块提供了多种图处理功能,如 `breadth_first_order()` 方法可按广度优先顺序遍历图。示例代码展示了如何使用该方法从给定的邻接矩阵中获取广度优先遍历的顺序。
13 2
|
4天前
|
算法 Python
SciPy 教程 之 SciPy 图结构 5
SciPy 图结构教程,介绍图的基本概念和SciPy中处理图结构的模块scipy.sparse.csgraph。重点讲解贝尔曼-福特算法,用于求解任意两点间最短路径,支持有向图和负权边。通过示例演示如何使用bellman_ford()方法计算最短路径。
14 3
|
4天前
|
缓存 测试技术 Apache
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
14 1
|
9天前
|
存储 Python
SciPy 教程 之 SciPy 稀疏矩阵 2
SciPy教程之SciPy稀疏矩阵2:介绍稀疏矩阵的概念、应用场景及scipy.sparse模块的使用。重点讲解CSC和CSR两种稀疏矩阵类型及其常用方法,如data属性和count_nonzero()方法。
33 4
|
1天前
|
安全 API 网络架构
Python中哪个框架最适合做API?
本文介绍了Python生态系统中几个流行的API框架,包括Flask、FastAPI、Django Rest Framework(DRF)、Falcon和Tornado。每个框架都有其独特的优势和适用场景。Flask轻量灵活,适合小型项目;FastAPI高性能且自动生成文档,适合需要高吞吐量的API;DRF功能强大,适合复杂应用;Falcon高性能低延迟,适合快速API开发;Tornado异步非阻塞,适合高并发场景。文章通过示例代码和优缺点分析,帮助开发者根据项目需求选择合适的框架。
13 0
下一篇
无影云桌面