Web 自动化测试全面提升之 Pytest

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 中使用 tmpdir.mkdir() 可创建临时目录,使用 tmpdir.join() 可创建临时文件。

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

从结果中可以看到测试用例是通过的,断言是成功的,显然临时目录和临时文件是创建成功的,并且可以对创建的文件进行读写操作。

下面给大家再简单的介绍一下 tmpdir_factory。

tmpdir_factory 与 tmpdir 使用和作用是一样的,但是 tmpdir 的作用范围是函数级别的,而 tmpdir_factory 的作用范围是会话级别的,在 session、module、class 和 function 中都可以使用。

使用也很简单,给大家写一个简单的示例,代码如下:

import pytest
@pytest.fixture(scope='module')
def fixture_tmpdir_factory(tmpdir_factory):
    # 创建临时目录
    tmp_dir = tmpdir_factory.mktemp('testdir')
    # 创建临时 txt 文件
    tmp_file = tmp_dir.join('tmpfile.txt')
    # 对临时文件写入内容
    tmp_file.write("这是一个临时文件")

def test_tmpdir_factory(fixture_tmpdir_factory):
    pass

固件 cache

在自动化测试中,我们有时候会希望每个测试的会话可以重复,不会因为上一段会话的运行影响到下一段的测试行为。对于这种将上一段会话信息传递给下一段的工作可以使用 pytest 框架内置的 cache 固件,该固件能够存储一段测试会话的信息并且在下一段测试会话中使用。

先给大家介绍一下 cache 固件中一些命令参数,然后再练习。

cache 固件中经常使用的命令参数有以下几个:

  • --lf:也可以写成 --last-failed,只执行上次失败的测试,如果没有失败的测试用例则全部执行。
  • --ff:也可以写成 --failed-first,先执行上次失败的测试,然后执行上次正常的测试。执行过程中可能会对测试进行重置,从而导致重复执行 fixture。
  • --cache-show:显示缓存内容,不执行收集或测试。
  • --cache-clear:在测试运行开始时删除所有缓存的内容。

实例演示

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

import pytest

def test_cache1():
    assert 1 == 1

def test_cache2():
    assert 0

def test_cache3():
    assert True

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

上面我们写了三个测试用例函数,其中 test_cache2 函数断言是失败的,test_cache1 和 test_cache3 是成功的。

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

查看缓存

脚本执行完成后,使用命令 pytest --cache-show 查看缓存的内容,操作截图如下所示:

从结果中可以看到缓存的内容,上次执行失败的是 test_cache.py 文件中的 test_cache2 测试用例函数。

细心的同学可能已经发现了,我们每次运行脚本后都会在脚本所在的文件下生成一个 .pytest_cache 文件夹,这里面存放的也是缓存文件。截图如下所示:

例如 /home/shiyanlou/test/FileCache/.pytest_cache/v/cache 下的 lastfailed 文件,就是专门存放上次执行失败的测试用例。其内容如下:
{
  "test_fixture_cache.py::test_cache2": true
}

根据缓存执行脚本

接下来我们使用 --lf 参数只执行上次失败的测试用例函数。在 VS Code 的终端中进入 /home/shiyanlou/test/FileCache 文件夹下,输入命令 pytest -v --lf 后回车,操作截图如下所示:

结果很明显,只执行了上次失败的测试用例函数 test_cache2 。

Pytest 之插件

pytest 测试框架是非常受大家喜欢的一个框架,不只是因为它拥有非常灵活地测试固件 fixture,还是因为它拥有非常强大的插件生态系统,所以有时候也称 pytest 是一个插件化的测试平台。本次实验将会给大家简单的介绍插件,并且会实例体验 Allure 插件。

插件简介

在 pytest 插件生态系统中,有许多非常强有力的插件。我们可以使用不同的插件满足测试过程中的一些特殊需求,这就使得 pytest 拥有了更多的便利与可能性。

在 pytest Plugin Compatibility 的网站中可以查看针对不同 pytest 和 Python 版本的几乎所有的插件列表。

如下截图中就是 pytest Plugin Compatibility 网站列出的部分插件状态:

下面给大家列举一些非常实用的插件:

  • pytest-sugar:改变 pytest 的默认外观,增加了一个进度条,并立即显示失败的测试。使用非常方便,只要安装了 pytest-sugar 插件并且使用 pytest 运行测试就可获得更漂亮、更有用的输出。
  • pytest-xdist:允许开启多个 worker 进程,同时执行多个测试用例,达到并发运行的效果,提升了构建效率。
  • pytest-allure-adaptor:生成漂亮的 Allure 报告,在持续集成中推荐使用。
  • pytest-instafail:在测试运行期间报告失败。修改 pytest 默认行为,将失败和错误的代码立即显示,改变了 pytest 需要完成每个测试后才显示的行为。
  • pytest-rerunfailures:失败用例重跑,是个非常实用的插件。如果对失败的测试用例进行重新测试,将有效地提高报告的准确性。
  • pytest-ordering:可以指定一个测试套中所有用例执行顺序。pytest 默认情况下是根据测试方法名由小到大执行的,使用 pytest-ordering 插件则可改变这种运行顺序。
  • pytest-cov:覆盖率报告,与分布式测试兼容。支持 pytest 的代码覆盖,显示已经被测和没有测试的代码,还包括项目的测试覆盖率。
  • pytest-django:为 Django 应用程序和项目添加 pytest 支持。
  • pytest-timeout: 根据函数标记或全局定义使测试超时。

插件安装与卸载

插件安装

pytest 的第三方插件安装很简单,和 Python 的包安装类似,使用 Python 包管理工具 pip 安装即可。

插件安装语法为:pip install pytest-NAME ,NAME 为插件名。

例如我们安装插件 pytest-ordering。

打开命令行工具,输入命令 pip install pytest-ordering ,操作截图如下所示:

插件卸载

卸载插件和安装插件类似,只需要将 install 换成 uninstall。

插件卸载语法为:pip uninstall pytest-NAME ,NAME 为插件名。

例如我们卸载插件 pytest-ordering。

在命令行工具输入命令 pip uninstall pytest-ordering ,操作截图如下所示:

插件 sugar

插件 sugar 是一个比较有意思的插件,笔者一直很喜欢。pytest-sugar 改变了 pytest 的默认外观,增加了一个进度条,并立即显示失败的测试。它不需要配置,只需安装 pytest-sugar,然后使用 pytest 运行测试脚本,便可获得一个漂亮、有用的输出。

打开命令行工具,输入命令 pip install pytest-sugar 安装插件 sugar,操作截图如下所示:

然后使用命令 cd /home/shiyanlou/test/ 进入 test 文件夹下。

接着输入命令 pytest -v ,然后回车。运行 test 文件夹下所有的测试文件,操作截图如下所示:

大家可以看到,结果展示与没有安装插件 sugar 之前显示是不同的。从截图中可以看到每一个测试用例函数执行完成后都会有一个进度条展示执行进度。失败的测试用例也会显示出失败的信息。

插件 Allure

Allure 是一款非常轻量级且灵活的开源测试报告生成框架,它支持绝大多数测试框架, 例如 TestNG、Pytest、JUint 等,使用起来很简单,也可配合持续集成工具 Jenkins 使用。

如果要使用 Allure 则需要安装 Allure 程序,如果要在 pytest 框架下使用 Allure,还需要安装 Allure Pytest 插件。只有安装了 Allure Pytest 插件,才可以通过 pytest 命令运行测试脚本生成 Allure 需要的数据,然后在通过 Allure 工具将这些数据生成 HTML 测试报告。

Allure 安装

Allure 是基于 Java 语言的一个程序,所以在使用时需要 Java 环境。在我们的虚拟环境中已经安装好了 Java,我们可以直接使用。

提示: 如果大家以后需要使用 Allure,请确保已经安装了 Java JDK。最后是 Java1.8+ 。

安装 Allure 程序,只需要进入到 Allure 下载界面 进行下载,然后解压使用。

由于我们的虚拟环境是 Linux 系统,所以可以通过 npm 命令进行安装,在命令行工具中输入安装命令 npm install -g allure-commandline --save-dev 。

等到安装完成,我们在命令行中输入命令 allure 进行验证是否安装成功。操作截图如下所示:

Allure Pytest 安装

Allure Pytest 是 pytest 的一个插件,所以遵循 pytest 插件安装的规则,在命令行工具中执行命令 pip install allure-pytest 即可完成安装。操作截图如下所示:

安装成功后会在最后面出现 Successfully installed 信息。

Allure 使用

Allure 的使用需要分两步走,先生成 JSON 数据文件,然后利用 JSON 数据文件生成 HTML 测试报告。

生成测试数据

在命令行模式下使用命令 cd /home/shiyanlou/test/ 进入 test 文件夹下。

然后输入命令 pytest --alluredir ./result/ 执行测试脚本,并且产生测试数据。添加 --alluredir 参数,意思是将测试数据保存。如上命令就是将测试数据保存到当前文件夹下的 result 文件中。操作截图如下所示:

脚本执行完成后,会在当前文件夹下创建一个 result 文件夹,存放产生的 JSON 格式测试数据文件。产生的文件如下截图:

生成 HTML 报告

接下来我们进入 cd /home/shiyanlou/test/ 文件夹下,利用产生的测试数据通过命令 allure generate ./result/ -o ./report/ --clean 生成 HTML 格式的测试报告。

对上面命令中的参数做以下说明:

  • generate:生成测试报告,后面跟测试数据路径。
  • --clear:也可以写成 -c,清除之前的旧报告。
  • -o:也可以写成 --report-dir 或 --output,生成报告的路径。

在命令行模式下进入 cd /home/shiyanlou/test/ 文件夹下,执行命令 allure generate ./result/ -o ./report/ --clean ,操作截图如下所示:

命令执行完成后会在当前文件夹下生成一个 report 文件夹,里面存放的便是测试报告文件。 report 文件夹下内容如下截图:

下面我们在浏览器中打开 /home/shiyanlou/test/report/ 下的 index.html 文件,查看测试报告内容。截图如下所示:

注意: 由于跨域等问题的影响,在打开 index.html 后可能会显示数据一直加载,404 等现象。此时我们可在 VS Code 工具中安装插件 Live Server,然后右键点击,选择 【Open with Live Server】进行打开。

打开之后大家可以通过左边菜单,以不同的形式查看报告内容。

数据库操作 PyMySQL

在自动化测试中经常需要用到大量的数据,由于数据的类型多样,所以我们需要将这些数据按一定的规则存储,以便使用时易于获取。存贮这些数据最好的解决方案就是数据库。而 MySQL 数据库以入门容易、语法简单、功能强大、适应性广等特点,得到了广泛应用。本次实验为大家介绍在 Python3 中使用 PyMySQL 连接数据库,并实现简单的增删改查。

创建数据库


在我们的实验环境中已经安装了 MySQL 的一个分支数据库 MariaDB ,它与 MySQL 在绝大多数功能上是兼容的,所以我们可以使用它来替代 MySQL ,因为它在某些时候占用的系统资源更少,性能却并不减弱。

在使用过程中,我们可以把 MariaDB 当作 MySQL ,所有的命令都是一样。
    启动 MySQL

在命令行中使用下面命令进行启动:

sudo service mysql start
提示:如果虚拟环境进行了保存,重新启动后需要再次执行上面的命令启动 MySQL。
    连接 MySQL

命令行中输入 mysql -u root -p ,然后输入回车键,回车后提示需要输入密码,然后继续输入回车键,就可以连接数据库。由于我们的数据库没有设置密码,所以直接使用命令 mysql -u root 便可以连接 MySQL 数据库。操作如下截图:

    查看数据库

使用 SHOW DATABASES; 命令可以查看 MySQL 中已经存在那些数据库。如下截图:

可以看到已经存在三个数据库,information_schema,mysql,perfomance_schema。这是 MySQL 默认存在的三个库。
    创建数据库

下面我们使用命令 CREATE DATABASE test_case; 创建一个数据库 test_case。

然后使用 SHOW DATABASES; 命令查看数据库。如下截图:

从截图中可以看到,数据库 test_case 已经创建成功。
    退出数据库

使用 exit 命令,或者快捷键 Ctrl + C 可以退出数据库。



连接数据库

安装 PyMySQL

PyMySQL 模块是 Python 的一个第三方库,使用时需要先安装,在命令行中输入 sudo pip install PyMySQL 便可安装,操作截图如下所示:

连接数据库

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

import pymysql
# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="",
    database="test_case",
)

# 创建游标对象
cursor = db.cursor()

#  执行 SQL 查询
cursor.execute("SELECT VERSION()")

# 获取一条数据
data = cursor.fetchone()

# 打印获取的数据
print ("Database version : %s " % data)

# 关闭游标
cursor.close()

# 断开连接
db.close()

连接数据库时用到了一些参数,做以下说明:

  • host="localhost":数据库地址,IP。
  • user="root":用户名。
  • password="":密码。
  • database="test_case":数据库名。

然后点击 VS Code 工具右上角的执行按钮运行脚本,截图如下:

从截图中可以看到,输出了 “Database version : 10.5.8-MariaDB-1:10.5.8+maria~focal” 内容。表示数据库连接成功,并且执行了 SQL 查询。

创建数据表

连接数据库后,我们便可以对连接的数据库做一些操作。下面我们来创建一个数据表 LOGIN。

创建数据表的方法很简单,使用 execute() 方法执行创建表的 SQL 语句即可。

下面我们编辑 /home/shiyanlou/test/database_operation.py 文件,注释掉所有的代码,重新编写内容如下:e()

import pymysql


# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="",
    database="test_case",
)

# 创建游标对象
cursor = db.cursor()

sql = """
    CREATE TABLE LOGINCASE(
    `id` INT UNSIGNED AUTO_INCREMENT,
    `title` CHAR(200) NOT NULL,
    `email` CHAR(100),
    `password` CHAR(100),
    `expected_location` CHAR(100),
    `expected_results` CHAR(200),
    PRIMARY KEY ( `id` ))
    """
cursor.execute(sql)

# 关闭游标
cursor.close()
# 断开连接
db.close()

注意: 数据表名,字段名尽量和示例一致,因为在接下来的实验中我们会使用到。

在此我们创建了一张表 LOGINCASE ,有六个字段,分别是 id、title、email、password、expected_location、expected_results。并且将 id 设置成了主键。

然后点击 VS Code 工具右上角的执行按钮运行脚本。

接下来在命令行中依次执行下面语句,查看创建的数据表:

mysql -u root
USE test_case;
DESC LOGINCASE;

操作截图如下:

从结果中可以看到,成功创建了数据表。

数据库插入数据

可以通过执行 SQL 语句对数据表添加数据。

下面我们编辑 /home/shiyanlou/test/database_operation.py 文件,注释掉所有的代码,重新编写内容如下:

import pymysql


# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="",
    database="test_case",
)

# 创建游标对象
cursor = db.cursor()

sql = """
    INSERT INTO LOGINCASE(title,
         email, password, expected_location, expected_results)
         VALUES ("登录成功", 'admin@tynam.com', 'tynam123', 'login success', "登录成功!")
    """
try:
   cursor.execute(sql)
   # 提交到数据库执行
   db.commit()
except:
   # 如果发生错误则回滚
   db.rollback()

# 关闭游标
cursor.close()
# 断开连接
db.close()

我们向数据表 LOGINCASE 中插入了一条数据【title="登录成功",email='admin@tynam.com',password='tynam123',expected_location='login success',expected_results="登录成功!"】。

然后点击 VS Code 工具右上角的执行按钮运行脚本。

接着我们将上面的插入数据进行修改为 【title="邮箱地址不输入",email='',password='tynam124',expected_location='email error',expected_results="邮箱地址不可为空"】,再次点击 VS Code 工具右上角的执行按钮运行脚本。

我们总共向数据库中插入了两条数据。

数据库查询数据

PyMySQL 中可以使用 fetch 获取数据,默认是以元组的形式返回。常用的方法如下:

  • 获取第一行数据:cursor.fetchone()
  • 获取前 n 行数据:cursor.fetchmany(n)
  • 获取所有数据:cursor.fetchall()

下面我们编辑 /home/shiyanlou/test/database_operation.py 文件,注释掉所有的代码,重新编写内容如下:

import pymysql


# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="",
    database="test_case",
)

# 创建游标对象
cursor = db.cursor()

sql = """
    select * from LOGINCASE
    """
cursor.execute(sql)
# 获取第一行数据
row_first = cursor.fetchone()
print(row_first)

cursor.execute(sql)
# 获取前两行数据
row_n = cursor.fetchmany(2)
print(row_n)

cursor.execute(sql)
# 获取所有数据
row_all = cursor.fetchall()
print(row_all)

# 关闭游标
cursor.close()
# 断开连接
db.close()

我们分别使用 cursor.fetchone()、 cursor.fetchmany(n) 和 cursor.fetchall() 获取表 LOGINCASE 中的数据。

然后点击 VS Code 工具右上角的执行按钮运行脚本。截图如下:

从结果中可以看到,以元组的形式的返回了数据,得到的数据与预期结果一致。

数据库更新数据

可以通过执行 SQL 语句对数据表更新数据。

下面我们编辑 /home/shiyanlou/test/database_operation.py 文件,注释掉所有的代码,重新编写内容如下:

import pymysql

# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="",
    database="test_case",
)

# 创建游标对象
cursor = db.cursor()

sql = """
    select * from LOGINCASE WHERE id = 2
    """
# 更新前 id=2 的内容
cursor.execute(sql)
print(cursor.fetchone())

sql_upadte = """
    UPDATE LOGINCASE SET password = 'tynam111' WHERE id = 2
    """
try:
   cursor.execute(sql_upadte)
   db.commit()
except:
   db.rollback()

# 更新后 id =2 的内容
cursor.execute(sql)
print(cursor.fetchone())

# 关闭游标
cursor.close()
# 断开连接
db.close()

我们对数据表 LOGINCASE 进行数据更新,如果 id=2 则将其 password 变更为 tynam111。并且将更新前后的 id=2 的数据进行输出。

然后点击 VS Code 工具右上角的执行按钮运行脚本。截图如下:

从结果中可以看到,数据 id=2 的 password 成功地由 tynam124 变更为 tynam111。

数据库删除数据

可以通过执行 SQL 语句对数据表删除数据。

下面我们编辑 /home/shiyanlou/test/database_operation.py 文件,注释掉所有的代码,重新编写内容如下:

import pymysql
# 连接数据库
db = pymysql.connect(
    host="localhost",
    user="root",
    password="",
    database="test_case",
)

# 创建游标对象
cursor = db.cursor()

sql = """
    select * from LOGINCASE
    """
# 删除操作前所有的数据
cursor.execute(sql)
print(cursor.fetchall())

sql_delete = """
    DELETE FROM LOGINCASE WHERE id > 1
    """
try:
   cursor.execute(sql_delete)
   db.commit()
except:
   db.rollback()

# 删除操作后所有的数据
cursor.execute(sql)
print(cursor.fetchall())

# 关闭游标
cursor.close()
# 断开连接
db.close()

我们对数据表 LOGINCASE 进行数据删除,如果 id>1 则将其删除。并且将删除前后的所有数据进行输出。

然后点击 VS Code 工具右上角的执行按钮运行脚本。截图如下:

从结果中可以看到,删除前存在 id>1 的数据,删除操作后没有 id>1 的数据,说明成功进行了删除操作。

本次实验给大家介绍了数据库的操作模块 PyMySQL。通过本次实验的学习,大家可以掌握简单的增删改查操作。在自动化测试中,特别是数据驱动模型,就需要提供大量的测试数据,并且以一定的格式存储,相信这些对大家来说将不再是问题。

数据驱动模型实战之结构设计

在本系列实验会结合 pytest 框架实战练习数据驱动模型和关键字驱动模型。

本次实验进行数据驱动模型实战,会以一个登录页面为例,将数据存储在数据库中进行实战。

数据驱动模型简介

数据驱动模型是针对同一个功能使用一套测试脚本进行不同数据的测试,从而检测测试结果的变化。数据驱动的形式有很多种,比如通过定义数据变量、字典的方式进行参数化,或者通过读取 Excel、CSV 等文件的方式进行参数化。

数据驱动模型简介

数据驱动模型是从某个数据文件(例如 txt 文件、Excel 文件、CSV 文件、数据库等)中读取输入或输出的测试数据,然后以变量的形式传入事先录制好的或手工编写好的测试脚本中。在这个过程中,作为传递(输入/输出)的变量被用来验证应用程序的测试数据,而测试数据只包含在数据文件中而不是脚本里。测试脚本只是作为一个“驱动”,更恰当的说是一个传送数据的机制。同一个测试脚本可使用不同的测试数据来执行,测试数据和测试行为完全分离,这样的测试脚本设计模式叫做数据驱动模型。

数据驱动的一般流程如下:

  1. 测试结构搭建:设计测试结构,做到目录清晰,项目容易解读。
  2. 设计测试用例:数据文件以怎样的数据格式进行设计,明确测试用例。
  3. 数据文件的操作:对数据文件中的数据进行操作。
  4. 编写测试脚本:包括对获取的文件数据处理、断言的封装及测试用例的步骤。
  5. 执行且生成测试报告:运行测试用例,将测试结果输出。

ddt 介绍

在《Python Web 自动化测试入门与实战》书籍第八章中,介绍数据驱动模型时使用的是 ddt + unittest 框架。因为在 unittest 单元测试框架中自身是没有参数化功能,所以就需要 ddt 框架。在本系列实验中,给大家介绍 pytest 框架,pytest 自身就有参数化功能,所以我们就不需要再多一个框架的引入。但是 ddt 是一个不错的参数化框架,在此给大家做以简单的提及。

ddt 是 data-driven tests 的缩写,字面意思可理解为数据驱动测试。使用时用于给一个测试用例传入不同的参数,每组参数都会运行一次,就像是运行了多个测试用例一样。

关于 ddt 的使用可参考 ddt 的 Demo 示例:http://ddt.readthedocs.io/en/latest/example.html 。

使用比较简单,就不再做练习了,因为与本系列实验关系不大。

结构搭建

根据数据驱动的特性,我们来搭建一个简单的自动化测试结构。

首先我们在 /home/shiyanlou/Code/ 下创建一个文件夹,并且命名为 DataDrivenModel ,用以存放我们本次数据驱动实战的相关文件。然后使用 VS Code 打开 DataDrivenModel 文件夹。

接下来将我们项目进行分类管理,创建相关的文件夹。在 /home/shiyanlou/Code/DataDrivenModel/ 下创建文件夹 page、common、case、report 。然后创建文件 run.py 和 readme.md 。最后在所有的文件夹下添加一个 __init__.py 文件,包括 DataDrivenModel 文件夹。这样就可以将所在的文件夹变成一个可以被导入的 Python 包。创建完成后打开 VS Code 工具的终端,使用 tree 命令查看 /home/shiyanlou/Code/DataDrivenModel 下目录结构,操作截图如下:

下面对我们创建的文件做以说明:

  • case:存放测试用例脚本层。
  • common:公共层,存放数据文件的读写等公共文件。
  • page:页面层,包含页面元素定位,页面操作步骤等。
  • report:存放测试报告层。
  • run.py:运行脚本并生成测试报告。
  • readme.md:对项目的说明。

readme.md 是对我们项目的说明。例如我们可以进行说明每个文件夹的作用。接下来我们编辑 readme.md 文件,内容如下:

- case:测试用例层,存放测试用例脚本目录。
- common:公共目录,存放数据文件的读写等公共操作。
- page:页面目录,对页面元素定位,编写测试步骤等。
- report:存放测试报告目录。
- run.py:运行脚本并生成测试报告。

创建项目

下面我们来写一个简单的登录页面,作为数据驱动模型实战的案例。

使用 VS Code 在 /home/shiyanlou/Code/DataDrivenModel/ 下新建文件夹 LoginProject 。然后在 LoginProject 下新建两个文件 index.html 和 index.css 。 index.html 文件用于写登录页面,index.css 文件用于对登录页面美化。

编辑 index.html 文件,内容如下:

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>数据驱动模型实战</title>
    <link href="./index.css" rel="stylesheet" type="text/css" />
  </head>

  <body>
    <h1>数据驱动模型实战</h1>
    <div class="login box">
      <!-- form starts-->
      <div id="login-form">
        <div class="txt">
          <input placeholder="请输入您的邮箱" id="ty-email" />
          <div class="msg" id="ty-email-error"></div>
        </div>
        <div class="txt">
          <input type="password" placeholder="请输入密码" id="ty-pwd" />
          <div class="msg" id="ty-pwd-error"></div>
        </div>
        <div class="login-btn">
          <input type="button" onclick="login()" value="登  录" />
        </div>
        <div class="account">
          <p>账号: admin@tynam.com; 密码: tynam123</p>
        </div>
      </div>
    </div>
    <!-- form ends -->
    <!--copyright-->
    <div class="copyright">
      <p>Copyright © 2021 Tynam</p>
    </div>
    <!--copyright-->

    <script type="text/javascript">
      var emailMsg = document.getElementById("ty-email-error");
      var pwdMsg = document.getElementById("ty-pwd-error");
      var email = document.getElementById("ty-email");
      var pwd = document.getElementById("ty-pwd");
      var regex_mail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;

      function login() {
        emailMsg.innerHTML = "";
        pwdMsg.innerHTML = "";
        if (email.value == "") {
          emailMsg.innerHTML = "邮箱地址不可为空";
          return false;
        } else if (email.value.indexOf(" ") != -1) {
          emailMsg.innerHTML = "邮箱地址中不能有空格";
          return false;
        } else if (email.value.length < 6 || email.value.length > 30) {
          emailMsg.innerHTML = "邮箱地址长度在 6~30 位";
          return false;
        } else if (!regex_mail.test(email.value)) {
          emailMsg.innerHTML = "邮箱地址格式不正确";
          return false;
        }

        if (pwd.value == "") {
          pwdMsg.innerHTML = "密码不可为空";
          return false;
        } else if (pwd.value.length < 6 || pwd.value.length > 20) {
          pwdMsg.innerHTML = "密码长度在 6~20 位";
          return false;
        }

        if (email.value == "admin@tynam.com" && pwd.value == "tynam123") {
          email.value = "";
          pwd.value = "";
          alert("登录成功!");
          return true;
        } else {
          alert("邮箱或密码错误!");
          return false;
        }
      }
    </script>
  </body>
</html>

上面我们写了一个简单的登录页面。大家可以直接将内容复制进去。注意;如果直接复制进去,由于编码格式不同导致汉子显示有问题。因此我们可以把内容保存成文件,通过实验环境右侧菜单上传文件,然后再进行下一步操作。

下面我们用火狐浏览器会打开 /home/shiyanlou/Code/DataDrivenModel/LoginProject/index.html ,页面截图如下:

可以看到,内容已经有了。此处大家需要记住打开后的 URL 是 file:///home/shiyanlou/Code/DataDrivenModel/LoginProject/index.html 。

内容有了,但是还缺少一些样式。接下来我们编辑 /home/shiyanlou/Code/DataDrivenModel/LoginProject/ 下的 index.css 文件,编辑内容如下:

html,
body {
  margin: 0;
  padding: 0;
  border: 0;
  vertical-align: baseline;
  background: rgb(86, 228, 221);
}

body {
  font-family: "Catamaran", sans-serif;
  font-size: 100%;
  text-align: center;
}

.login {
  display: flex;
  justify-content: center;
  align-items: center;
  justify-content: center;
}

h1 {
  font-size: 2.8em;
  font-weight: 300;
  text-transform: capitalize;
  color: #333;
  text-shadow: 1px 1px 1px #000;
  letter-spacing: 2px;
  margin: 1.2em 1vw;
  text-align: center;
}

#login-form {
  max-width: 500px;
  margin: 0 5vw;
  padding: 3.5vw;
  border-width: 5px 0;
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  background: #fff;
}

.txt {
  flex-basis: 100%;
  margin-bottom: 1.5em;
}
#ty-email,
#ty-pwd {
  width: 100%;
  color: rgb(0, 0, 0);
  outline: none;
  background: rgba(192, 192, 192, 0.726);
  font-size: 17px;
  letter-spacing: 0.5px;
  padding: 12px;
  box-sizing: border-box;
  border: none;
  -webkit-appearance: none;
  font-family: "Catamaran", sans-serif;
}

.login ::placeholder {
  color: rgb(95, 95, 95);
}

.msg {
  color: red;
  text-align: left;
  margin: 5px 0;
  font-size: 14px;
}

.login-btn input {
  color: rgb(255, 255, 255);
  background: rgba(46, 45, 45, 0.904);
  width: 100%;
  padding: 0.4em 0;
  font-size: 1em;
  font-weight: 400;
  letter-spacing: 2px;
  cursor: pointer;
  border: none;
  outline: none;
}

.login-btn input:hover {
  color: rgb(255, 255, 255);
  background: rgb(46, 46, 46);
}

.login-btn {
  margin-top: 1em;
  width: 100%;
}

.account {
  font-size: 0.8em;
  color: rgb(95, 95, 95);
}

.copyright {
  margin: 4em 0 2em;
}

.copyright p {
  color: #333;
  font-size: 1em;
  letter-spacing: 1.5px;
  line-height: 1.8;
  margin: 0 3vw;
}

大家将内容直接复制进去,具体含义不做解释,因为这不是我们本系列实验的重点。如果有兴趣的话可以了解一些前端知识,了解后上面的代码含义则不难理解。

填充内容后保存文件。然后刷新浏览器,页面截图如下:

可以看到,一个简单登录页面以及经过了美化。

项目解析

从上面的登录页面出发,我们来分析怎么进行自动化测试,使用脚本进行测试的正常操作流程为:

  1. 打开浏览器。
  2. 输入 URL 后访问登录页面。
  3. 输入邮箱地址。
  4. 输入密码。
  5. 单击登录按钮。
  6. 对结果进行断言。
  7. 退出系统,关闭浏览器。

项目在数据驱动模型中进行的流程为:

  1. 根据功能测试用例设计方法,设计测试用例,编写用例编号、用例标题、预期结果等。本次实验我们将测试用例存储在数据库。
  2. 编写脚本文件,读取测试用例数据。即从数据库中读取测试用例。
  3. 对页面进行元素定位,根据手动测试的步骤使用代码写成测试用例函数。
  4. 利用 pytest 框架,将读取到的测试用例数据对测试用例函数进行参数化。
  5. 编写测试用例执行脚本,运行测试用例,对成功或失败的用例进行标记。

下面根据上面分析,我们对流程步骤展开说明。

设计测试用例

测试用例设计思想

根据页面的输入及操作可知,数据需要邮箱地址、密码和登录操作;根据自动化测试的操作可知,除了邮箱地址、密码、登录操作还需要 URL、操作步骤、预期结果和实际结果;除以上数据外还可增加用例的编号、用例标题,方便用例设计时知道用例的作用。

在本系列第六次实验数据库的操作中,我们知道数据表中可以设置主键,因此可以将主键作为用例编号使用。

URL 可直接写入代码中,操作步骤是在执行过程中需要代码实现的,实际结果是在一个用例运行后使用代码获取的。因此测试用例数据格式可设计为:用例标题、邮箱地址、密码、预期结果。

大家可以对项目进行手工测试。由手工测试操作可知,预期结果会在三个地方出现。邮箱地址输入错误时出现在邮箱地址输入框下方;密码输入错误时出现在密码输入框下方;登录时出现在 alert 弹窗中。总之,预期结果出现的位置不唯一,所以需要确定预期结果的位置。因此测试用例数据格式中还需要添加预期结果定位。

经过以上分析,最终确定测试用例的格式为:用例标题、邮箱地址、密码、预期结果定位、预期结果。

测试用例实现

根据上面的分析和测试用例设计方法,我们来设计手工测试用例数据,结果如下:

用例标题 邮箱地址 密码 预期结果定位 预期结果
登录成功 admin@tynam.com tynam123 login success 登录成功!
邮箱地址不输入 tynam124 email error 邮箱地址不可为空
邮箱地址输入空格 tynam125 email error 邮箱地址中不能有空格
邮箱地址长度小于 6 位 a@t.c tynam126 email error 邮箱地址长度在 6 ~ 30 位
邮箱地址长度大于 30 位 adminadmadminadminci
min@adminadmin.comcom
tynam127 email error 邮箱地址长度在 6 ~ 30 位
邮箱格式错误 1 adminadm123 tynam128 email error 邮箱地址格式不正确
邮箱格式错误 2 @#$%^&@f.234$% tynam129 email error 邮箱地址格式不正确
密码不输入 admin@tynam.com password error 密码不可为空
密码长度小于 6 位 admin@tynam.com tynam password error 密码长度在 6 ~ 20 位
密码长度大于 20 位 admin@tynam.com tynam133tynam133
tynam133tynam133
password error 密码长度在 6 ~ 20 位
邮箱地址不存 admin1@tynam.com tynam123 login fail 邮箱或密码错误!

测试用例插入数据库

根据测试用例设计思想中的分析,我们需要在数据库中创建一张含有用例标题、邮箱地址、密码、预期结果定位、预期结果,以及一个主键作为测试用例编号。

在第六次实验数据库的操作中,我们已经创建了一个数据库 test_case ,并且创建了一张表 LOGINCASE ,有 id、title、email、password、expected_location、expected_results 六个字段。并且将 id 设置成了主键。所以我们拿来直接用。

接下来在命令行中依次输入如下命令,进入数据库 test_case ,清除表 LOGINCASE 中的数据:

mysql -u root
USE test_case;
DELETE FROM LOGINCASE;

操作截图如下:

然后输入下面 SQL 语句,插入我们的设计好的测试用例:

INSERT INTO LOGINCASE(title, email, password, expected_location, expected_results) values
                    ("登录成功", "admin@tynam.com", "tynam123", "login success", "登录成功!"),
                    ("邮箱地址不输入", "", "tynam124", "email error", "邮箱地址不可为空"),
                    ("邮箱地址输入空格", "t y", "tynam125", "email error", "邮箱地址中不能有空格"),
                    ("邮箱地址长度小于6位", "a@t.c", "tynam126", "email error", "邮箱地址长度在 6~30 位"),
                    ("邮箱地址长度大于30位", "adminadmadminadmincimin@adminadmin.comcom", "tynam127", "email error", "邮箱地址长度在 6~30 位"),
                    ("邮箱格式错误1", "adminadm123", "tynam128", "email error", "邮箱地址格式不正确"),
                    ("邮箱格式错误2", "@#$%^&@f.234$%", "tynam129", "email error", "邮箱地址格式不正确"),
                    ("密码不输入", "admin@tynam.com", "", "password error", "密码不可为空"),
                    ("密码长度小于6位", "admin@tynam.com", "tynam", "password error", "密码长度在 6~20 位"),
                    ("密码长度大于20位", "admin@tynam.com", "tynam133tynam133tynam133tynam133", "password error", "密码长度在 6~20 位"),
                    ("邮箱地址不存", "admin1@tynam.com", "tynam123", "login fail", "邮箱或密码错误!");

提示:在命令模式下进行数据库的操作,如果不能直接使用快捷键进行粘贴操作,则可通过右键菜单进行粘贴操作。

操作截图如下:

接下来我们使用 SELECT * FROM LOGINCASE; 语句查看插入的数据。操作截图如下:

可以看到,我们一共插入了 11 条数据。

数据驱动模型实战之测试代码

本系列实验会结合 pytest 框架实战练习数据驱动模型和关键字驱动模型。

本次实验进行数据驱动模型实战,会以一个登录页面为例,将数据存储在数据库中进行实战操作。

获取用例数据    

在上节实验中我们将设计好的测试用例添加到了数据库,那么接下来我们就需要用代码从数据库中获取测试用例数据。对数据文件的读取我们可以采用一种灵活的方式,将读取方法进行封装,在使用的时候直接调用封装好的方法即可。

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

import pymysql

class SqlExecute:
    def __init__(self):
        """连接数据库"""
        self.db = pymysql.connect(
            host="localhost",
            user="root",
            password="",
            database="test_case",
        )

    def close(self):
        """断开连接"""
        self.db.close()

    def get_data(self, data_table):
        """返回表 data_table 中的数据"""
        cursor = self.db.cursor()
        sql = "select * from %s" % data_table
        cursor.execute(sql)
        data_all = cursor.fetchall()
        cursor.close()

        return data_all

    def get_case(self, data_table):
        """返回表 data_table 中的自动化测试用例数据"""
        data_all = self.get_data(data_table)

        case_all = []
        for data in data_all:
            case_all.append(data[2:])

        return case_all


if __name__ == '__main__':
    sql = SqlExecute()
    login_case = sql.get_case('LOGINCASE')
    sql.close()
    print(login_case)

封装思路:我们先写了一个执行数据库操作的类 SqlExecute() ,在实例化时就执行 __init__() 构造函数,使其连接到数据库 test_case() 。有连接就有断开,所以我们紧接着就写了一个断开数据库连接的方法 close() 。接着就是我们对数据库操作的函数了,由于本次我们只使用到获取登录测试用例,所以只写了两个个方法 get_data(data_table) 和 get_case(data_table) ,当然也可写其他的操作,比如只获取第一条测试用例数据,获取前 n 条测试用例数据,测试用例更新等方法。

方法 get_data(data_table) 中参数 data_table 为数据表名,传入那张表就返回对应表中数据。在测试中,我们有很多个功能,测试用例表也会有很多张,所以参数化更会方便我们的使用。

而表中的 id 和用例标题两个字段是为了方便我们写测试用例时查看,辨别,在自动化测试中关注的是测试数据,所以需要剔除。因此写了方法 get_case(data_table) ,剔除 id 和用例标题两个字段。

在 if __name__ == '__main__': 下,我们对类 SqlExecute() 进行使用,查看结果。

点击 VS Code 工具右上角的执行按钮运行脚本。截图如下:

从结果中可以看到,获取的字段依次是:邮箱地址、密码、预期结果定位、预期结果。

编写测试步骤

对于登录页面的测试步骤设计,需要用到 Selenium 库,包括元素定位、元素操作等内容。在《Python Web 自动化测试入门实战》初级实验中已经做了详细解释,在此就不做解释了,直接使用。

在第七次实验中,对项目进行了解析,分析了自动化测试时执行的步骤,现在我们需要将步骤使用代码实现。

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

import  time
from selenium import webdriver

class Login:
    def __init__(self, driver):
        time.sleep(3)
        self.driver = driver

    def login(self, email, password):
        """登录操作"""
        if email:
            email_element = self.driver.find_element_by_id('ty-email')
            email_element.clear()
            email_element.send_keys(email)
            time.sleep(1)
        if password:
            password_element = self.driver.find_element_by_id('ty-pwd')
            password_element.clear()
            password_element.send_keys(password)
            time.sleep(1)

        login_btn = self.driver.find_element_by_css_selector("input[value='登  录']")
        login_btn.click()


if __name__ == '__main__':
    driver = webdriver.Firefox()
    driver.get("file:///home/shiyanlou/Code/DataDrivenModel/LoginProject/index.html")
    Login(driver).login('admin@tynam.com', 'tynam123')
    time.sleep(3)
    driver.quit()

我们新建了一个 Login() 类,然后添加了一个 login() 方法,就是我们登录的操作步骤。操作步骤中我们依次实现了定位邮箱地址并且输入对应的值、定位密码输入框并且输入对应的值,然后点击登录按钮进行登录。如果邮箱或密码输入的值是 None ,也就是不输入,则对应的不会进行定位,也不会对其输入值。

在 if __name__ == '__main__': 下,我们对类 Login() 进行使用,对 login() 方法进行测试。

注意: 我们通过脚本启动火狐浏览器时需要火狐浏览器驱动,在《Python Web 自动化测试入门实战》初级实验中已经做了详细解释,在此就不做解释了,只说一下操作步骤。在命令行中依次执行如下命令,下载火狐浏览器驱动,然后解压压缩包,解压后将获得火狐浏览器驱动,将其移动到 Python 程序所在目录下,最后删除下载压缩包。

https://labfile.oss.aliyuncs.com/courses/3776/geckodriver.zip
unzip geckodriver.zip
rm -rf __MACOSX geckodriver.zip
sudo mv geckodriver /usr/bin/

操作截图如下:

接下来点击 VS Code 工具右上角的执行按钮运行脚本。截图如下:

运行后会依次执行了打开火狐浏览器、访问登录页面、输入邮箱地址和密码、点击登录、提示登录成功,最后关闭了浏览器。

断言处理

因为预期结果所需要的提示信息会在不同的地方出现,所以需要分析预期结果出现的位置。

  1. 邮箱地址输出的错误提示信息如下截图所示。

  1. 密码错误提示信息如下截图所示。

  1. 登录后提示信息如下截图所示。

明确断言出现的位置后,需要根据传入的预期结果定位进行对应处理,因为登录操作方法和登录预期结果判断都是对登录功能的测试,所以将两者都放在同一个类 Login() 下。

在 /home/shiyanlou/Code/DataDrivenModel/page/login.py 下 Login() 类中添加 login_assert() 方法 ,编写如下内容:

    def login_assert(self, assert_location, assert_message):
        """登录断言"""
        time.sleep(1)
        message = ''
        if assert_location == 'email error':
            message = self.driver.find_element_by_id('ty-email-error').text
        elif assert_location == 'password error':
            message = self.driver.find_element_by_id('ty-pwd-error').text
        elif assert_location in ['login success', 'login fail']:
            message = self.driver.switch_to.alert.text
        else:
            pass

        assert message == assert_message
login_assert() 方法作为登录测试断言方法,使用时只需要传入两个参数 assert_location 和 assert_message,通过 assert_location 传入预期结果出现的位置(邮箱地址错误 email error、密码错误 password error、登录失败 login fail 和登录成功 login success)获取实际结果。然后通过参数 assert_message 传入预期结果,进而将预期结果与实际结果进行比较,若相等则测试通过,否则测试失败。

生成测试脚本

我们已经有了测试数据,也有了测试步骤,现在将使用测试数据对测试步骤进行参数化,生成可执行的测试用例脚本。

配置文件

在测试用例执行时我们需要先设置一些配置数据。

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

import pytest
from selenium import webdriver


@pytest.fixture
def open_login_page():
    url = "file:///home/shiyanlou/Code/DataDrivenModel/LoginProject/index.html"
    driver = webdriver.Firefox()
    driver.implicitly_wait(10)
    driver.maximize_window()
    driver.get(url)

    yield driver
    driver.quit()

在此我们将启动浏览器,并且访问登录页面设置为 fixture。测试用例运行完成后我们使用 driver.quit() 关闭浏览器。

测试脚本文件

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

import os, sys
import pytest
from selenium import webdriver

current_path = os.path.abspath(os.path.dirname(__file__))
autotest_path = os.path.join(current_path, os.path.pardir)
sys.path.append(autotest_path)
from common.sql_execute import SqlExecute
from page.login import Login


class TestLogin:
    case = SqlExecute().get_case('LOGINCASE')
    @pytest.mark.parametrize('email, password, assert_location, assert_message', case)
    def test_login(self, email, password, assert_location, assert_message, open_login_page):
        driver = open_login_page
        login = Login(driver)
        login.login(email=email, password=password)
        login.login_assert(assert_location=assert_location, assert_message=assert_message)


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

这儿的代码大家应该很好理解,获取所有的测试用例数据,然后将数据依次传递给测试用例方法 test_login() 。测试用例方法依次执行登录测试步骤、登录测试断言。

接下来点击 VS Code 工具右上角的执行按钮运行脚本。截图如下:

从结果可以看到,总共运行了 11 条测试用例,测试都通过。

测试执行

测试用例脚本文件编写完成后,我们现在来编写 /home/shiyanlou/Code/DataDrivenModel/ 下的 run.py 执行文件。

测试执行是将所有的测试用例组织在一起统一执行,最终生成可视化测试报告。

编写内容如下:

# -*- coding: utf-8 -*-


"""
Created on 2021-4-4
Project: DataDrivenModel
@Author: Tynam
"""

import os
import pytest


current_path = os.path.abspath(os.path.dirname(__file__))
case_path = os.path.join(current_path, 'case')

def run_case():
    pytest.main(['-v', case_path])

def create_report():
    run_case = """
        cd %s &
        pytest %s --alluredir ./report/result/
        """ % (current_path, case_path)

    generate_report = """
        cd %s &
        allure generate ./report/result/ -o ./report/report/ --clean
        """ % (current_path)

    os.system(run_case)
    os.system(generate_report)


if __name__ == '__main__':
    # run_case()
    create_report()

我们定义了两个方法,run_case() 和 create_report() 。run_case() 是只执行测试用例脚本,但不生成测试报告。create_report() 是执行测试用例脚本后生成 Allure 测试报告。

下面我们在 run.py 文件中执行 create_report() 函数。

点击 VS Code 右上角的执行按钮进行运行。

点击后可以看到火狐浏览器不断的打开,执行登录操作,关闭。运行完毕后 VS Code 终端输出结果如下截图:

我们使用 Live Server 打开 /home/shiyanlou/Code/DataDrivenModel/report/report/index.html 。截图如下:

至此,我们数据驱动模型实验已经完成。

本次实验是对数据驱动模型的实战练习。通过本次实验的学习,希望大家可以自己搭建一个简单的数据驱动结构,在以后工作、学习中,将测试用例数据存放在其他地方,例如 Excel、CSV 等,也可以迎刃解决。








全部评论

相关推荐

点赞 评论 收藏
转发
1 5 评论
分享
牛客网
牛客企业服务