pytest之assert断言的具体使用
作者:阿刁阿 发布时间:2021-11-21 18:04:26
背景
本文总结使用pytest编写自动化测试时常用的assert断言。
说明
本文将从以下几点做总结:
为测试结果作断言
为断言不通过的结果添加说明信息
为预期异常作断言
为失败断言自定义说明信息
为测试结果作断言
在断言方面,pytest框架比其他类似的框架(比如unittest)更加简洁,易用,我想这是我选择pytest作为自动化测试框架之一的原因之一。
pytest的assert断言关键字支持使用python内置的assert表达式。可以理解为pytest的断言就是直接使用python自带的assert关键字。
python assert的概念:
Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。
我们可以在在assert后面添加任何符合python标准的表达式,如果表达式的值通过bool转换后等于False,则意味着断言结果为失败。
以下举例常用的表达式:
# ./test_case/test_func.py
import pytest
from func import *
class TestFunc:
def test_add_by_class(self):
assert add(2,3) == 5
def test_add_by_func_aaa():
assert 'a' in 'abc'
assert 'a' not in 'bbc'
something = True
assert something
something = False
assert not something
assert 1==1
assert 1!=2
assert 'a' is 'a'
assert 'a' is not 'b'
assert 1 < 2
assert 2 > 1
assert 1 <= 1
assert 1 >= 1
assert add(3,3) == 6
'''
# 以上全是合法的表达式且表达式的值都为True,所以测试结果为通过
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items
test_case/test_func.py::TestFunc::test_add_by_class PASSED [ 50%]
test_case/test_func.py::test_add_by_func_aaa PASSED [100%]
============================== 2 passed in 0.06s ==============================
[Finished in 1.8s]
'''
为断言不通过的结果添加说明信息
在编写测试时,为了提高易用性,我们想知道断言失败时的一些关于失败的原因等说明信息,assert也能满足该功能。
请看示例:
# ./test_case/test_func.py
import pytest
from func import *
class TestFunc:
def test_add_by_class(self):
assert add(2,3) == 5
def test_add_by_func_aaa():
assert add(3,3) == 5, "3+3应该等于6"
'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items
test_case/test_func.py::TestFunc::test_add_by_class PASSED [ 50%]
test_case/test_func.py::test_add_by_func_aaa FAILED [100%]
================================== FAILURES ===================================
____________________________ test_add_by_func_aaa _____________________________
def test_add_by_func_aaa():
> assert add(3,3) == 5, "3+3应该等于6"
E AssertionError: 3+3应该等于6
E assert 6 == 5
E -6
E +5
test_case\test_func.py:14: AssertionError
========================= 1 failed, 1 passed in 0.09s =========================
[Finished in 1.4s]
'''
为预期异常作断言
在某些测试用例中,比如异常测试用例,测试的结果必然是失败并应该爆出异常的。这时候自动化测试用例的期望结果就是该异常。如果期望结果等于该异常,那么测试用例执行通过,否则用例结果为失败。pytest提供为为预期异常作断言的方法:pytest.raises()。一般结合with上下文管理器使用。
使用示例:
# ./func.py
def add(a,b):
if isinstance(a,int) and isinstance(b,int):
return a+b
else:
raise NameError('数据类型错误')
# ./test_case/test_func.py
import pytest
from func import *
class TestFunc:
# 正常测试用例
def test_add_by_class(self):
assert add(2,3) == 5
# 异常测试用例,期望结果为爆出TypeError异常
def test_add_by_func_aaa():
with pytest.raises(TypeError):
add('3',4)
# ./run_test.py
import pytest
if __name__ == '__main__':
pytest.main(['-v'])
'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items
test_case/test_func.py::TestFunc::test_add_by_class PASSED [ 50%]
test_case/test_func.py::test_add_by_func_aaa PASSED [100%]
============================== 2 passed in 0.06s ==============================
[Finished in 1.4s]
'''
接下来看看没有爆出预期异常的示例:
# ./func.py
def add(a,b):
# 指定异常
raise NameError("天降异常")
if isinstance(a,int) and isinstance(b,int):
return a+b
else:
raise NameError('数据类型错误')
# ./test_case/test_func.py
import pytest
from func import *
'''
class TestFunc:
# 正常测试用例
def test_add_by_class(self):
assert add(2,3) == 5
'''
# 异常测试用例,期望结果为爆出TypeError异常
def test_add_by_func_aaa():
with pytest.raises(TypeError):
add('3',4)
# ./run_test.py
import pytest
if __name__ == '__main__':
pytest.main(['-v'])
'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 1 item
test_case/test_func.py::test_add_by_func_aaa FAILED [100%]
================================== FAILURES ===================================
____________________________ test_add_by_func_aaa _____________________________
def test_add_by_func_aaa():
with pytest.raises(TypeError):
> add('3',4)
test_case\test_func.py:14:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
a = '3', b = 4
def add(a,b):
# 指定异常
> raise NameError("天降异常")
E NameError: 天降异常
func.py:4: NameError
============================== 1 failed in 0.09s ==============================
[Finished in 1.4s]
'''
判定用例执行结果为失败。
上面我们只是断言了异常的类型。但有的时候我们想更进一步断言异常的说明信息,pytest也可以做到。with pytest.raises()执行结束后会生成一个ExceptionInfo的实例对象。该对象包含type , value, traceback属性。value属性就是我们需要的异常说明信息。
见示例:
# ./func.py
def add(a,b):
if isinstance(a,int) and isinstance(b,int):
return a+b
else:
raise TypeError('数据类型错误')
# ./test_case/test_func.py
import pytest
from func import *
class TestFunc:
# 正常测试用例
def test_add_by_class(self):
assert add(2,3) == 5
# 异常测试用例,期望结果为爆出TypeError异常
def test_add_by_func_aaa():
with pytest.raises(TypeError) as E:
add('3',4)
print(E.type)
print(E.value)
print(E.traceback)
# 加入该不通过断言为了查看stdout
assert 1 == 2
# ./run_test.py
import pytest
if __name__ == '__main__':
pytest.main(['-v'])
'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 2 items
test_case/test_func.py::TestFunc::test_add_by_class PASSED [ 50%]
test_case/test_func.py::test_add_by_func_aaa FAILED [100%]
================================== FAILURES ===================================
____________________________ test_add_by_func_aaa _____________________________
def test_add_by_func_aaa():
with pytest.raises(TypeError) as E:
add('3',4)
print(E.type)
print(E.value)
print(E.traceback)
> assert 1 == 2
E assert 1 == 2
E -1
E +2
test_case\test_func.py:18: AssertionError
---------------------------- Captured stdout call -----------------------------
<class 'TypeError'>
数据类型错误
[<TracebackEntry D:\Python3.7\project\pytest\test_case\test_func.py:14>, <TracebackEntry D:\Python3.7\project\pytest\func.py:6>]
========================= 1 failed, 1 passed in 0.10s =========================
[Finished in 1.4s]
'''
控制台输出的“Captured stdout call”就是异常的信息,包含类型,异常说明,异常跟踪信息。
可以通过assert断言这些信息。
也可以通过给pytest.raises()传入match关键字参数来完成E.value的断言,这里运用到的是python中正则表达式的原理。
示例:
该示例意味断言通过
def test_add_by_func_aaa():
with pytest.raises(TypeError, match=r'.*类型错误$') as E:
add('3',4)
该示例意味断言失败:
# 异常测试用例,期望结果为爆出TypeError异常
def test_add_by_func_aaa():
with pytest.raises(TypeError, match=r'.*正确$') as E:
add('3',4)
'''
During handling of the above exception, another exception occurred:
def test_add_by_func_aaa():
with pytest.raises(TypeError, match=r'.*正确$') as E:
> add('3',4)
E AssertionError: Pattern '.*正确$' not found in '数据类型错误'
test_case\test_func.py:14: AssertionError
'''
如果,某个测试用例可能出现不同的预期异常,只要爆出的异常在预期的几个异常之内,那么如何断言呢。解决方法很简单,原理和接口都没变,只是在pytest.raises()中传入异常类型的参数,从传入一个异常类型,改变为传入一个异常类型组成的元组。同样只是传入一个参数。
示例:
# ./func.py
def add(a,b):
raise NameError('名字错了')
if isinstance(a,int) and isinstance(b,int):
return a+b
else:
raise TypeError('数据类型错误')
# ./test_case/test_func.py
import pytest
from func import *
'''
class TestFunc:
# 正常测试用例
def test_add_by_class(self):
assert add(2,3) == 5
'''
# 异常测试用例,期望结果为爆出TypeError异常
def test_add_by_func_aaa():
with pytest.raises((TypeError,NameError),match=r'.*错.*$') as E:
add('3',4)
# ./run_test.py
import pytest
if __name__ == '__main__':
pytest.main(['-v'])
'''
============================= test session starts =============================
platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe
cachedir: .pytest_cache
rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini
plugins: allure-pytest-2.8.9, rerunfailures-8.0
collecting ... collected 1 item
test_case/test_func.py::test_add_by_func_aaa PASSED [100%]
============================== 1 passed in 0.04s ==============================
[Finished in 1.4s]
'''
为失败断言自定义说明信息
这种行为,相当于改变了pytest的运行方式,虽然只是一种锦上添花的改变。我们通过编写hook函数来改变pytest的行为。hook函数是pytest提供的,有很多,各个hook函数的详细定义应该参考pytest的官方文档。
为失败断言自定义说明信息是通过pytest_assertrepr_compare这个hook函数完成的。
先看没有编写pytest_assertrepr_compare这个hook函数时,默认的失败断言说明:
def test_add_by_func_aaa():
assert 'aaa' == 'bbb'
'''
================================== FAILURES ===================================
____________________________ test_add_by_func_aaa _____________________________
def test_add_by_func_aaa():
> assert 'aaa' == 'bbb'
E AssertionError: assert 'aaa' == 'bbb'
E - aaa
E + bbb
test_case\test_func.py:16: AssertionError
'''
再看编写pytest_assertrepr_compare这个hook函数后:
# ./conftest.py
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, str) and isinstance(right, str) and op == "==":
return ['两个字符串比较:',
' 值: %s != %s' % (left, right)]
# ./test_case/test_func.py
import pytest
def test_add_by_func_aaa():
assert 'aaa' == 'bbb'
'''
.F [100%]
================================== FAILURES ===================================
____________________________ test_add_by_func_aaa _____________________________
def test_add_by_func_aaa():
> assert 'aaa' == 'bbb'
E assert 两个字符串比较:
E 值: aaa != bbb
test_case\test_func.py:15: AssertionError
1 failed, 1 passed in 0.09s
[Finished in 1.5s]
'''
pytest还提供其他的hook函数,这些函数的作用就是用来改变pytest的运行方式和运行效果。所以编写第三方插件一般是使用这些hook函数。
来源:https://blog.csdn.net/lc_buzhidao/article/details/105177573


猜你喜欢
- 一、多表连接类型1. 笛卡尔积(交叉连接) 在MySQL中可以为CROSS JOIN或者省略CROSS即JOIN,或者使用',
- 注:可视化工具 Navicat 15 for MySQL当我们某天忘记我们的root用户密码,登录不到数据库时,只需要修改root用户的密码
- python中,为了方便字符串的大小写转换,为我们提供了三种方法:title()lower()upper()python title()方法
- async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案。目前,async / await这个特性已经是sta
- 最近,某水果手机厂在万众期待中开了一场没有发布万众期待的手机产品的发布会,发布了除手机外的其他一些产品,也包括最新的水果14系统。几天后,更
- 本文实例为大家分享了python实现支付宝当面付示的具体代码,供大家参考,具体内容如下一、配置信息准备登录蚂蚁金服开放平台:https://
- 前言 &nbs
- 前言之前一直认为UTF-8是万能的字符集问题解决方案,直到最近遇到这个问题。最近在做新浪微博的爬虫, 在存库的时候发现只要保持emoji表情
- 一、前言MySQL启动后,BufferPool就会被初始化,在你没有执行任何查询操作之前,BufferPool中的缓存页都是一块块空的内存,
- 简介:上文中已经介绍如何安装Pycharm已经环境变量的配置。现在软件已经安装成功,现在就开始动手做第一个Python项目。第一个“Hell
- 1. 确认已经安装了NT/2000和SQL Server的最新补丁程序,不用说大家应该已经安装好了,但是我觉得最好还是在这里提醒一
- 本文实例为大家分享了python实现局域网内聊天功能的具体代码,供大家参考,具体内容如下功能: 可以向局域网内开启接收信息功能的ip进行发送
- 本文实例为大家分享了Python深度优先算法生成迷宫,供大家参考,具体内容如下import random #warning: x and y
- 前言检测图像中对象颜色的一种简单方法是首先将图像从 RGB 转换为 HSV 颜色空间,然后使用一系列色调检测对象,这可以通过使用 OpenC
- Python中函数参数的定义主要有四种方式:1. F(arg1,arg2,…)这是最常见的定义方式,一个函数可以定义任意个参数,每个参数间用
- 在本节描述的示例代码,提供真实的例子来示范在 FileSystemObject 对象模式中可用的许多功能。该代码显示了如何一起使用对象模式的
- yaml简单介绍YAML是一种标记语言,它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、
- 本文实例为大家分享了Python实现俄罗斯方块游戏的具体代码,供大家参考,具体内容如下玩法:童年经典,普通模式没啥意思,小时候我们都是玩加速
- 这篇文章主要介绍了python多进程并发demo实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- Stochastic Depth论文:Deep Networks with Stochastic Depth本文的正则化针对于ResNet中