背景

本文总结使用pytest编写自动化测试时常用的assert断言。

说明

本文将从以下几点做总结:

  1. 为测试结果作断言
  2. 为断言不通过的结果添加说明信息
  3. 为预期异常作断言
  4. 为失败断言自定义说明信息

为测试结果作断言

在断言方面,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函数。

到此这篇关于pytest之assert断言的具体使用的文章就介绍到这了,更多相关pytest assert断言内容请搜索程序员的世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员的世界!

pytest之assert断言的具体使用的更多相关文章

  1. Python opencv操作深入详解

    直接读取图片def display_img(file="p.jpeg"):img = cv.imread(file)print (img.shape)cv.imshow('image',img)cv.waitKey(0)cv.destroyAllWindows()读取灰度图片de......

  2. 【Python爬虫】:使用高性能异步多进程爬虫获取豆瓣电影Top250

    在本篇博文当中,将会教会大家如何使用高性能爬虫,快速爬取并解析页面当中的信息。一般情况下,如果我们请求网页的次数太多,每次都要发出一次请求,进行串行执行的话,那么请求将会占用我们大量的时间,这样得不偿失。因此我们可以i使用高性能爬虫,也就是采用多进程,异步的方式对数据进行爬取和解析,这样就可以在更快......

  3. python,selenium爬取微博热搜存入Mysql

    python爬取微博热搜存入Mysql 最终的效果 使用的库 目标分析 一:得到数据 二:链接......

  4. python使用scapy模块实现ARP扫描的过程

    前言上篇文章主要写了利用scapy实现ping扫描,这篇文章主要是利用scapy模块实现内网ARP扫描实现过程上篇文章中介绍了通过scapy来伪造包,那么ARP包的伪造过程这里不再详述,说一工具的简单整体流程1.首先,实现单个IP的ARP包的发送和接收2.其次,实现多进程同时多个ARP包的发送和接收......

  5. django中显示字符串的实例方法

    1、quicktool/view.py文件修改视图函数index(),渲染一个home.html模板,在视图中传递一个字符串名称是 string 到模板 home.htmldef index(request):string = u'这是一个由Django2.1.7版本写出的视图内容!'return ......

  6. python pandas合并Sheet,处理列乱序和出现Unnamed列的解决

    使用python中的pandas,xlrd,openpyxl库完成合并excel中指定sheet的操作# -*- coding: UTF-8 -*- import xlrdimport pandas as pdfrom pandas import DataFramefrom openpyxl imp......

  7. python实现多图像叠置输出

    本文实例为大家分享了python实现多图像叠置输出,供大家参考,具体内容如下代码from PIL import Imagedef add_alpha_channel(img):img = Image.open(img)img = img.convert('RGBA')# 更改图像透明度# facto......

  8. Python 有可能删除 GIL 吗?

    我们知道,在 CPython 中,有一个全局解释器锁,英文叫 global interpreter lock,简称 GIL,是一个互斥锁,用来保护 Python 世界里的对象,防止同一时刻多个线程执行 Python 的字节码,从而确保线程安全,这导致了 Python 的线程无法利用多核 CPU 的优......

  9. 爬虫-urllib模块的使用

    urllib是Python中请求url连接的官方标准库,在Python3中将Python2中的urllib和urllib2整合成了urllib。urllib中一共有四个模块,分别如下:request:主要负责构造和发起网络请求,定义了适用于在各种复杂情况下打开 URL (主要为 HTTP) 的函数和......

  10. Python 实现自动化Excel报表

    好几个月没有写笔记了, 并非没有积累, 而是有点懒了. 想想还是要续上, 作为工作成长的一部分哦.最近有做一些报表, 但一直找不到一个合适的报表工具, 又实在不想写前端, 后端... 思来想去, 感觉 Excel 就一定程度上能做可视化的, 除了不能动态交互外, 其他都挺好. 今天分享的就是一个关于......

随机推荐

  1. Java 找不到或无法加载主类的修复方法

    有时,当我们运行Java程序时,我们可能会看到“找不到或无法加载主类”。原因很容易猜测:JVM找不到主类并给出了这个错误。但是为什么不能呢?在本文中,我们将讨论找不到主类的可能原因。另外,我们将看看如何修复它们。示例程序我们将从HelloWorld程序开始:public class HelloWor......

  2. python中yield的用法详解

    首先我要吐槽一下,看程序的过程中遇见了yield这个关键字,然后百度的时候,发现没有一个能简单的让我懂的,讲起来真TM的都是头头是道,什么参数,什么传递的,还口口声声说自己的教程是最简单的,最浅显易懂的,我就想问没有有考虑过读者的感受。接下来是正题:首先,如果你还没有对yield有个初步分认识,那么......

  3. python3 如何读取python2的npy文件

    python3读取python2打包的npy文件会报错,原因是编码方式不同,所以只要在读取的时候加上编码方式即可。解决方法docs_train = np.load('./data/20news_clean/train.txt.npy', allow_pickle=True, encoding='by......

  4. spring使用RedisTemplate操作Redis数据库

    一.什么是RedisRedis是一个非关系型数据库,具有很高的存取性能,一般用作缓存数据库,减少正常存储数据库的压力。Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。下面......

  5. C# 调用FFmpeg处理音视频的示例

    FFmpeg 开源、跨平台、体积小、功能强大,提供了录制、转换以及流化音视频的完整解决方案。官网:https://www.ffmpeg.org/百科:https://baike.baidu.com/item/ffmpeg/2665727?fr=aladdinFFmpeg 应用非常广泛,可以用......

  6. Unity 实现删除missing脚本组件

    通过Resources.FindObjectsOfTypeAll查找所有GameObject,然后通过.hideFlags == HideFlags.None判断是否为存在于Hierarchy面板。(此为编辑器脚本)详细代码:/************************************......

  7. Python统计列表元素出现次数的方法

    1. 引言在使用Python的时候,通常会出现如下场景:array = [1, 2, 3, 3, 2, 1, 0, 2]获取array中元素的出现次数比如,上述列表中:0出现了1次,1出现了2次,2出现了3次,3出现了2次。本文阐述了Python获取元素出现次数的几种方法。点击获取完整代码。2. 方......

  8. docker中实现安装php拓展步骤讲解

    一:php核心拓展包安装在docker中安装的php有一些核心的拓展包,他们一般放置在/usr/src/php/ext/目录下如下:docker exec -it php /bin/bash #进入php容器cd /usr/src/php/ext/dir如下展示的就是php的核心拓展包安装方式:#相......

  9. 浅谈在Java中JSON的多种使用方式

    1. 常用的JSON转换JSONObject 转 JSON 字符串JSONObject json = new JSONObject();jsonObject.put("name", "test");String str = JSONObject.toJSONS......

  10. std::async的使用总结

    C++98标准中并没有线程库的存在,直到C++11中才终于提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作、原子操作等类。多线程库对应的头文件是#include ,类名为std::thread。然而线程毕竟是比较贴近系统的东西,使用起来仍然不是很方便,特别是线程同步及获取线程运行结......