- Pytest教程
- Pytest - 主页
- Pytest - 简介
- Pytest - 环境设置
- 识别测试文件和函数
- Pytest - 从基本测试开始
- Pytest - 文件执行
- 执行测试套件的子集
- 测试名称的子字符串匹配
- Pytest - 对测试进行分组
- Pytest - 装置
- Pytest-Conftest.py
- Pytest - 参数化测试
- Pytest - Xfail/跳过测试
- 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开头。pytest 不会将非test*格式的函数名称视为测试函数。我们不能明确地让 pytest 将任何不以test开头的函数视为测试函数。
我们将在后续章节中了解测试的执行。
Pytest - 从基本测试开始
现在,我们将从我们的第一个 pytest 程序开始。我们将首先创建一个目录,然后在该目录中创建测试文件。
让我们按照下面所示的步骤操作 -
创建一个名为自动化的新目录,并在命令行中导航到该目录。
创建一个名为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 个测试被取消选择。这是因为这些测试名称中不包含“伟大”一词。
注意- 测试函数的名称仍应以“test”开头。
Pytest - 对测试进行分组
在本章中,我们将学习如何使用标记对测试进行分组。
Pytest 允许我们在测试函数上使用标记。标记用于设置测试功能的各种特征/属性。Pytest 提供了许多内置标记,例如 xfail、skip 和 parametrize。除此之外,用户还可以创建自己的标记名称。使用下面给出的语法将标记应用于测试 -
@pytest.mark.<markername>
要使用标记,我们必须在测试文件中导入 pytest模块。我们可以为测试定义我们自己的标记名称,并运行具有这些标记名称的测试。
要运行标记的测试,我们可以使用以下语法 -
pytest -m <markername> -v
-m <markername> 表示要执行的测试的标记名称。
使用以下代码更新我们的测试文件test_compare.py和test_square.py 。我们定义了 3 个标记——伟大、方形、其他。
测试比较.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
测试方.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
现在要运行标记为“其他”的测试,请运行以下命令 -
pytest -m others -v
请参阅下面的结果。它运行了标记为“其他”的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 ==========================
同样,我们也可以使用其他标记进行测试 - 太棒了,比较一下
Pytest - 装置
夹具是函数,它将在应用它的每个测试函数之前运行。Fixtures 用于向测试提供一些数据,例如数据库连接、要测试的 URL 和某种输入数据。因此,我们可以将固定功能附加到测试中,而不是为每个测试运行相同的代码,它会在执行每个测试之前运行并将数据返回到测试。
一个函数被标记为固定装置 -
@pytest.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的固定函数,它为测试提供输入。要访问固定装置功能,测试必须将固定装置名称作为输入参数。
Pytest 在执行测试时,会将夹具名称视为输入参数。然后执行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 ==========================
然而,这种方法有其自身的局限性。测试文件内定义的固定功能仅在测试文件内具有作用域。我们不能在另一个测试文件中使用该装置。为了使固定装置可用于多个测试文件,我们必须在名为 conftest.py 的文件中定义固定装置函数。conftest.py将在下一章中解释。
Pytest-Conftest.py
我们可以在此文件中定义夹具函数,以便可以跨多个测试文件访问它们。
创建一个新文件conftest.py并将以下代码添加到其中 -
import pytest @pytest.fixture def input_value(): input = 39 return input
编辑test_div_by_3_6.py以删除固定功能 -
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中定义的固定装置。
通过执行以下命令来运行测试 -
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 ==========================
测试将在同一文件中查找固定装置。由于在文件中找不到固定装置,它将检查 conftest.py 文件中的固定装置。找到它后,调用固定方法并将结果返回到测试的输入参数。
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 组输入,每组输入有 2 个值 - 一组是要与 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/跳过测试
在本章中,我们将了解 Pytest 中的 Skip 和 Xfail 测试。
现在,考虑以下情况 -
- 由于某些原因,测试在一段时间内不相关。
- 一项新功能正在实施,我们已经为该功能添加了测试。
在这些情况下,我们可以选择使测试失败或跳过测试。
Pytest 将执行 xfailed 测试,但不会被视为部分失败或通过的测试。即使测试失败,也不会打印这些测试的详细信息(请记住 pytest 通常会打印失败的测试详细信息)。我们可以使用以下标记来使测试失败 -
@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 <num>运行测试
pytest -n 3
-n <num> 使用多个worker运行测试,这里是3。
当只有几个测试需要运行时,我们不会有太多时间差异。然而,当测试套件很大时,这一点很重要。
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>
这里,标签<testsuit>总结了 4 次测试,失败次数为 1。
标签<testcase>给出每个已执行测试的详细信息。
<failure> 标签给出了失败的测试代码的详细信息。
Pytest - 总结
在这个 pytest 教程中,我们涵盖了以下领域 -
- 安装 pytest..
- 识别测试文件和测试函数。
- 使用 pytest –v 执行所有测试文件。
- 使用 pytest <filename> -v 执行特定文件。
- 通过子字符串匹配 pytest -k <substring> -v 来执行测试。
- 基于标记执行测试 pytest -m <marker_name> -v。
- 使用@pytest.fixture 创建装置。
- conftest.py 允许从多个文件访问装置。
- 使用@pytest.mark.parametrize 参数化测试。
- 使用 @pytest.mark.xfail 进行 Xfailing 测试。
- 使用@pytest.mark.skip 跳过测试。
- 使用 pytest --maxfail = <num> 在 n 次失败时停止测试执行。
- 使用 pytest -n <num> 并行运行测试。
- 使用 pytest -v --junitxml = "result.xml" 生成结果 xml。
Pytest - 结论
本教程向您介绍了 pytest 框架。现在您应该能够开始使用 pytest 编写测试。
作为一个好的做法 -
- 根据正在测试的功能/模块创建不同的测试文件。
- 为测试文件和方法提供有意义的名称。
- 有足够的标记来根据各种标准对测试进行分组。
- 需要时使用固定装置。