# Pytest 教程

> 原文： [http://zetcode.com/python/pytest/](http://zetcode.com/python/pytest/)

Pytest 教程展示了如何使用 Pytest 模块测试 Python 应用。

## python Pytest

Pytest 是用于测试 Python 应用的 Python 库。 它是鼻子测试和单元测试的替代方法。

## Pytest 安装

使用以下命令安装 Pytest：

```py
$ pip install pytest 

```

这将安装 Pytest 库。

## Pytest 测试发现约定

如果未指定任何参数，则在`testpaths`（如果已配置）或当前目录中的位置搜索测试文件。 另外，命令行参数可以在目录，文件名或节点 ID 的任何组合中使用。

Pytest 在所选目录中查找`test_*.py`或`*_test.py`文件。 在选定的文件中，Pytest 在类之外查找带前缀的测试函数，并在带前缀的测试类中查找带前缀的测试方法（无`__init__()`方法）。

## 运行 Pytest

Pytest 不带任何参数，将查看当前工作目录（或其他一些预配置的目录）以及测试文件的所有子目录，并运行找到的测试代码。

```py
$ pytest

```

运行当前目录中的所有测试文件。

```py
$ pytest min_max_test.py

```

我们可以通过指定名称作为参数来运行特定的测试文件。

```py
$ pytest min_max_test.py::test_min

```

可以通过在`::`字符后提供其名称来运行特定函数。

```py
$ pytest -m smoke

```

标记可用于对测试进行分组。 然后使用`pytest -m`运行一组标记的测试。

```py
$ pytest -k <expression>

```

另外，我们可以使用表达式来运行与测试函数和类的名称匹配的测试。

## Python Pytest 简单示例

在第一个示例中，我们将使用 Pytest 测试两个简单的数学算法。

`algo.py`

```py
def max(values):

  _max = values[0]

  for val in values:
      if val > _max:
          _max = val

  return _max

def min(values):

  _min = values[0]

  for val in values:
      if val < _min:
          _min = val

  return _min

```

我们有一个带有自定义`max()`和`min()`函数的模块。

`min_max_test.py`

```py
#!/usr/bin/env python3

import algo

def test_min():
    values = (2, 3, 1, 4, 6)

    val = algo.min(values)
    assert val == 1

def test_max():
    values = (2, 3, 1, 4, 6)

    val = algo.max(values)
    assert val == 6

```

测试文件`min_max_test.py`的名称中包含一个测试词。

```py
def test_min():
  values = (2, 3, 1, 4, 6)

  val = algo.min(values)
  assert val == 1

```

此外，测试函数`test_min()`具有测试字。 我们使用`assert`关键字来测试算法的值。

```py
$ pytest min_max_test.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest
collected 2 items

min_max_test.py ..                                                                                               [100%]

============================================== 2 passed in 0.03 seconds ===============================================

```

这是输出。 有两个测试，并且都成功通过了。 `pytest -v min_max_test.py`显示了更详细的输出。

## Pytest 跳过

使用跳过装饰器，我们可以跳过指定的测试。 跳过测试有多种原因。 例如，数据库/在线服务目前不可用，或者我们跳过了 Windows 上针对 Linux 的特定测试。

`skipping.py`

```py
#!/usr/bin/env python3

import algo
import pytest

@pytest.mark.skip
def test_min():
    values = (2, 3, 1, 4, 6)

    val = algo.min(values)
    assert val == 1

def test_max():
    values = (2, 3, 1, 4, 6)

    val = algo.max(values)
    assert val == 6

```

在示例中，`test_min()`被跳过。

```py
$ pytest min_max_test.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest
collected 2 items

min_max_test.py s.                                                                                               [100%]

========================================= 1 passed, 1 skipped in 0.04 seconds =========================================

```

在测试文件名后面的输出中，`s`代表跳过的和。 通过。

## Pytest 标记

我们可以使用标记将测试组织为单元。

`marking.py`

```py
#!/usr/bin/env python3

# pytest -m a marking.py
# pytest -m b marking.py

import pytest

@pytest.mark.a
def test_a1():

    assert (1) == (1)

@pytest.mark.a
def test_a2():

    assert (1, 2) == (1, 2)

@pytest.mark.a
def test_a3():

    assert (1, 2, 3) == (1, 2, 3)

@pytest.mark.b
def test_b1():

    assert "falcon" == "fal" + "con"

@pytest.mark.b
def test_b2():

    assert "falcon" == f"fal{'con'}"

```

我们有两组由标记`a`和`b`标识的测试。 这些单元由`pytest -m a marking.py`和`pytest -m b marking.py`运行。

## Pytest 参数化测试

通过参数化测试，我们可以向断言中添加多个值。 我们使用`@pytest.mark.parametrize`标记。

`parametrized.py`

```py
#!/usr/bin/env python3

import algo
import pytest

@pytest.mark.parametrize("data, expected", [((2, 3, 1, 4, 6), 1), 
    ((5, -2, 0, 9, 12), -2), ((200, 100, 0, 300, 400), 0)])
def test_min(data, expected):

    val = algo.min(data)
    assert val == expected

@pytest.mark.parametrize("data, expected", [((2, 3, 1, 4, 6), 6), 
    ((5, -2, 0, 9, 12), 12), ((200, 100, 0, 300, 400), 400)])
def test_max(data, expected):

    val = algo.max(data)
    assert val == expected

```

在示例中，我们使用多个输入数据测试这两个函数。

```py
@pytest.mark.parametrize("data, expected", [((2, 3, 1, 4, 6), 1), 
    ((5, -2, 0, 9, 12), -2), ((200, 100, 0, 300, 400), 0)])
def test_min(data, expected):

    val = algo.min(data)
    assert val == expected

```

我们将两个值传递给测试函数：数据和期望值。 在我们的例子中，我们用三个数据元组测试`min()`函数。

```py
$ pytest parametrized.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest
collected 6 items

parametrized.py ......                                                                                           [100%]

============================================== 6 passed in 0.03 seconds ===============================================

```

Pytest 输出告知有六次运行。

## Pytest 夹具

测试需要在一组已知对象的背景下进行。 这组对象称为测试夹具。

`algo.py`

```py
def sel_sort(data):

  if not isinstance(data, list):
      vals = list(data)
  else:
      vals = data

  size = len(vals)

  for i in range(0, size):

      for j in range(i+1, size):

          if vals[j] < vals[i]:
              _min = vals[j]
              vals[j] = vals[i]
              vals[i] = _min
  return vals
...  

```

对于此示例，我们向`algo.py`模块添加了一个选择排序算法。

`fixtures.py`

```py
#!/usr/bin/env python3

import algo
import pytest

@pytest.fixture
def data():

    return [3, 2, 1, 5, -3, 2, 0, -2, 11, 9]

def test_sel_sort(data):

    sorted_vals = algo.sel_sort(data)
    assert sorted_vals == sorted(data)

```

我们用夹具测试选择排序。

```py
@pytest.fixture
def data():

    return [3, 2, 1, 5, -3, 2, 0, -2, 11, 9]

```

我们的测试装置仅返回一些测试数据。 请注意，我们通过其名称引用此灯具：`data`。

```py
def test_sel_sort(data):

  sorted_vals = algo.sel_sort(data)
  assert sorted_vals == sorted(data)

```

在`test_sel_sort()`函数中，我们将数据夹具作为函数参数传递。

```py
$ pytest fixtures.py
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest
collected 1 item

fixtures.py .                                                                                                    [100%]

============================================== 1 passed in 0.02 seconds ===============================================

```

这是输出。

## Pytest 布局

Python 测试可以多种方式组织。 测试可以集成在 Python 包中，也可以放在包外。

### 综合测试

接下来，我们展示如何在 Python 包中运行测试。

```py
setup.py
utils
│   algo.py
│   srel.py
│   __init__.py
│
└───tests
        algo_test.py
        srel_test.py
        __init__.py

```

我们有这种包装布局。 测试与包一起位于`tests`子目录中。

`setup.py`

```py
#!/usr/bin/env python3

from setuptools import setup, find_packages

setup(name="utils", packages=find_packages())

```

这是`setup.py`。

`utils/algo.py`

```py
def sel_sort(data):

    if not isinstance(data, list):
        vals = list(data)
    else:
        vals = data

    size = len(vals)

    for i in range(0, size):

        for j in range(i+1, size):

            if vals[j] < vals[i]:
                _min = vals[j]
                vals[j] = vals[i]
                vals[i] = _min
    return vals

def max(values):

    _max = values[0]

    for val in values:
        if val > _max:
            _max = val

    return _max

def min(values):

    _min = values[0]

    for val in values:
        if val < _min:
            _min = val

    return _min

```

这是`algo.py`文件。

`utils/srel.py`

```py
def is_palindrome(val):

    return val == val[::-1]

```

我们还有另一个模块，其中包含一个测试单词是否为回文的函数。

`tests/algo_test.py`

```py
#!/usr/bin/env python3

import utils.algo
import pytest

@pytest.fixture
def data():

    return [3, 2, 1, 5, -3, 2, 0, -2, 11, 9]

def test_sel_sort(data):

    sorted_vals = utils.algo.sel_sort(data)
    assert sorted_vals == sorted(data)

def test_min():
    values = (2, 3, 1, 4, 6)

    val = utils.algo.min(values)
    assert val == 1

def test_max():
    values = (2, 3, 1, 4, 6)

    val = utils.algo.max(values)
    assert val == 6

```

这些是`utils.algo`模块的测试。 注意，我们使用完整的模块名称。

`tests/srel_test.py`

```py
#!/usr/bin/env python3

import utils.srel
import pytest

@pytest.mark.parametrize("word, expected", [('kayak', True), 
    ('civic', True), ('forest', False)])
def test_palindrome(word, expected):

    val = utils.srel.is_palindrome(word)
    assert val == expected

```

这是对`is_palindrome()`函数的测试。

`utils/__init__.py`

`utils/tests/__init__.py`

两个`__init__.py`文件均为空。

```py
$ pytest --pyargs utils
================================================= test session starts =================================================
platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\Jano\Documents\pyprogs\pytest\structure
collected 6 items

utils\tests\algo_test.py ...                                                                                     [ 50%]
utils\tests\srel_test.py ...                                                                                     [100%]

============================================== 6 passed in 0.06 seconds ===============================================

```

我们使用`pytest --pyargs utils`命令运行测试。

### 外部测试

下一个示例显示了应用源布局，其中测试未集成在包内。

```py
setup.py
src
└───utils
│       algo.py
│       srel.py
tests
    algo_test.py
    srel_test.py

```

在这种布局中，我们在源代码树之外进行测试。 请注意，不需要`__init__.py`文件。

```py
$ set PYTHONPATH=src
$ pytest

```

我们设置`PYTHONPATH`并运行 Pytest。

在本教程中，我们介绍了 Python Pytest 库。

您可能也对以下相关教程感兴趣： [Django 电子邮件教程](/django/email/)， [Python Jinja 教程](/python/jinja/)和 [Python 教程](/lang/python/)，或列出[所有 Python 教程](/all/#python)。