Pytest -初识

发布时间 2023-07-19 15:06:26作者: 乐乐乐乐乐乐樂

Pytest

Pytest是一个基于Python的测试框架,用于编写和执行测试代码。

Pytest的优点
  • Pytest可以并行运行多个测试,这减少了测试套件的执行时间
  • Pytest有自己的方法来自动监测测试文件和测试功能
  • Pytest允许我们运行整个测试套件的子集
  • Pytest允许我们在执行期间跳过测试的子集
  • Pytest免费开源
Pytest环境设置
pip install pytest  #安装最新版本
pytest -h	
识别测试文件和测试函数

在不提及文件名的情况下运行pytest将运行当前目录和子目录中所有格式为test_*.py。Pytest会自动将这些文件识别为测试文件。我们可以通过明确提及其它文件名来使pytest运行它们。

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 与 40 进行比较,这是错误的。最后我们可以看到测试执行总结,1个失败,1个通过。

函数 tesequality 不会被执行,因为 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.pytest_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-Fixtures

夹具是函数,它将在应用它的每个测试函数之前运行。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 的文件中定义固定装置函数。

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 将执行 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 运行测试

pytest -n 3

-n 使用多个worker运行测试,这里是3。

当只有几个测试需要运行时,我们不会有太多时间差异。然而,当测试套件很大时,这一点很重要。