
- Pytest 教程
- Pytest - 首页
- Pytest - 简介
- Pytest - 环境设置
- 识别测试文件和函数
- Pytest - 从基本测试开始
- Pytest - 文件执行
- 执行测试套件的子集
- 测试名称的子字符串匹配
- Pytest - 测试分组
- Pytest - Fixture
- Pytest - conftest.py
- Pytest - 参数化测试
- Pytest - Xfail/Skip 测试
- 在 N 次测试失败后停止测试套件
- Pytest - 并行运行测试
- XML 格式的测试执行结果
- Pytest - 总结
- Pytest - 结论
- Pytest 有用资源
- Pytest 快速指南
- Pytest - 有用资源
- Pytest - 讨论
Pytest 快速指南
Pytest - 简介
Pytest 是一个基于 Python 的测试框架,用于编写和执行测试代码。在当今 REST 服务盛行的时代,Pytest 主要用于 API 测试,尽管我们可以使用 Pytest 编写从简单到复杂的测试,例如,我们可以编写代码来测试 API、数据库、UI 等。
Pytest 的优点
Pytest 的优点如下:
Pytest 可以并行运行多个测试,从而减少测试套件的执行时间。
Pytest 有其自身的方法来自动检测测试文件和测试函数,如果未明确提及。
Pytest 允许我们在执行期间跳过测试的子集。
Pytest 允许我们运行整个测试套件的子集。
Pytest 是免费且开源的。
由于其简单的语法,Pytest 非常易于上手。
在本教程中,我们将通过示例程序解释 Pytest 的基础知识。
Pytest - 环境设置
在本节中,我们将学习如何安装 Pytest。
要开始安装,请执行以下命令:
pip install pytest == 2.9.1
我们可以安装任何版本的 Pytest。这里,2.9.1 是我们正在安装的版本。
要安装最新版本的 Pytest,请执行以下命令:
pip install pytest
使用以下命令确认安装,以显示 Pytest 的帮助部分。
pytest -h
识别测试文件和测试函数
在不提及文件名的情况下运行 pytest 将运行当前目录和子目录中所有格式为 **test_*.py** 或 ***_test.py** 的文件。Pytest 会自动将这些文件识别为测试文件。我们可以通过明确提及它们来让 Pytest 运行其他文件名。
Pytest 要求测试函数名称以 **test** 开头。名称不为 **test*** 格式的函数名称不被 Pytest 视为测试函数。我们**不能**显式地使 Pytest 将任何不以 **test** 开头的函数视为测试函数。
我们将在后续章节中了解测试的执行。
Pytest - 从基本测试开始
现在,我们将从第一个 Pytest 程序开始。我们将首先创建一个目录,然后在该目录中创建我们的测试文件。
让我们按照以下步骤操作:
创建一个名为 **automation** 的新目录,并在命令行中导航到该目录。
创建一个名为 **test_square.py** 的文件,并将以下代码添加到该文件中。
import math def test_sqrt(): num = 25 assert math.sqrt(num) == 5 def testsquare(): num = 7 assert 7*7 == 40 def tesequality(): assert 10 == 11
使用以下命令运行测试:
pytest
上述命令将生成以下输出:
test_square.py .F ============================================== FAILURES ============================================== ______________________________________________ testsquare _____________________________________________ def testsquare(): num=7 > assert 7*7 == 40 E assert (7 * 7) == 40 test_square.py:9: AssertionError ================================= 1 failed, 1 passed in 0.06 seconds =================================
请查看结果的第一行。它显示文件名和结果。F 代表测试失败,点 (.) 代表测试成功。
在那之下,我们可以看到失败测试的详细信息。它将显示测试在哪个语句失败。在我们的示例中,7*7 与 49 的相等性比较是错误的。最后,我们可以看到测试执行摘要,1 个失败,1 个通过。
函数 tescompare 没有执行,因为 Pytest 不会将其视为测试,因为其名称不是 **test*** 格式。
现在,执行以下命令并再次查看结果:
pytest -v
-v 增加详细程度。
test_square.py::test_sqrt PASSED test_square.py::testsquare FAILED ============================================== FAILURES ============================================== _____________________________________________ testsquare _____________________________________________ def testsquare(): num = 7 > assert 7*7 == 40 E assert (7 * 7) == 40 test_square.py:9: AssertionError ================================= 1 failed, 1 passed in 0.04 seconds =================================
现在,结果更详细地说明了失败的测试和通过的测试。
**注意** - pytest 命令将执行当前目录和子目录中所有格式为 **test_*** 或 ***_test** 的文件。
Pytest - 文件执行
在本节中,我们将学习如何执行单个测试文件和多个测试文件。我们已经创建了一个测试文件 **test_square.py**。创建一个新的测试文件 **test_compare.py**,其中包含以下代码:
def test_greater(): num = 100 assert num > 100 def test_greater_equal(): num = 100 assert num >= 100 def test_less(): num = 100 assert num < 200
现在,要运行所有文件(此处为 2 个文件)中的所有测试,我们需要运行以下命令:
pytest -v
上述命令将运行 **test_square.py** 和 **test_compare.py** 中的测试。将生成以下输出:
test_compare.py::test_greater FAILED test_compare.py::test_greater_equal PASSED test_compare.py::test_less PASSED test_square.py::test_sqrt PASSED test_square.py::testsquare FAILED ================================================ FAILURES ================================================ ______________________________________________ test_greater ______________________________________________ def test_greater(): num = 100 > assert num > 100 E assert 100 > 100 test_compare.py:3: AssertionError _______________________________________________ testsquare _______________________________________________ def testsquare(): num = 7 > assert 7*7 == 40 E assert (7 * 7) == 40 test_square.py:9: AssertionError =================================== 2 failed, 3 passed in 0.07 seconds ===================================
要执行特定文件中的测试,请使用以下语法:
pytest <filename> -v
现在,运行以下命令:
pytest test_compare.py -v
上述命令将仅执行 **test_compare.py** 文件中的测试。我们的结果将是:
test_compare.py::test_greater FAILED test_compare.py::test_greater_equal PASSED test_compare.py::test_less PASSED ============================================== FAILURES ============================================== ____________________________________________ test_greater ____________________________________________ def test_greater(): num = 100 > assert num > 100 E assert 100 > 100 test_compare.py:3: AssertionError ================================= 1 failed, 2 passed in 0.04 seconds =================================
执行测试套件的子集
在实际场景中,我们将有多个测试文件,每个文件将包含多个测试。测试将涵盖各种模块和功能。假设我们只想运行一组特定的测试;我们该如何操作呢?
Pytest 提供两种方法来运行测试套件的子集。
- 根据测试名称的子字符串匹配选择要运行的测试。
- 根据应用的标记选择要运行的测试组。
我们将在后续章节中通过示例解释这两点。
测试名称的子字符串匹配
要执行测试名称中包含字符串的测试,我们可以使用以下语法:
pytest -k <substring> -v
-k <substring> 表示要在测试名称中搜索的子字符串。
现在,运行以下命令:
pytest -k great -v
这将执行测试名称中包含单词 **‘great’** 的所有测试。在这种情况下,它们是 **test_greater()** 和 **test_greater_equal()**。请查看下面的结果。
test_compare.py::test_greater FAILED test_compare.py::test_greater_equal PASSED ============================================== FAILURES ============================================== ____________________________________________ test_greater ____________________________________________ def test_greater(): num = 100 > assert num > 100 E assert 100 > 100 test_compare.py:3: AssertionError ========================== 1 failed, 1 passed, 3 deselected in 0.07 seconds ==========================
在这里的结果中,我们可以看到 3 个测试被取消选择。这是因为这些测试名称中不包含单词 **great**。
**注意** - 测试函数的名称仍然应该以 'test' 开头。
Pytest - 测试分组
在本节中,我们将学习如何使用标记对测试进行分组。
Pytest 允许我们在测试函数上使用标记。标记用于为测试函数设置各种特性/属性。Pytest 提供了许多内置标记,例如 xfail、skip 和 parametrize。除此之外,用户还可以创建自己的标记名称。使用下面给出的语法将标记应用于测试:
@pytest.mark.<markername>
要使用标记,我们必须在测试文件中 **导入 pytest** 模块。我们可以将我们自己的标记名称定义到测试中,并运行具有这些标记名称的测试。
要运行标记的测试,我们可以使用以下语法:
pytest -m <markername> -v
-m <markername> 表示要执行的测试的标记名称。
使用以下代码更新我们的测试文件 **test_compare.py** 和 **test_square.py**。我们定义了 3 个标记 **– great、square、others**。
test_compare.py
import pytest @pytest.mark.great def test_greater(): num = 100 assert num > 100 @pytest.mark.great def test_greater_equal(): num = 100 assert num >= 100 @pytest.mark.others def test_less(): num = 100 assert num < 200
test_square.py
import pytest import math @pytest.mark.square def test_sqrt(): num = 25 assert math.sqrt(num) == 5 @pytest.mark.square def testsquare(): num = 7 assert 7*7 == 40 @pytest.mark.others def test_equality(): assert 10 == 11
现在,要运行标记为 **others** 的测试,请运行以下命令:
pytest -m others -v
请查看下面的结果。它运行了标记为 **others** 的 2 个测试。
test_compare.py::test_less PASSED test_square.py::test_equality FAILED ============================================== FAILURES ============================================== ___________________________________________ test_equality ____________________________________________ @pytest.mark.others def test_equality(): > assert 10 == 11 E assert 10 == 11 test_square.py:16: AssertionError ========================== 1 failed, 1 passed, 4 deselected in 0.08 seconds ==========================
同样,我们也可以运行具有其他标记的测试 - great、compare
Pytest - Fixture
Fixture 是函数,它将在应用于其的每个测试函数之前运行。Fixture 用于向测试提供一些数据,例如数据库连接、要测试的 URL 和某种输入数据。因此,与其为每个测试运行相同的代码,我们可以将 fixture 函数附加到测试中,它将在执行每个测试之前运行并向测试返回数据。
一个函数通过以下方式标记为 fixture:
@pytest.fixture
测试函数可以通过将 fixture 名称作为输入参数来使用 fixture。
创建一个文件 **test_div_by_3_6.py** 并将以下代码添加到其中
import pytest @pytest.fixture def input_value(): input = 39 return input def test_divisible_by_3(input_value): assert input_value % 3 == 0 def test_divisible_by_6(input_value): assert input_value % 6 == 0
这里,我们有一个名为 **input_value** 的 fixture 函数,它向测试提供输入。要访问 fixture 函数,测试必须将 fixture 名称作为输入参数提及。
Pytest 在测试执行期间,将看到 fixture 名称作为输入参数。然后它执行 fixture 函数,并将返回值存储到输入参数中,测试可以使用该参数。
使用以下命令执行测试:
pytest -k divisible -v
上述命令将生成以下结果:
test_div_by_3_6.py::test_divisible_by_3 PASSED test_div_by_3_6.py::test_divisible_by_6 FAILED ============================================== FAILURES ============================================== ________________________________________ test_divisible_by_6 _________________________________________ input_value = 39 def test_divisible_by_6(input_value): > assert input_value % 6 == 0 E assert (39 % 6) == 0 test_div_by_3_6.py:12: AssertionError ========================== 1 failed, 1 passed, 6 deselected in 0.07 seconds ==========================
但是,这种方法有其自身的局限性。在测试文件中定义的 fixture 函数的范围仅限于测试文件内。我们不能在另一个测试文件中使用该 fixture。要使 fixture 可用于多个测试文件,我们必须在名为 conftest.py 的文件中定义 fixture 函数。**conftest.py** 在下一节中解释。
Pytest - conftest.py
我们可以在此文件中定义 fixture 函数,以使其可在多个测试文件中访问。
创建一个新文件 **conftest.py** 并将以下代码添加到其中:
import pytest @pytest.fixture def input_value(): input = 39 return input
编辑 **test_div_by_3_6.py** 以删除 fixture 函数:
import pytest def test_divisible_by_3(input_value): assert input_value % 3 == 0 def test_divisible_by_6(input_value): assert input_value % 6 == 0
创建一个新文件 **test_div_by_13.py**:
import pytest def test_divisible_by_13(input_value): assert input_value % 13 == 0
现在,我们有 **test_div_by_3_6.py** 和 **test_div_by_13.py** 文件使用在 **conftest.py** 中定义的 fixture。
通过执行以下命令来运行测试:
pytest -k divisible -v
上述命令将生成以下结果:
test_div_by_13.py::test_divisible_by_13 PASSED test_div_by_3_6.py::test_divisible_by_3 PASSED test_div_by_3_6.py::test_divisible_by_6 FAILED ============================================== FAILURES ============================================== ________________________________________ test_divisible_by_6 _________________________________________ input_value = 39 def test_divisible_by_6(input_value): > assert input_value % 6 == 0 E assert (39 % 6) == 0 test_div_by_3_6.py:7: AssertionError ========================== 1 failed, 2 passed, 6 deselected in 0.09 seconds ==========================
测试将查找同一文件中的 fixture。由于在文件中找不到 fixture,它将检查 conftest.py 文件中的 fixture。找到后,将调用 fixture 方法,并将结果返回到测试的输入参数。
Pytest - 参数化测试
测试的参数化是为多个输入集运行测试。我们可以使用以下标记来做到这一点:
@pytest.mark.parametrize
将以下代码复制到名为 **test_multiplication.py** 的文件中:
import pytest @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)]) def test_multiplication_11(num, output): assert 11*num == output
这里,测试将输入乘以 11 并将结果与预期输出进行比较。测试有 4 组输入,每组有两个值 - 一个是要乘以 11 的数字,另一个是预期结果。
通过运行以下命令来执行测试:
Pytest -k multiplication -v
上述命令将生成以下输出:
test_multiplication.py::test_multiplication_11[1-11] PASSED test_multiplication.py::test_multiplication_11[2-22] PASSED test_multiplication.py::test_multiplication_11[3-35] FAILED test_multiplication.py::test_multiplication_11[4-44] PASSED ============================================== FAILURES ============================================== _________________ test_multiplication_11[3-35] __________________ num = 3, output = 35 @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)]) def test_multiplication_11(num, output): > assert 11*num == output E assert (11 * 3) == 35 test_multiplication.py:5: AssertionError ============================== 1 failed, 3 passed, 8 deselected in 0.08 seconds ==============================
Pytest - Xfail/Skip 测试
在本节中,我们将学习 Pytest 中的 Skip 和 Xfail 测试。
现在,考虑以下情况:
- 由于某些原因,测试在一段时间内不相关。
- 正在实施一项新功能,我们已经为此功能添加了一个测试。
在这些情况下,我们可以选择 xfail 测试或跳过测试。
Pytest 将执行 xfailed 测试,但它不会被视为失败或通过的测试的一部分。即使测试失败,这些测试的详细信息也不会打印(记住 pytest 通常会打印失败测试的详细信息)。我们可以使用以下标记来 xfail 测试:
@pytest.mark.xfail
跳过测试意味着不会执行测试。我们可以使用以下标记来跳过测试:
@pytest.mark.skip
稍后,当测试变得相关时,我们可以删除标记。
编辑我们已经拥有的 **test_compare.py** 以包含 xfail 和 skip 标记:
import pytest @pytest.mark.xfail @pytest.mark.great def test_greater(): num = 100 assert num > 100 @pytest.mark.xfail @pytest.mark.great def test_greater_equal(): num = 100 assert num >= 100 @pytest.mark.skip @pytest.mark.others def test_less(): num = 100 assert num < 200
使用以下命令执行测试:
pytest test_compare.py -v
执行后,上述命令将生成以下结果:
test_compare.py::test_greater xfail test_compare.py::test_greater_equal XPASS test_compare.py::test_less SKIPPED ============================ 1 skipped, 1 xfailed, 1 xpassed in 0.06 seconds ============================
Pytest - 在 N 次测试失败后停止测试套件
在实际场景中,一旦准备就绪要部署新版本的代码,它首先会部署到预生产/登台环境中。然后,测试套件会在其上运行。
只有当测试套件全部通过时,代码才符合生产部署的条件。如果出现测试失败,无论是一次还是多次失败,代码都无法用于生产。
因此,如果我们想在n个测试失败后立即停止测试套件的执行,该怎么办呢?这可以使用pytest中的`maxfail`选项来实现。
在n个测试失败后立即停止测试套件执行的语法如下:
pytest --maxfail = <num>
创建一个名为`test_failure.py`的文件,其中包含以下代码。
import pytest import math def test_sqrt_failure(): num = 25 assert math.sqrt(num) == 6 def test_square_failure(): num = 7 assert 7*7 == 40 def test_equality_failure(): assert 10 == 11
执行此测试文件将导致所有3个测试失败。在这里,我们将通过以下方式在第一次失败后停止测试执行:
pytest test_failure.py -v --maxfail 1
test_failure.py::test_sqrt_failure FAILED =================================== FAILURES =================================== _______________________________________ test_sqrt_failure __________________________________________ def test_sqrt_failure(): num = 25 > assert math.sqrt(num) == 6 E assert 5.0 == 6 E + where 5.0 = <built-in function sqrt>(25) E + where <built-in function sqrt>= math.sqrt test_failure.py:6: AssertionError =============================== 1 failed in 0.04 seconds ===============================
从上面的结果可以看出,执行在第一次失败时停止了。
Pytest - 并行运行测试
默认情况下,pytest按顺序运行测试。在实际场景中,一个测试套件将包含许多测试文件,每个文件将包含许多测试。这将导致较长的执行时间。为了克服这个问题,pytest提供了一个并行运行测试的选项。
为此,我们需要首先安装pytest-xdist插件。
运行以下命令安装pytest-xdist:
pip install pytest-xdist
现在,我们可以使用语法`pytest -n
pytest -n 3
`-n
如果只有少量测试需要运行,时间差异不会很大。但是,当测试套件很大时,这将非常重要。
XML格式的测试执行结果
我们可以生成测试执行的详细信息到一个xml文件中。这个xml文件主要用于那些具有显示测试结果的仪表盘的场景。在这种情况下,可以解析xml以获取执行的详细信息。
我们现在将执行`test_multiplcation.py`中的测试,并通过运行以下命令生成xml:
pytest test_multiplication.py -v --junitxml="result.xml"
现在我们可以看到生成了包含以下数据的`result.xml`:
<?xml version = "1.0" encoding = "utf-8"?> <testsuite errors = "0" failures = "1" name = "pytest" skips = "0" tests = "4" time = "0.061"> <testcase classname = "test_multiplication" file = "test_multiplication.py" line = "2" name = "test_multiplication_11[1-11]" time = "0.00117516517639> </testcase> <testcase classname = "test_multiplication" file = "test_multiplication.py" line = "2" name = "test_multiplication_11[2-22]" time = "0.00155973434448"> </testcase> <testcase classname = "test_multiplication" file = "test_multiplication.py" line = "2" name = "test_multiplication_11[3-35]" time = "0.00144290924072"> failure message = "assert (11 * 3) == 35">num = 3, output = 35 @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)]) def test_multiplication_11(num, output):> assert 11*num == output E assert (11 * 3) == 35 test_multiplication.py:5: AssertionErro </failure> </testcase> <testcase classname = "test_multiplication" file = "test_multiplication.py" line = "2" name = "test_multiplication_11[4-44]" time = "0.000945091247559"> </testcase> </testsuite>
这里,标签`
标签`
`提供了每个已执行测试的详细信息。 <failure> 标签提供了失败测试代码的详细信息。
Pytest - 总结
在本pytest教程中,我们涵盖了以下方面:
- 安装pytest。
- 识别测试文件和测试函数。
- 使用`pytest –v`执行所有测试文件。
- 使用`pytest
-v`执行特定文件。 - 使用子字符串匹配执行测试:`pytest -k
-v`。 - 基于标记执行测试:`pytest -m
-v`。 - 使用`@pytest.fixture`创建fixture。
- `conftest.py`允许从多个文件中访问fixture。
- 使用`@pytest.mark.parametrize`参数化测试。
- 使用`@pytest.mark.xfail`使测试预期失败。
- 使用`@pytest.mark.skip`跳过测试。
- 使用`pytest --maxfail =
`在n次失败后停止测试执行。 - 使用`pytest -n
`并行运行测试。 - 使用`pytest -v --junitxml = "result.xml"`生成结果xml。
Pytest - 结论
本教程介绍了pytest框架。现在您应该能够开始使用pytest编写测试了。
作为一个好的实践:
- 根据被测试的功能/模块创建不同的测试文件。
- 为测试文件和方法起有意义的名字。
- 使用足够的标记根据各种标准对测试进行分组。
- 在需要时使用fixture。