Web 自动化测试(基于Pytest极简)

Pytest 初体验

在使用 Python 进行 Web UI 自动化测试时,我们除了使用 unittest 单元测试框架,还可以使用 pytest,本节实验就给大家简单的介绍一下 pytest。

环境配置

本系列实验我们借助 VS Code 工具编写代码,使用的 Python 版本是 3.8,大家可以在命令行输入 python -V 查看 Python 版本。操作截图如下:

添加 Python 插件

下面需要大家手动在 VS Code 中添加 Python 插件,并且选择解释器,如此我们才可以在 VS Code 中直接运行 Python 代码。

  1. 启动 VS Code 程序。

  2. 安装 Python 插件。

在 VS Code 的应用商店(快捷键:Ctrl + Shift + X)里搜索 Python 插件并且插入。一般情况下,搜索出来的第一个就是我们需要的。截图如下:

插件安装后在编写 Python 脚本时便可使用代码自动补全功能。

  1. 选择 Python 解释器。

使用快捷键 Ctrl + Shift + P(或 F1),在打开的输入框中输入 Python: Select Interpreter 搜索,然后选择 /usr/bin/python 中的 Python3.8 解释器。截图如下所示:

至此,环境准备完成。

pytest 简介

pytest 是一个非常流行且成熟的,全功能的 Python 测试框架,适用于单元测试、UI 测试、接口测试。它和单元测试框架 unittest 类似,但是 pytest 更简洁、高效。所以很多测试人员学习 unittest 和 pytest 之后,都会感觉到 pytest 才是做测试的最好框架。这是因为 pytest 有许多优点。

pytest 主要优点如下:

  • 简单灵活,容易上手。
  • 支持参数化。
  • 可标记测试功能与属性。
  • pytest 具有很多第三方插件,并且可以自定义扩展,比较好用的如 pytest-selenium(集成 Selenium)、pytest-html(生成 HTML 测试报告)、pytest-rerunfailures(失败 case 重复执行)等。
  • 使用 skip 和 xfail 可以处理不成功的测试用例。
  • 可通过 xdist 插件分发测试到多个 CPU。
  • 允许直接使用 assert 进行断言,而不需要使用 self.assert *。
  • 方便在持续集成工具中使用。

接下来我们安装 pytest,打开 Xfce 终端,输入命令 sudo pip install pytest 然后回车,操作截图如下:

安装完成后,输入命令 pytest -V 或 pytest --version 检查 pytest 是否安装成功,操作截图如下:

如果出现 pytest version 等版本信息,则表示安装成功。

实例体验

我们在 /home/shiyanlou/ 下新建一个文件夹 test,然后使用 VS Code 打开 test 文件夹。再在 test 文件夹下新建一个 py 文件,并且命名为 test_example.py 。编写 test_example.py 内容如下:

import pytest


def add(a, b):
    return a + b

def test_add1():
    print("add(2, 3)的结果是:{}".format(add(2, 3)))
    assert add(2, 3) == 5

def test_add2():
    assert add(2, 3) == 6


if __name__ == "__main__":
    pytest.main()

提示:输入中文可使用搜狗输入法。虚拟环境中已经帮助大家安装好了搜狗输入法。可以双击桌面上的搜狗输入法图标激活搜狗输入法。然后右键点击虚拟环境右下角的键盘图标,而后选择搜狗输入法,便可以得到切换。

运行 pytest 标记的测试用例有两种方法,在 py 文件中添加 pytest.main() ,使用 VS Code 工具右上角的运行按钮执行。另一种方法是在命令行中使用命令运行,本次实验我们采用在命令行中运行。

打开 VS Code 的终端,通过命令 cd /home/shiyanlou/test/ 进入到 test 文件夹下,输入 pytest 后回车,操作截图如下:

从运行结果中可以看到一些信息:

  • 可以看到运行的平台,运行的 Python 版本,执行的根目录。
  • 收集的测试用例,collected 2 items 表示总共检测到两条测试用例。
  • 执行的测试文件及测试文件中测试用例的结果,其中 . 表示测试通过的用例,F 表示测试失败的用例。
  • [100%] 指运行所有测试用例的总体进度。
  • 如果测试用例运行不通过,则会显示具体的测试用例,并且标注出错的地方。
  • 在最后,会对运行的整体情况给出一个简单的统计。例如 1 failed, 1 passed in 0.09s。

注解:

我们在命令行中输入 pytest 后,会遵照下面几条标准去收集测试文件:

  • 如果未指定参数,则从 testpaths (如果配置)或当前目录收集。
  • 递归到目录文件中。
  • 搜索 test_*.py 或 *_test.py 文件。

收集到测试文件后,再从测试文件中收集测试用例。收集测试用例的标准是:

  • 不在类里面的,以 test 开头的函数或方法。
  • 以 Test 开头的类,在此类下面的,以 test 开头的函数或方法。但是类中不能有构造函数 __init__ 方法。

参数说明

在命令行中执行 pytest 命令时还可以添加一些参数,添加某些特定意义的事件,比如 -v、-q。

我们还是在 VS Code 的终端,通过命令 cd /home/shiyanlou/test/ 进入到 test 文件夹下,然后使用命令添加参数执行文件。

-v 参数

-v 参数用于查看测试的详细信息。

在命令行中执行命令 pytest -v ,操作截图如下:

测试结果是 test_example.py::test_add1 PASSED [ 50%]。比起不添加参数 -v 的输出结果更加详细,详细到每条测试用例的测试名字、结果、测试整体进度都会显示出来,而不仅仅再是一个结果标识。

如果需要在脚本中添加运行代码,则可写成 pytest.main(['-v']) 。添加其他参数运行也是类似的。

-q 参数

-q 与 -v 相反,可简化输出信息。

在命令行中执行命令 pytest -q ,操作截图如下:

通过查看运行结果可知,-q 参数简化了许多,只使用 . 和 F 标注出了测试用例运行后是成功的还是失败的。比直接使用命令 pytest 还简洁。

-s 参数

-s 是将我们在测试用例中的调式信息进行输出,比如 print 打印信息。

在命令行中执行命令 pytest -s ,操作截图如下:

从结果中可以看到,打印出了我们在函数 test_add1 中添加的打印信息。在test_example.py 后面输出了打印信息:add(2, 3)的结果是:5 。

其他参数

在前面给大家介绍了 -v、-q、-s 参数,在 pytest 还有其他好多参数可以使用,我们可以通过命令 pytest -h 查看帮助信息。使用命令 pytest -h 后,部分内容信息截图如下:

通过帮助信息可以知道 pytest 的使用语法和所有能够使用的参数。我们可以看到 pytest 命令语法是:

pytest [options] [file_or_dir] [file_or_dir] [...]

即 pytest 后面接固定参数,然后接文件目录或文件。

下面给大家列出一些比较常见的参数:

参数 含义 示例
-l 由于失败的测试用例会被堆栈追踪,所以所有的局部变量及其值都会显示出来 pytest -l
-k 模糊匹配时使用 pytest -k
-m 标记测试并且分组,运行时可以快速选择分组并且运行 pytest -m
-x pytest 运行时遇到失败的测试用例后会终止运行 pytest -x
--collect-only 显示要执行的用例,但是不会执行 pytest --collect-only
--ff 也可以写成 --failed-first ,先执行上次失败的测试,然后执行上次正常的测试 pytest --ff
--lf 也可以写成 --last-failed ,只执行上次失败的测试 pytest --lf
--setup-show 用于查看具体的 setup 和 teardown 顺序 pytest --setup-show
--sw 也可以写成 --stepwise ,测试失败时退出并从上次失败的测试继续下一次 pytest --sw
--junit-xml=path 在指定路径创建 JUnit XML 样式的报告文件 pytest --junit-xml=path
--color=color 终端信息彩色输出(是/否/自动),可选值 yes、no、auto pytest --color=color

Pytest之mark标记

pytest.mark 是用来对测试方法进行标记的一个装饰器,主要作用是在执行过程中对标记的测试用例进行判断且选择性的执行。本次实验会为大家详细介绍 mark 标记的使用。

标记测试函数

mark 装饰器使用起来非常方便。在使用之前我们先来看看可以怎么使用。在命令行模式下执行命令 pytest --markers 查看官方提供的 mark 说明,操作截图如下:

下面对 mark 标记做以下说明:

  1. @pytest.mark.filterwarnings(warning) :在标记的测试方法上添加警告过滤。
  2. @pytest.mark.skip(reason=None) :执行时跳过标记的测试方法,reason 为跳过的原因,默认为空。
  3. @pytest.mark.skipif(condition) :通过条件判断是否跳过标记的测试方法。如果 condition 的判断结果为真则跳过,否则不跳过。
  4. @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False) : 如果条件 condition 的值为 True,则将测试的预期结果标记为 False。
  5. @pytest.mark.parametrize(argnames, argvalues) :测试函数参数化,即调用多次测试函数,依次传递不同的参数。 如果 argnames 只有一个名称,则 argvalues 需要以列表的形式给出值;如果 argnames 有多个名称,则 argvalues 需要以列表嵌套元组或列表的形式给出值。如果 parametrize 的参数名称和 fixture 名称一样,会覆盖掉 fixture。例如:@parametrize('arg1', [1,2]) 将对测试函数调用两次,第一次调用 arg1 = 1,第二次调用 arg1 = 2。@parametrize('arg1, arg2', [(1,2), (3,4)]) 将对测试函数调用两次,第一次调用 arg1 = 1, arg2 = 2;第二次调用 arg1 = 3, arg2 = 4。
  6. @pytest.mark.usefixtures(fixturename1, fixturename2, ...) :将测试用例标记为需要指定的所有 fixture。和直接使用 fixture 的效果是一样的,只不过不需要把 fixture 名称作为参数放置在方法声明当中,并且可以使用 class( fixture 暂时不能用于 class )。
  7. @pytest.mark.tryfirst :标记一个挂钩实现函数,使所标记的测试方法可以首先或尽早执行。在实际情况中,如果有 fixture 的 parametrize,测试方法执行的顺序会比较复杂。
  8. @pytest.mark.trylast :标记一个挂钩实现函数,使所标记的测试方法可以最后或尽可能晚执行。和 tryfirst 相反。

接下来会为大家详细介绍最常用的 skip、skipif、xfail、parametrize 装饰器。

skip

如果在测试用例函数之上添加装饰器 @pytest.mark.skip(reason=None) ,则在执行中遇到该测试用例函数会跳过不执行。reason 是方便我们在代码中做说明,为什么要跳过,或者跳过的原因是什么,但是 reason 对结果是没有任何影响的。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_skip.py ,然后编写如下内容:

import pytest


@pytest.mark.skip()
def test_skip1():
    assert 1 == 1

@pytest.mark.skip(reason="跳过该条测试用例")
def test_skip2():
    assert 1 == 1


if __name__ == "__main__":
    pytest.main(['-v'])

我们定义了两个测试用例函数 test_skip1 和 test_skip2 ,并且都添加了 @pytest.mark.skip 装饰器,其中函数 test_skip2 的装饰器中添加了跳过的原因。

下面我们通过 VS Code 工具右上角的执行按钮执行文件,执行后截图如下所示:

从结果中可以看到,两条测试用例的结果都被标示为 SKIPPED。SKIPPED 代表的就是跳过。

skipif

skipif 是对事物进行判断,从而决定是否跳过测试用例函数。如果满足条件则跳过,否则执行。

在做 skipif 实验之前,我们先来查看系统信息,在命令行中依次执行如下语句,可获取当前环境是什么系统,代码如下:

python
import sys
sys.platform

操作截图如下:

从结果中可知当前系统是 linux。

下面我们进行 skipif 实验。通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_skipif.py ,然后编写如下内容:

import pytest
@pytest.mark.skipif('sys.platform == "linux"',reason="不适合在 linux 系统运行")
def test_skipif1():
    assert 1 == 1

@pytest.mark.skipif('sys.platform != "linux"')
def test_skipif2():
    assert 1 == 2

if __name__ == "__main__":
    pytest.main(['-v'])

我们定义了两个测试用例函数 test_skipif1 和 test_skipif2 。在 test_skipif1 函数上添加 skipif 判断条件装饰器,如果当前系统是 linux 则结果记为 True。在 test_skipif2 函数上也添加 skipif 判断条件装饰器,如果当前系统不是 linux 系统则结果记为 True。只有判断条件为 True 时才会跳过对应的测试用例函数。

我们使用 VS Code 工具执行脚本,结果截图如下所示:

由于我们当前系统是 linux,所以 test_skipif1 函数上添加的装饰器条件判断结果是 True,因此跳过了该测试用例函数。 test_skipif2 函数上添加的装饰器条件判断结果是 False,所以执行了。

xfail

xfail 表示的是可以预期到结果是失败的测试。xfail 装饰器解决的是对于很清楚知道它的结果是失败的,但是又不想直接跳过的测试方法,通过添加 xfail 装饰器可以在结果中给出明显的标识。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_xfail.py ,然后编写如下内容:

import pytest


@pytest.mark.xfail(reason="0 不能做除数")
def test_xfail1():
    assert 2 / 0 == 1

@pytest.mark.xfail
def test_xfail2():
    assert 2 / 1 == 2


if __name__ == "__main__":
    pytest.main(['-v'])

我们定义了两个测试用例函数 test_xfail1 和 test_xfail2 ,并且都添加了装饰器 xfail,其中 test_xfail1 函数的断言是失败的,test_xfail2 函数的断言是正确的。

使用 VS Code 工具执行脚本,结果截图如下所示:

从结果中可以得知,测试用例函数 test_xfail1 的结果是 XFAIL,test_xfail2 函数的结果是 XPASS。也就是说,使用 xfail 装饰器标记的测试用例函数并不会跳过,还是会继续执行的,执行的结果也会正常显示,只不过会在执行的结果前会添加一个 X 做出标识。

parametrize

parametrize 用于对测试方法进行数据参数化,使得同一个测试方法可以结合不同的测试数据进行测试。

单个参数

如果被装饰的函数参数只有一个,则需要以列表的形式给出值。

我们通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_parametrize1.py,然后编写如下内容:

import pytest


list = [1, 2, 3]

@pytest.mark.parametrize('num', list)
def test_parametrize(num):
    assert num in list


if __name__ == "__main__":
    pytest.main(['-v'])

定义一个列表 list 和一个测试用例函数 test_ parametrize 。并且在 test_ parametrize 函数上添加装饰器 @pytest.mark.parametrize('num', list) ,在 pytest 执行时就会遍历 list 中的元素并且依次作为参数传入 test_parametrize 。

使用 VS Code 工具执行脚本,结果截图如下所示:

通过测试结果我们看到 test_parametrize1.py 文件后出现了 3 条测试成功的标识,并且测试用例函数 test_parametrize 出现的三次都带有参数值,与列表 list 中的元素一致。

多个参数

如果被装饰的函数有多个参数,则需要以列表嵌套元组或列表的形式给出值。当然以元祖套元祖的形式也是允许的。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_parametrize2.py ,然后编写如下内容:

import pytest


list = [(1, 2, 3), (2, 3, 5), (3, 4, 7)]

@pytest.mark.parametrize('num1, num2, sum', list)
def test_parametrize(num1, num2, sum):
    assert num1 + num2 == sum


if __name__ == "__main__":
    pytest.main(['-v'])

定义了一个列表 list = [(1, 2, 3), (2, 3, 5), (3, 4, 7)] 和一个测试用例函数 test_ parametrize ,在 test_ parametrize 函数前添加装饰器 @pytest.mark.parametrize('num1, num2, sum', list) , 测试用例函数 test_ parametrize 断言是第一个参数和第二个参数之和等于第三个参数。

使用 VS Code 工具执行脚本,结果截图如下所示:

通过测试结果我们看到 test_parametrize2.py 文件后出现了 3 条测试成功的标识,并且测试函数 test_parametrize 出现的三次都带有参数值,与列表 list 中的元祖元素一致。

直接标记

mark 标记是通过在测试方法上添加装饰器进行标记,还有一种标记方法是在命令行中使用参数进行标记。

使用参数进行标记的方法有两种:一种是直接标记,即只运行某个固定的 py 文件,或在 py 文件后通过添加双引号 (::) 运行固定的测试方法;另一种是通过参数 -k 进行模糊匹配标记。

运行指定 py 文件

打开 VS Code 工具的终端,使用命令 cd /home/shiyanlou/test/ 进入 test 文件夹下。

接着我们使用命令 pytest -v test_parametrize2.py 运行指定的 py 文件 test_parametrize2.py 。操作后结果截图如下所示:

可以看到只运行了 test_parametrize2.py 文件中测试用例函数 test_parametrize 的三条测试用例。

运行指定测试用例

进入 VS Code 工具的终端,在 /home/shiyanlou/test/ 文件夹下使用命令 pytest -v test_xfail.py::test_xfail2 运行文件 test_xfail.py 中的测试用例函数 test_xfail2 。操作截图如下所示:

从结果中可以看到只运行了 test_xfail.py 文件中的 test_xfail2 测试用例函数。

如果一次需要执行多个测试用例函数,则可写成如下语句的形式:

pytest C:\test_1.py::test_1 C:\test_2.py::test_2

模糊匹配

在直接标记中一次只能执行指定的测试文件或测试函数。如果某些测试用例有共同的特点,我们可使用模糊匹配标记,一次执行多个具有共同特点的测试用例函数。模糊匹配标记使用很简单,在使用 pytest 运行时添加参数 -k ,后面添加匹配的字符串即可进行匹配。

模糊匹配可以匹配测试 py 文件,也可匹配测试函数。

下面我们匹配测试用例函数名中含有 parametrize 字符串的文件和函数。

进入 VS Code 工具的终端,在 /home/shiyanlou/test/ 文件夹下执行命令 pytest -v -k parametrize 。操作截图如下所示:

从结果中可以看到,匹配到了 test_parametrize1.py 和 test_parametrize2.py 两个文件中的六条测试用例。

自定义标记

我们可以使用 mark 进行自定义标记,只需在测试方法前添加装饰器 pytest.mark.标记名 即可。标记名建议根据项目取比较容易识别的词,例如:commit、merger、done、undo 等。

使用时,只需要通过参数 -m 加上标记名就可执行被标记的测试用例函数。

我们通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_customize.py,然后编写如下内容:

import pytest


@pytest.mark.done
def test_customize_done():
    assert 1 == 1

@pytest.mark.undo
def test_customize_undo1():
    assert 1 == 1

@pytest.mark.undo
def test_customize_undo2():
    assert 1 == 1


if __name__ == "__main__":
    pytest.main(['-v'])

定义了三个测试用例函数 test_customize_done、test_customize_undo1 和 test_customize_undo2 。其中 test_customize_done 函数添加装饰器 @pytest.mark.done ,test_customize_undo1 和 test_customize_undo2 添加装饰器 @pytest.mark.undo 。

进入 VS Code 工具的终端,在 /home/shiyanlou/test/ 文件夹下执行命令 pytest -v -m "undo" 。操作截图如下所示:

从结果中可以看到,只执行了 test_customize.py 文件中被装饰器 @pytest.mark.undo 标记的两条测试用例函数。

细心的同学已经发现在结果中弹出了警告(由于截图内容有限,上面的截图中未能显示出警告提示),我们可以通过添加参数 --disable-warnings 禁用。但是一般情况下不建议禁用,因为某些警告可能会转换成错误。

Pytest 之 fixture 固件使用

fixture 是 pytest 中非常重要的一个内容,用于测试用例函数在执行前的数据准备、环境搭建和执行后的数据销毁、环境恢复等工作。与单元测试框架 unittest 中的 setup、teardown 功能类似,但是 pytest 中提供的 fixture 更方便使用,灵活度也更高。还允许代码运行时在测试用例和测试用例之间传递参数和数据。本次实验将会带大家熟悉 fixture 的功能。

fixture 的使用

如果我们在写自动化脚本时需要将一个函数当作 fixture 使用,则只需要在该函数上面添加装饰器 @pytest.fixture() 便可。

实例体验

下面我们来写一个简单的示例进行体验一下。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_fixture.py,编写如下内容:

import pytest
@pytest.fixture()
def fixture_demo():
    print("这是一个 fixture 示例,在测试用例函数之前执行")

def test_fixture1(fixture_demo):
    print("fixture 示例中第一个测试用例函数")

def test_fixture2(fixture_demo):
    print("fixture 示例中第二个测试用例函数")


if __name__ == "__main__":
    pytest.main(['-v', '-s', 'test_fixture.py'])

我们定义了一个函数 fixture_demo ,并且添加了装饰器 @pytest.fixture() ,使其可以作为 fixture 使用。然后写了两个测试用例函数 test_fixture1 和 test_fixture1 ,并且将 fixture_demo 以参数的形式传入,此时 fixture_demo 就与两个测试用例函数形成了关联。

使用语句 pytest.main(['-v', '-s', 'test_fixture.py']) 执行测试脚本,表示只运行 test_fixture.py 文件, 参数 -v 和 -s ,我们在第一节实验中已经使用过了,就不再做解释。

然后点击 VS Code 工具右上角的执行按钮执行文件,截图如下所示:

从结果中可以看到,两个测试用例函数 test_fixture1 和 test_fixture1 在执行前都执行了 fixture 函数 fixture_demo 。也就是说在每条测试用例函数执行前都执行一次。在此大家就会体会到,和 unittest 单元测试框架中 setup 具有一样的效果。

fixture 定义

下面我们来看下源码中 fixture 的定义。

源码中 fixture 的定义是 fixture(scope="function", params=None, autouse=False, ids=None, name=None) 。我们可以看到有 scope、params、autouse、ids、name 共 5 个参数可以使用。下面对这 5 个参数做简单的解释:

  • scope:定义 fixture 作用域,有 4 个可选值,分别是 function、class、module、package/session ,默认是 function。在下面小节 fixture 作用域中会详细作出说明。
  • params:参数化,会使用多个参数调用 fixture 函数。在下面小节 fixture 参数化中会详细作出说明。
  • autouse:布尔类型参数,如果是 True ,则所有测试用例函数都会执行此固件函数;如果是 False ,则只会对添加了固件函数的测试用例函数使用。默认值是 False 。
  • ids:每个参数都与列表中的字符串 id 对应,因此它们是测试 id 的一部分。如果没有提供 id 将会从参数中自动生成。
  • name:fixture 的名称,默认为装饰器的名称。如果 fixture 在它定义的模块中使用,那么这个 fixture 功能名称就会被请求的 fixture 功能参数遮盖。我们就可以通过将装饰函数命名为 fixture_ <fixturename> ,然后使用 @pytest.fixture(name ='<fixturename>') 解决这个问题。

fixture 作用域

fixture 作用域是用来指定固件的使用范围,通过参数 scope 可声明作用范围。scope 参数有 4 个可选值,分别是 function、class、module、package/session,下面对这 4 个可选项做简单的说明:

  • function:函数级别,也是默认值。意思是在每个测试用例函数执行前都会执行一次。
  • class:类级别,每个测试类执行前执行一次。
  • module:模块级别,每个模块执行前执行一次,也就是每个 .py 文件执行前都会执行一次。
  • package/session:会话级别,一次测试只执行一次,即多个文件调用一次,可以跨 .py 文件。

下面我们来做一个小示例进行说明。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_fixture_scope.py,编写如下内容:

import pytest
@pytest.fixture(scope='session')
def fixture_session():
    print("session 级别的 fixture")

@pytest.fixture(scope='module')
def fixture_module():
    print("module 级别的 fixture")

@pytest.fixture(scope='class')
def fixture_class():
    print("class 级别的 fixture")

@pytest.fixture(scope='function')
def fixture_function():
    print("function 级别的 fixture")

def test_scope(fixture_session, fixture_module, fixture_class, fixture_function):
    print("fixture scope 示例中的测试用例函数")

if __name__ == "__main__":
    pytest.main(['-v', '--setup-show', 'test_fixture_scope.py'])

我们定义了四个不同级别的固件函数 fixture_session、fixture_module、fixture_class、fixture_function 。然后写了一个测试用例函数 test_scope ,并且将四个固件函数都以参数的形式传入到测试用例函数。

然后点击 VS Code 工具右上角的执行按钮执行文件,截图如下所示:

添加参数 --setup-show(查看具体的 setup 和 teardown 顺序)运行脚本,可以清楚地看到各个固件的作用域和执行顺序。

从结果中可以看到,添加了不同作用域的四个 fixture 函数,执行时有明显的先后顺序。我们也可以通过 S、M、C、F 等标识来知道 fixture 的作用范围。

  • S:表示作用范围最大的 fixture,session 会话级。
  • M:表示 module 模块级。
  • C:表示 class 类级别的 fixture。
  • F:表示 function 函数级。

mark.usefixtures

在上面的示例体验中,我们可以看到,如果某个测试用例函数需要使用 fixture ,则只需要将指定的 fixture 以参数的形式传入到测试用例函数中即可。但是,如果我们有一个测试类,测试类中有很多测试用例方法,这些测试用例方法都需要使用某个固定的 fixture ,那么每个测试用例方法都以参数的形式传入 fixture ,是不是觉的很累?很不舒服。

这个时候我们就可以选择在 class 类上添加装饰器 @pytest.mark.usefixtures('fixture_name') ,使整个 class 类都使用 fixture。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_fixture_mark_use.py,然后编写如下内容:

import pytest


@pytest.fixture(scope='function')
def fixture_mark_use():
    pass


@pytest.mark.usefixtures('fixture_mark_use')
class TestFixture():
    def test_mark_use1(self):
        pass

    def test_mark_use2(self):
        pass


if __name__ == "__main__":
    pytest.main(['-v', '--setup-show', 'test_fixture_mark_use.py'])

我们定义了一个固件函数 fixture_mark_use ,并将作用级别设置为 function 。然后写了一个测试类 TestFixture ,测试类中又添加了两个测试用例方法 test_mark_use1 和 test_mark_use2 。测试类上添加了装饰器 @pytest.mark.usefixtures('fixture_mark_use') ,使 fixture 函数 fixture_mark_use 作用于整个测试类。

然后点击 VS Code 工具右上角的执行按钮执行文件,截图如下所示:


从结果中可以看到,测试类下的每个测试用例方法在执行前都先执行了 fixture_mark_use 函数。

autouse

我们在测试用例函数中通过以参数的形式使用 fixture 固件。但是大家有没有想过,当测试用例函数特别多时,每次都以参数的形式传入会不会显得有些麻烦呢?当然有同学可能会想到使用 mark.usefixtures 作用在类上进行解决,当然是可以的。但是如果我们的测试用例函数都不在类中呢?又该怎样解决。

其实在将具体的函数标记为 fixture 时,我们就可以通过将参数 autouse 设置为 True 来达到自动将测试固件添加到测试用例函数上。

参数 autouse 的类型是布尔类型,默认值是 Fasle,不启用。如果设置为 True 则开启自动使用 fixture 功能,如此每次使用时不需要当作参数传入也可使用测试固件。

下面我们来做一个小示例进行说明。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_fixture_autouse.py ,编写如下内容:

import pytest


@pytest.fixture(autouse=True)
def fixture_autouse():
    print("\n 自动使用 fixture")

def test_fixture_autouse():
    print("fixture autouse 示例中的测试用例函数")

class TestFixture():
    def test_autouse(self):
        print("fixture autouse 示例中的测试用例方法")


if __name__ == "__main__":
    pytest.main(['-v', '-s', '--setup-show', 'test_fixture_autouse.py'])

我们定义了一个固件函数 fixture_autouse ,并且将参数 autouse 设置为 True 。接着写了一个测试用例函数 test_fixture_autouse 和一个测试类 TestFixture ,测试类下添加了一个测试用例方法 test_autouse 。

在 pytest.main() 中添加参数 -v、-s 和 –-setup-show。然后点击 VS Code 工具右上角的执行按钮运行 test_fixture_autouse.py 脚本,截图如下所示:

从结果中可以看到,无论是测试用例函数还是测试用例方法,在执行前都先执行了 fixture_autouse 函数。也就是说只要将参数 autouse 设置为 True ,则在运行脚本中就会自动启用 fixture 。

参数化

在第二次实验 mark 标记中,我们已经使用过装饰器 @pytest.mark.parametrize() 对参数化有过讲解。本次我们将使用固件中的参数 params 实现参数化。

在测试中如果我们需要使用不同的参数和基本相同逻辑来构造环境或者结果稍微有所不同的场景,这个时候就可以利用 fixture 的参数化(parametrizing)来实现。

下面我们来做一个小示例进行说明。

通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_fixture_params.py ,编写如下内容:

import pytest
@pytest.fixture(params=[
    (1, 2, 3),
    (2, 3, 5),
    (3, 4, 7)
])
def fixture_params(request):
    return request.param

def test_add(fixture_params):
    assert fixture_params[2] == fixture_params[0] + fixture_params[1]

if __name__ == "__main__":
    pytest.main(['-v', 'test_fixture_params.py'])

我们对 fixture_params 函数添加 fixture 时,在参数 params 添加了一个数组,数组中有三个元祖类型的数据。然后写了一个测试用例函数 test_add,并且将 fixture_params 函数以参数的形式传入。

代码在执行时 fixture 参数化会使用 pytest 内置的固件 request,并通过 request.param 来获取参数。

然后点击 VS Code 工具右上角的执行按钮执行文件,截图如下所示:

从结果中可以看到,测试用例函数总共运行了 3 次,与我们添加的 3 组测试数据数量相同。

Pytest 之 fixture 固件功能

本次实验将会为大家介绍 fixture 更多的一些用法。带领大家结合关键词 yield 设置后置处理,多个文件共享相同的 fixture,以及内置 fixture 三个方面继续学习 fixture 固件。

yield 的使用

在上节实验 autouse 的讲解中,大家有没有发现一个问题,在测试结果显示的测试脚本中的 print() 内容都是在测试用例函数之前,测试用例函数执行之后就没有内容打印。那么,如果需要在测试用例执行之后有所操作该怎么办呢?

现在我们就来解决这个问题。

在 pytest 中使用 fixture,如果想要在测试用例函数执行之后进行某些操作,就需要关键字 yield 配合,如果没有 yield 就相当于只有 setup 而没有 teardown。如果在函数中添加了 yield,则 yield 之后的内容就相当是 teardown。我们在写测试脚本时,通常会使用关键字 yield, 这样就可以将同一组的准备、销毁工作写在一起,理解起来更清晰。

接下来通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_fixture_yield.py ,编写如下内容:

import pytest
@pytest.fixture()
def fixture_yield():
    print("\n 测试用例函数执行前操作")
    yield
    print("\n 测试用例函数执行后操作")

def test_fixture_yield1(fixture_yield):
    print("fixture yield 示例中的第一个测试用例函数")

def test_fixture_yield2(fixture_yield):
    print("fixture yield 示例中的第二个测试用例函数")


if __name__ == "__main__":
    pytest.main(['-v', '-s', 'test_fixture_yield.py'])

上面的代码比较简单,相信经过前面的 fixture 的学习不难理解。在此就不再做说明了。

然后点击 VS Code 工具右上角的执行按钮执行文件,截图如下所示:

从结果中可以看到,两个测试用例函数 test_fixture_yield1 和 test_fixture_yield2 在执行前都运行了固件函数 fixture_yield 中关键字 yield 之前的语句。两个测试用例函数在执行后都运行了固件函数中关键字 yield 之后的语句。

注意:

  • 如果测试用例中的代码出现异常或者断言失败,并不会影响固件函数中关键字 yield 后面代码的执行。
  • 如果固件函数中 yield 之前的代码出现异常,那么测试方法不会继续执行,关键字 yield 后面的代码也不会再执行。
  • yield 只是一个关键字,后面或前面的代码执行范围取决于 fixture 装饰器给出的作用域。

共享 fixture 功能

在 pytest 框架下提供了一个共享 fixture 的功能。意思是我们只需要创建一个名为 conftest.py 的文件,然后将需要共享的功能写在 conftest.py 文件里面,在脚本运行时其他测试文件就会自动查找使用。

在使用 conftest.py 文件时需要注意以下几点:

  • conftest.py 文件名称需固定,不能更改。
  • conftest.py 需要与运行的测试用例文件在同一个 pakage 下。
  • 使用 conftest.py 时不需要 import 导入,pytest 会自动识别。
  • 如果 conftest.py 放在项目的根目录下,则对全局生效。如果放在某个 package 下则只对 package 下的用例文件生效。
  • 允许存在多个 conftest.py 文件。
  • conftest.py 文件不能被其他文件导入。
  • 所有同目录测试文件运行前都会执行 conftest.py 文件。

接下来通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件夹 FileConftest ,然后在 FileConftest 下创建一个 __init__.py 文件、一个 conftest.py 文件和两个测试文件 test_file1.py 、test_file2.py 。

创建后的文件目录如下截图所示:

编写 /home/shiyanlou/test/FileConftest/conftest.py 文件内容如下:

import pytest
@pytest.fixture
def fixture_conftest():
    print("\n 测试用例函数开始执行")
    yield
    print("\n 测试用例函数结束执行")

我们定义了一个固件函数 fixture_conftest ,使测试用例函数在执行前打印“测试用例函数开始执行”内容,测试用例函数在执行后打印“测试用例函数结束执行”内容。

在 /home/shiyanlou/test/FileConftest/test_file1.py 文件中定义一个测试用例函数 test_conftest1 ,内容如下:    

import pytest
def test_conftest1(fixture_conftest):
    print("fixture conftest 示例中的第一个测试用例函数")

在 /home/shiyanlou/test/FileConftest/test_file2.py 文件中定义两个测试用例函数 test_conftest2 和 test_conftest3 ,内容如下:

import pytest
def test_conftest2(fixture_conftest):
    print("fixture conftest 示例中的第二个测试用例函数")

def test_conftest3(fixture_conftest):
    print("fixture conftest 示例中的第三个测试用例函数")

打开 VS Code 的终端,通过命令 cd /home/shiyanlou/test/FileConftest 进入到 FileConftest 文件夹下,输入 pytest -v -s 运行脚本,操作截图如下所示:

从结果中可以看到,所有的测试用例函数在运行前和运行后都执行共享文件 /home/shiyanlou/test/FileConftest/conftest.py 中的 fixture 函数 fixture_conftest 。因为 conftest.py 文件中函数 fixture_conftest 作用域是 function 级别的,所以每个测试用例函数都会使用。如果想要在程序运行过程中只运行一次,那么只需要将 fixture 作用域修改为会话 session 级别即可。

内置 fixture

上面我们学习了 pytest 中固件的使用。其实在 pytest 测试框架中已经内置了许多 fixture ,我们可以直接使用,不需要再单独编写代码。使用内置的 fixture 可以大幅简化测试工作,提高工作效率。

打开命令行工具,使用命令 pytest --fixtures 或 pytest --funcargs 查看所有可用的 fixture,包括内置的、插件中的以及当前项目定义的,操作截图如下所示:

对截图中部分插件做以下说明:

  1. pytestconfig:使用内置固件 pytestconfig 能够方便地读取命令行参数和配置文件,可以通过命令行参数、选项、配置文件、插件、运行目录等方式来控制 pytest。准确地说,pytestconfig 是 request.config 的快捷方式,在 pytest 文档中也被称为“ pytest 配置对象”。

  2. capsys:用于捕获 stdout 和 stderr 的内容,并临时关闭系统输出。

  3. recwarn:用于检查待测代码产生的警告信息,有两种使用方法,一种是当作参数传入被测方法中,例如 test_warn(recwarn) ;另一种是当作方法使用 pytest.warns() 。

  4. monkeypatch:程序在运行时动态修改类或模块,测试结束后无论结果成功或失败代码都会还原,不会影响下次运行。

  5. tmp_path:在临时目录的根目录中创建一个独立的临时目录,方便测试时使用。默认情况下,临时目录创建为系统临时目录的子目录。

  6. tmpdir:用于临时文件和目录的管理,在测试开始前创建临时文件目录,并在测试结束后进行销毁。作用范围是在函数级别。

  7. tmpdir_factory:与 tmpdir 使用和作用是一样的,只不过作用范围是在会话级别。

  8. cache:能够存储一段测试会话的信息并且在下一段测试会话中使用。

固件 tmpdir

tmpdir 用于临时文件和目录的管理,在测试开始前创建临时文件目录,并在测试结束后进行销毁。适用于在测试过程中创建一个临时文件,并且对文件进行读写操作的场景。

接下来通过 VS Code 工具在 /home/shiyanlou/test/ 下新建文件 test_fixture_tmpdir.py ,编写如下内容:

import pytest
def test_tmpdir(tmpdir):
    # 创建临时目录
    tmp_dir = tmpdir.mkdir('testdir')
    # 创建临时 txt 文件
    tmp_file = tmp_dir.join('tmpfile.txt')
    # 对临时文件写入内容
    tmp_file.write("这是一个临时文件")
    # 对写入临时文件中的内容进行验证
    assert tmp_file.read() == "这是一个临时文件"

if __name__ == "__main__":
    pytest.main(['-v', 'test_fixture_tmpdir.py'])

在 tmpdir 中使用 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值