一、测试用例识别与运行
1.测试用例的识别规则
1)pytest框架的默认识别规则
- 测试文件:必须为test_*.py或*_test.py
- 测试类:必须为Test*
- 测试方法或函数:必须为test_*
2)修改pytest的默认执行规则
在测试框架的根目录下新建pytest.ini文件,加入新的用例识别规则。假如我们要定义新的pytest用例识别的规则如下:
- 测试文件:必须为test_*.py或weeds_*.py
- 测试类:必须为Test*或Weeds*
- 测试方法或函数:必须为test_*或weeds_*
pytest.ini配置如下:
[pytest]
python_files = weeds_* test_*
python_classes = Weeds* Test*
python_functions = weeds_* test_*
2.测试用例的执行顺序
根据测试用例的编写顺序从上到下依次执行。
二、pytest命令行常用参数
- pytest -v打印详细的日志信息
- pytest -s将用例中print方法打印的信息输出到控制台
- pytest 文件名 执行该文件中所有的测试用例
- pytest 文件名:类名 单独执行文件中的类
- pytest 文件名:类名:方法名 单独执行类中的方法
- pytest -k "类名 and not 方法名"跳过某个方法执行测试用例
- pytest -m [标记名] 执行被@pytest.mark.[标记名]装饰的测试用例
- pytest -x 文件名 一旦用例执行,就停止执行其他用例
- pytest --maxfail=[num] 当错误用例数达到num时,停止执行用例
三、pytest用例参数化
1.参数化的使用
使用@pytest.mark.parametrize(argnames,argvalues)对用例进行参数化。
- argnames:要参数化的变量,string,list,tuple
- argvalues:参数化的值,list,list[tuple]
2.参数化实战
参数化变量使用string。
# 要参数的变量使用string,参数化的值使用list[touple]
@pytest.mark.parametrize("a,b", [(1, 2), (2, 3)])
def test_add(a, b):
print(a + b)
参数化变量使用tuple。
# 要参数化的变量使用list,参数化的值使用tuople(touple)
@pytest.mark.parametrize(["a", "b"], ((1, 2), (3, 4)))
def test_add01(a, b):
print(a + b)
参数化变量使用list类似tuple,就不再举例。
使用多个parametrize对参数进行组合。
# 对两组测试数据进行组合
@pytest.mark.parametrize("a", [1, 2, 3])
@pytest.mark.parametrize("b", ["a", "b", "c"])
def test_div(a, b):
print(f"{a} is {b}")
使用yaml文件存储要参数化的值,对测试用例进行数据驱动。
import yaml
# 使用yaml进行参数化
data = yaml.safe_load(open("data.yaml"))
@pytest.mark.parametrize("a,b", data)
def test_add02(a, b):
print(a+b)
四、pytest fixture使用
1.fixture的作用
说到pytest fixture要先提一下pytest的setup,teardown方法,用于测试前准备或测试后清理工作。
- 模块级(setup_module/teardown_module)模块始末执行
- 函数级(setup_function/teardown_function)只对函数用例生效(不在类中的方法称为函数)
- 类级(setup_class/teardown_class)在类中前后运行一次
- 方法级(setup_method/teardown_method)开始于方法始末,同setup/teardown
fixture是pytest用于将测试前后准备,清理工作的代码分离出核心测试逻辑的一种机制,从功能上看,fixture与setup/teardown相似。
2.fixture的作用域
fixture中有一个参数scope,通过该参数确定fixture的作用域,scope包含以下参数:
- function 函数或方法级别被调用
- class 类级别调用一次
- module 模块级别调用一次
- session 多个文件调用一次。比如一个目录下包含多个模块,模块中都使用了该fixture方法,使用pytest 目录名,运行该目录下所有模块,该fixture只调用一次
3.fixture的使用
场景一:同一模块下,有些用例需要登录,有些无需登录
import pytest
@pytest.fixture()
def login():
print("这是一个登陆方法")
class TestFixture001:
def test_001(self, login):
print("需要登陆")
def test_002(self):
print("无需登陆")
场景二:用例运行前打开浏览器,运行后关闭浏览器
yield之前的代码在用例执行前运行,yield之后的代码在用例执行后运行
import pytest
# 默认scope等于function,作用域为函数级别,类似于setup、teardown.
# scope="module",作用域为module,类似于setup_module,teardown_module
# scope参数有四种选择:function(测试函数级别),class(测试类级别),
# module(测试模块“.py”级别),session(多个文件级别)。默认是function级别。
@pytest.fixture(scope="module")
def open():
print("打开浏览器")
yield
print("关闭浏览器")
class TestFixture001:
def test_001(self, open):
print("登陆系统1")
def test_002(self, open):
print("登陆系统2")
场景三:自动导入conftest.py中的函数。conftest.py用于存放所有被fixture装饰的前置函数
conftest所在目录的上一级目录的文件无法访问到conftest中定义的函数,同级或者下级目录的文件可以访问到conftest中定义的函数
conftest.py存放装饰的前置函数,代码如下:
#!usr/bin/env python
#-*- coding:utf-8 -*-
"""
备注:
conftest.py这个文件名是固定的,不可以更改。
conftest.py与运行用例在同一个包下,并且该包中有__init__.py文件
使用的时候不需要导入conftest.py,会自动寻找。
"""
import pytest
#默认scope等于function,作用域为函数级别,类似于setup、teardown. scope="module",作用域为module,类似于setup_module,teardown_module
@pytest.fixture(scope="module")
def open():
print("打开浏览器")
yield
print("关闭浏览器")
调用fixture的用例模块代码如下:
import pytest
class TestFixture001:
def test_001(self, open):
print("登陆系统1")
# 使用@pytest.mark.usefixtures(open)将open函数作为test_002的前置函数
@pytest.mark.usefixtures("open")
def test_002(self):
print("登陆系统2")
场景四:pytestmark = pytest.mark.usefixtures("open_everyone")
定义模块下所有用例的前置函数都为open_everyone
coftest.py代码如下:
import pytest
@pytest.fixture()
def open_everyone():
print("打开浏览器")
yield
print("关闭浏览器")
用例模块中的代码如下:
import pytest
pytestmark = pytest.mark.usefixtures("open_everyone")
class TestFixture001:
def test_001(self):
print("登陆系统1")
def test_002(self):
print("登陆系统2")
场景五:@pytest.fixture(autouse="true")
让模块中的每个用例自动添加login为前置函数,无需将login作为参数传递给每个用例方法
import pytest
@pytest.fixture(autouse="true")
def login():
print("这是一个登陆方法")
class TestFixture001:
def test_001(self):
print("需要登陆1")
def test_002(self):
print("需要登陆2")
场景六:两个用例,第一个用例需要登录admin验证,第二个需要登录guest验证
import pytest
@pytest.fixture(params=[["admin","dlh12378612873"],["guest","dlh12378612873"]])
def login(request):
print("登录用户名{},登录密码{}".format(request.param[0],request.param[1]))
class TestFixture001:
def test_001(self,login):
print("需要登陆admin")
def test_002(self,login):
print("需要登陆guest")
四、pytest实用插件
插件的安装方式都为pip install [插件名],后面安装方式就不再赘述。
1.失败重跑:pytest-rerunfailures
用例失败时,重新运行5遍,每遍间隔时间为2秒。命令行执行:pytest test_rerunfailures.py --reruns 5 --reruns-delay 2 -vs
代码中实现如下:
import pytest
@pytest.mark.parametrize("a,b", [(1, 0)])
# 在方法中添加该装饰器,使用功能:用例失败时,重新运行5遍,用例执行时间间隔两秒。
# 如果方法中添加了该装饰器,命令行执行时,重新运行次数及间隔时间与装饰器配置不符,以装饰器的为准
@pytest.mark.flaky(reruns=5, reruns_delay=2)
def test_div(a, b):
print(a / b)
2.多重校验:pytest-assume
pytest自带的校验工具assert,如果用例中包含多个断言,第一个断言失败时,其他断言则不会在执行。在实际工作中,我们希望用例的每个断言都得到执行,此时就需要多重校验插件pytest-assume。
示例:第二个断言失败,第三个仍然执行,代码如下:
import pytest
# 第二条断言失败时,第三条依旧会校验
def test_assume():
pytest.assume(1 == 1)
pytest.assume(False)
pytest.assume(True)
3.指定用例执行顺序:pytest-ordering
在实际的工作过程中,我们会遇到需要按照一定顺序执行的用例,此时可以使用pytest-ordering来实现。如同一类中,有的用例指定了顺序,有的没有执行顺序,则先运行指定顺序的用例,之后再根据默认顺序执行剩余用例。
示例:指定用例按照一定顺序运行
import pytest
class TestOrder:
# 在该脚本中,test_a第三个执行,因为其余三条用例都指定了执行顺序
def test_a(self):
print("a")
# 该用例第二个执行
@pytest.mark.run(order=2)
def test_002(self):
print("002")
# 该用例第一个执行
@pytest.mark.run(order=1)
def test_001(self):
print("001")
# 该用例最后执行
@pytest.mark.run(order=-1)
def test_fianal(self):
print("fianal")
4.指定前后依赖运行用例:pytest-dependency
在实际工作共经常会遇到用例执行有依赖关系,比如订单系统,查看订单前必须要创建订单。
示例:依赖关系为先创建订单,再查看订单,然后删除订单。具体代码如下:
import pytest
class TestDepend:
"""
业务场景为新增/查看列表/删除
查看列表用例依赖新增用例
删除用例依赖查看列表用例
"""
# 定义用例的别名
@pytest.mark.dependency(name="create")
def test_create(self):
print("create")
# 定义用例的别名为list,需要依赖的用例create,
# 如果create执行失败,则list用例不会再执行
# 如果list执行失败,delete则不会再执行
@pytest.mark.dependency(name="list", depends=["create"])
def test_list(self):
print("list")
assert False
# 定义用例的别名为delete,需要依赖的用例为"create", "list"
@pytest.mark.dependency(name="delete", depends=["create", "list"])
def test_delete(self):
print("delete")
5.分布式执行用例:pytest-xdist
分布式执行用例的前提:所有的用例都可以单独执行,用例间无依赖关系。当我们有大量的用例需要执行时,就可以采用分布式执行用例,来降低执行用例的总时长。
命令行执行使用命令:pytest [目录或文件名] -n [线程数]
示例:使用5个线程执行test_xdist.py文件。在命令行输入命令 pytest test_xdist.py -n 5
from time import sleep
import pytest
class TestXdist:
@pytest.mark.parametrize("a", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
def test_001(self, a):
sleep(1)
print(a)
6.显示用例执行的进度条:pytest-sugar
安装成功后,在命令行运行用例时,显示进度条,具体效果如下图: