Pytest 简明教程
Pytest - Introduction
Pytest 是一个基于 Python 的测试框架,用于编写和执行测试代码。在当下 REST 服务中,Pytest 主要用于 API 测试,但我们也可以使用 Pytest 编写简单到复杂的测试,即我们可以编写代码来测试 API、数据库、UI 等等。
Pytest - Environment Setup
在本章中,我们将学习如何安装 Pytest。
要开始安装,请执行以下命令:
pip install pytest == 2.9.1
我们可以安装任何版本的 Pytest。此处,2.9.1 是我们安装的版本。
要安装 pytest 的最新版本,执行以下命令:
pip install pytest
使用以下命令确认安装,以显示 pytest 的帮助部分。
pytest -h
Identifying Test files and Test Functions
在不提及文件名的情况下运行 pytest 将运行当前目录和子目录中所有格式为 test_ .py* 或 *_test.py 的文件。Pytest 会自动将这些文件识别为测试文件。我们 can 显式提及其他文件名来让 pytest 运行它们。
Pytest 要求测试函数的名称以 test 开头。函数名称不属于格式 test * 的不被 pytest 视为测试函数。我们 cannot 显式声明 pytest 将任何名称不以 test 开头的函数视作测试函数。
我们将在后续章节中了解测试的执行。
Pytest - Starting With Basic Test
现在,我们将从第一个 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
=================================
现在,结果更好地解释了失败的测试和通过的测试。
Note - pytest 命令将在当前目录和子目录中执行所有格式为 test_ 或 _test 的文件。
Pytest - File Execution
在本章中,我们将了解如何执行单个测试文件和多个测试文件。我们已经创建了测试文件 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
=================================
Execute a Subset of Test Suite
在真实场景中,我们将有多个测试文件,每个文件将包含许多测试。测试将涵盖各个模块和功能。假设我们只希望运行特定的测试集;我们应该如何进行?
Pytest 提供了两种方式来运行测试套件的子集。
-
根据测试名称的子字符串匹配来选择要运行的测试。
-
根据应用的标记来选择要运行的测试组。
我们将在后续章节中用示例解释这两个方面。
Substring Matching of Test Names
为了执行在其名称中包含字符串的测试,我们可以使用以下语法 -
pytest -k <substring> -v
-k <子字符串> 表示在测试名称中搜索的子字符串。
现在,运行以下命令 -
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 。
Note − 测试函数的名称仍应以“test”开头。
Pytest - Grouping the Tests
在本章中,我们将学习如何使用标记对测试进行分组。
Pytest 允许我们对测试函数使用标记。标记用于为测试函数设置各种特征/属性。Pytest 提供了许多内置标记,如 xfail、skip 和 parametrize。除此之外,用户还可以创建他们自己的标记名称。标记使用下面给出的语法应用于测试:
@pytest.mark.<markername>
要使用标记,我们必须在测试文件中 import 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
==========================
同样,我们也可以运行带有其他标记的测试——好,对比
Pytest - Fixtures
fixtures 是函数,它将在应用于它的每个测试函数之前运行。fixtures 用于向测试馈送一些数据,例如数据库连接、待测试的 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 - Parameterizing Tests
测试参数化用于针对多组输入运行测试。我们可以使用以下标记来执行此操作 -
@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/Skip Tests
在本章中,我们将学习 Pytest 中的 Skip 和 Xfail 测试。
现在,考虑以下情况 −
-
由于某些原因,一段时间的测试无关紧要。
-
正在实施一项新功能,我们已经为该功能添加了一个测试。
在这些情况下,我们可以选择永久失败测试或跳过测试。
Pytest 将执行永久失败的测试,但不会将其视为已失败或已通过的测试的一部分。即使测试失败,也不会打印这些测试的详细信息(请记住,pytest 通常会打印已失败的测试的详细信息)。我们可以使用以下标记永久失败测试 −
@pytest.mark.xfail
跳过测试表示不会执行该测试。我们可以使用以下标记跳过测试 −
@pytest.mark.skip
稍后,当测试变得相关时,我们可以删除这些标记。
编辑 test_compare.py ,其中我们已经可以包括永久失败和跳过标记 −
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 - Stop Test Suite after N Test Failures
在实际场景中,一旦新版本的代码已经准备好部署,它将首先部署到预生产/暂存环境。然后在此环境上运行测试套件。
仅当测试套件通过时,代码才有资格部署到生产环境。如果存在测试失败,无论是一个还是多个,代码都尚未准备好部署到生产环境。
因此,如果我们想在 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 - Run Tests in Parallel
默认情况下,pytest 按顺序运行测试。在实际情况下,测试套件将包含许多测试文件,每个文件都将包含一堆测试。这会导致较长的执行时间。为了克服此问题,pytest 为我们提供了一个并行运行测试的选项。
为此,我们需要先安装 pytest-xdist 插件。
通过运行以下命令安装 pytest-xdist -
pip install pytest-xdist
现在,我们可以使用以下语法运行测试 pytest -n <num>
pytest -n 3
-n <num> 使用多个工作线程运行测试,这里有 3 个。
当只有几个测试要运行时,我们不会有太大的时间差异。然而,当测试套件很大时,这一点很重要。
Test Execution Results in XML Format
我们可以将测试执行的详细信息生成在 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 - Summary
在 pytest 教程中,我们涵盖以下内容 -
-
Installing pytest..
-
识别测试文件和测试功能。
-
使用 pytest –v 执行所有测试文件。
-
使用 pytest <filename> -v 执行特定文件。
-
通过子字符串匹配 pytest -k <substring> -v 执行测试。
-
根据标记 pytest -m <marker_name> -v 执行测试。
-
Creating fixtures using @pytest.fixture.
-
conftest.py 允许从多个文件访问固定装置。
-
Parametrizing tests using @pytest.mark.parametrize.
-
Xfailing tests using @pytest.mark.xfail.
-
Skipping tests using @pytest.mark.skip.
-
使用 pytest --maxfail = <num> 在 n 个失败中停止测试执行。
-
使用 pytest -n <num> 以并行方式运行测试。
-
使用 pytest -v --junitxml = "result.xml" 生成结果 xml。