pytest框架进阶自学系列 | 测试用例的断言管理

书籍来源:房荔枝 梁丽丽《pytest框架与自动化测试应用》

一边学习一边整理老师的课程内容及实验笔记,并与大家分享,侵权即删,谢谢支持!

附上汇总贴:pytest框架进阶自学系列 | 汇总_热爱编程的通信人的博客-CSDN博客


什么是断言

测试的本质是验证预期结果与实际结果是否一致,那么在语言或框架中如何验证呢?通过断言的方式,即通过断言比对两个结果是否一致。

pytest的断言使用Python中的assert函数。不同于其他框架单独设置一套assertEqual等函数,pytest使用的就是Python自带的assert函数,使应用更加灵活。也就是编程语言可以判断什么,测试验证就可以判断什么。

断言的时机

到底什么时候使用断言比较好呢?可以从开发和测试两个方面为大家简单介绍断言的时机及不使用断言的情况。

开发人员 通常在下面时机点添加断言(本书以测试为主,对开发只做简单介绍):

  • 防御性地编程,也就是不满足条件时直接显示失败。示例代码如下: 上述代码,当target为x时运行run_x_code();当target为y时运行run_y_code();当target为其他情况时运行else中的代码,也就是target如果为z,则运行run_z_code();如果不是上述内容,会直接引起一个简单而又直接的失败。
  • 运行时对程序逻辑检测;
  • 合约性检查(例如前置条件、后置条件); 例如:“如果你传给笔者一个非空字符串,笔者保证返回首字母转换成大写的字符串。”
  • 程序中的常量;
  • 检查文档。

测试人员 通常对所有需要比对的需求及功能进行验证时使用断言,下面分析一下断言的主要时机点:

  • 验证页面是否跳转到正确的页面;
  • 验证计算结果与正确结果是否一致;
  • 验证类型是否一致;
  • 验证添加功能是否成功添加;
  • 验证接口返回的JSON数据是否正确;
  • 验证接口返回的状态码是否正确;
  • 验证接口性能是否在范围内;
  • 验证元素是否已被选择;
  • 验证元素是否为不可操作;
  • 验证返回值是否与预期一致。

不使用断言的几种情况:

  • 不要用于测试用户提供的数据,或者那些在所有情况下需要改变检查的地方;
  • 不要用于检查你认为在通常使用中可能失败的地方;
  • 用户绝看不到一个AssertionError,如果看到了,那就是必须修复的缺陷;
  • 特别地不要因为断言只比一个明确的测试加一个触发异常简单而使用它。断言不是懒惰的代码编写者的捷径;
  • 不要将断言用于公共函数库输入参数的检查,因为用户不能控制调用者,并且不能保证它不破坏函数的执行;
  • 不要将断言用于用户期望修改的任何地方。换句话,用户没有任何理由在产品代码中捕获一个AssertionError异常;
  • 不要太多使用断言,它们将使代码变得晦涩难懂。

断言的分类与使用

pytest使用Python的assert函数,支持显示常见的Python子表达式的值,包括:调用、属性、比较、二进制和一元运算符。也就是Python语言有多少种判断,assert就有多少种断言,包括断言函数返回值是否相等、断言表达式执行后的结果是否正确、各种不同比较运算符的断言、比较各种数据类型(字符串、列表、字典、集合)不同的断言。

  1. 验证函数返回值是否相等

断言函数返回了某个值。如果此断言失败,则将看到函数调用的返回值。

示例代码如下:

def f():
    return 3

def test_f():
    assert f() == 4

执行效果如下。

def f():
    return 3

def test_f():
    assert f() == 4

2. 表达式断言

通过表达式运算后的结果进行真假判断。

示例代码如下,如果值是偶数,不会报错。如果值是奇数,则表示断言失败,此时就会显示断言后面的提示信息:

a = 5
def test_exp():
    assert a % 2 == 0, "你的值是奇数,它应该是偶数"

执行结果如图2-3所示,a %2==0表达式后面的参数表示当出错时的提示信息:"你的值是奇数,它应该是偶数",F表示失败。

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --target test_test.py::test_exp 
Testing started at 8:35 ...
Launching pytest with arguments test_test.py::test_exp in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 1 item

test_test.py::test_exp FAILED                                            [100%]
test_test.py:1 (test_exp)
1 != 0

Expected :0
Actual   :1
<Click to see difference>

def test_exp():
>       assert a % 2 == 0, "你的值是奇数,它应该是偶数"
E       AssertionError: 你的值是奇数,它应该是偶数
E       assert 1 == 0
E         +1
E         -0

test_test.py:3: AssertionError


================================== FAILURES ===================================
__________________________________ test_exp ___________________________________

    def test_exp():
>       assert a % 2 == 0, "你的值是奇数,它应该是偶数"
E       AssertionError: 你的值是奇数,它应该是偶数
E       assert 1 == 0
E         +1
E         -0

test_test.py:3: AssertionError
=========================== short test summary info ===========================
FAILED test_test.py::test_exp - AssertionError: 你的值是奇数,它应该是偶数
============================== 1 failed in 0.11s ==============================

Process finished with exit code 1

3. 比较类型断言

比较类型断言包括相等、不等、包含、不包含、真、假等断言,以及长列表中的详细断言。下面通过代码分别演示。test_lessmore测试方法用于比较小于或等于及大于或等于。test_true测试方法用于比较真假,通过1<3返回值再与真假比较。test_not_eq测试方法用于比较不等于,以及判断是否不在其中。

示例代码如下:

def test_lessmore():
    assert 1 <= 3
    assert 5 >= 1.3

def test_true():
    assert (1<3) is True
    assert (5>3) is False

def test_in_dict():
    x = 'linda1'
    assert x in "I'm linda"

def test_not_eq():
    assert 1 != 2
    assert ['linda'] not in ['linda', 'tom', 'seven']

def test_long_list():
    x = [str(i) for i in range(100)]
    y = [str(i) for i in range(0, 100, 2)]
    assert x == y

def test_long():
    assert [12]*50 == [11]*50

Python的长列表比较智能,在test_long_list测试方法中:列表x的元素为0~100的所有自然数,列表y的元素为0~100的所有偶数,它们之间的不同会有50个,直接提示'1'!='2'。test_long测试方法将50个12和50个11进行比较,直接提示12!=11。

代码执行结果如下,仔细体会。

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_assert_3.py 
Testing started at 8:44 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_assert_3.py in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 6 items

test_assert_3.py::test_lessmore PASSED                                   [ 16%]
test_assert_3.py::test_true FAILED                                       [ 33%]
test_assert_3.py:4 (test_true)
True != False

Expected :False
Actual   :True
<Click to see difference>

def test_true():
        assert (1<3) is True
>       assert (5>3) is False
E       assert (5 > 3) is False

test_assert_3.py:7: AssertionError

test_assert_3.py::test_in_dict FAILED                                    [ 50%]
test_assert_3.py:8 (test_in_dict)
'linda1' != "I'm linda"

Expected :"I'm linda"
Actual   :'linda1'
<Click to see difference>

def test_in_dict():
        x = 'linda1'
>       assert x in "I'm linda"
E       assert 'linda1' in "I'm linda"

test_assert_3.py:11: AssertionError

test_assert_3.py::test_not_eq PASSED                                     [ 66%]
test_assert_3.py::test_long_list FAILED                                  [ 83%]
test_assert_3.py:16 (test_long_list)
['0',
 '1',
 '2',
 '3',
 '4',
 '5',
 '6',
 '7',
 '8',
 '9',
 '10',
 '11',
 '12',
 '13',
 '14',
 '15',
 '16',
 '17',
 '18',
 '19',
 '20',
 '21',
 '22',
 '23',
 '24',
 '25',
 '26',
 '27',
 '28',
 '29',
 '30',
 '31',
 '32',
 '33',
 '34',
 '35',
 '36',
 '37',
 '38',
 '39',
 '40',
 '41',
 '42',
 '43',
 '44',
 '45',
 '46',
 '47',
 '48',
 '49',
 '50',
 '51',
 '52',
 '53',
 '54',
 '55',
 '56',
 '57',
 '58',
 '59',
 '60',
 '61',
 '62',
 '63',
 '64',
 '65',
 '66',
 '67',
 '68',
 '69',
 '70',
 '71',
 '72',
 '73',
 '74',
 '75',
 '76',
 '77',
 '78',
 '79',
 '80',
 '81',
 '82',
 '83',
 '84',
 '85',
 '86',
 '87',
 '88',
 '89',
 '90',
 '91',
 '92',
 '93',
 '94',
 '95',
 '96',
 '97',
 '98',
 '99'] != ['0',
 '2',
 '4',
 '6',
 '8',
 '10',
 '12',
 '14',
 '16',
 '18',
 '20',
 '22',
 '24',
 '26',
 '28',
 '30',
 '32',
 '34',
 '36',
 '38',
 '40',
 '42',
 '44',
 '46',
 '48',
 '50',
 '52',
 '54',
 '56',
 '58',
 '60',
 '62',
 '64',
 '66',
 '68',
 '70',
 '72',
 '74',
 '76',
 '78',
 '80',
 '82',
 '84',
 '86',
 '88',
 '90',
 '92',
 '94',
 '96',
 '98']

<Click to see difference>

def test_long_list():
        x = [str(i) for i in range(100)]
        y = [str(i) for i in range(0, 100, 2)]
>       assert x == y
E       AssertionError: assert ['0',\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9',\n '10',\n '11',\n '12',\n '13',\n '14',\n '15',\n '16',\n '17',\n '18',\n '19',\n '20',\n '21',\n '22',\n '23',\n '24',\n '25',\n '26',\n '27',\n '28',\n '29',\n '30',\n '31',\n '32',\n '33',\n '34',\n '35',\n '36',\n '37',\n '38',\n '39',\n '40',\n '41',\n '42',\n '43',\n '44',\n '45',\n '46',\n '47',\n '48',\n '49',\n '50',\n '51',\n '52',\n '53',\n '54',\n '55',\n '56',\n '57',\n '58',\n '59',\n '60',\n '61',\n '62',\n '63',\n '64',\n '65',\n '66',\n '67',\n '68',\n '69',\n '70',\n '71',\n '72',\n '73',\n '74',\n '75',\n '76',\n '77',\n '78',\n '79',\n '80',\n '81',\n '82',\n '83',\n '84',\n '85',\n '86',\n '87',\n '88',\n '89',\n '90',\n '91',\n '92',\n '93',\n '94',\n '95',\n '96',\n '97',\n '98',\n '99'] == ['0',\n '2',\n '4',\n '6',\n '8',\n '10',\n '12',\n '14',\n '16',\n '18',\n '20',\n '22',\n '24',\n '26',\n '28',\n '30',\n '32',\n '34',\n '36',\n '38',\n '40',\n '42',\n '44',\n '46',\n '48',\n '50',\n '52',\n '54',\n '56',\n '58',\n '60',\n '62',\n '64',\n '66',\n '68',\n '70',\n '72',\n '74',\n '76',\n '78',\n '80',\n '82',\n '84',\n '86',\n '88',\n '90',\n '92',\n '94',\n '96',\n '98']
E         At index 1 diff: '1' != '2'
E         Left contains 50 more items, first extra item: '50'
E         Full diff:
E           [
E            '0',
E         +  '1',
E            '2',
E         +  '3',
E            '4',
E         +  '5',
E            '6',
E         +  '7',
E            '8',
E         +  '9',
E            '10',
E         +  '11',
E            '12',
E         +  '13',
E            '14',
E         +  '15',
E            '16',
E         +  '17',
E            '18',
E         +  '19',
E            '20',
E         +  '21',
E            '22',
E         +  '23',
E            '24',
E         +  '25',
E            '26',
E         +  '27',
E            '28',
E         +  '29',
E            '30',
E         +  '31',
E            '32',
E         +  '33',
E            '34',
E         +  '35',
E            '36',
E         +  '37',
E            '38',
E         +  '39',
E            '40',
E         +  '41',
E            '42',
E         +  '43',
E            '44',
E         +  '45',
E            '46',
E         +  '47',
E            '48',
E         +  '49',
E            '50',
E         +  '51',
E            '52',
E         +  '53',
E            '54',
E         +  '55',
E            '56',
E         +  '57',
E            '58',
E         +  '59',
E            '60',
E         +  '61',
E            '62',
E         +  '63',
E            '64',
E         +  '65',
E            '66',
E         +  '67',
E            '68',
E         +  '69',
E            '70',
E         +  '71',
E            '72',
E         +  '73',
E            '74',
E         +  '75',
E            '76',
E         +  '77',
E            '78',
E         +  '79',
E            '80',
E         +  '81',
E            '82',
E         +  '83',
E            '84',
E         +  '85',
E            '86',
E         +  '87',
E            '88',
E         +  '89',
E            '90',
E         +  '91',
E            '92',
E         +  '93',
E            '94',
E         +  '95',
E            '96',
E         +  '97',
E            '98',
E         +  '99',
E           ]

test_assert_3.py:20: AssertionError

test_assert_3.py::test_long FAILED                                       [100%]
test_assert_3.py:21 (test_long)
[12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12,
 12] != [11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11,
 11]

<Click to see difference>

def test_long():
>       assert [12]*50 == [11]*50
E       assert [12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12] == [11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11]
E         At index 0 diff: 12 != 11
E         Full diff:
E           [
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E           ]

test_assert_3.py:23: AssertionError


================================== FAILURES ===================================
__________________________________ test_true __________________________________

    def test_true():
        assert (1<3) is True
>       assert (5>3) is False
E       assert (5 > 3) is False

test_assert_3.py:7: AssertionError
________________________________ test_in_dict _________________________________

    def test_in_dict():
        x = 'linda1'
>       assert x in "I'm linda"
E       assert 'linda1' in "I'm linda"

test_assert_3.py:11: AssertionError
_______________________________ test_long_list ________________________________

    def test_long_list():
        x = [str(i) for i in range(100)]
        y = [str(i) for i in range(0, 100, 2)]
>       assert x == y
E       AssertionError: assert ['0',\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9',\n '10',\n '11',\n '12',\n '13',\n '14',\n '15',\n '16',\n '17',\n '18',\n '19',\n '20',\n '21',\n '22',\n '23',\n '24',\n '25',\n '26',\n '27',\n '28',\n '29',\n '30',\n '31',\n '32',\n '33',\n '34',\n '35',\n '36',\n '37',\n '38',\n '39',\n '40',\n '41',\n '42',\n '43',\n '44',\n '45',\n '46',\n '47',\n '48',\n '49',\n '50',\n '51',\n '52',\n '53',\n '54',\n '55',\n '56',\n '57',\n '58',\n '59',\n '60',\n '61',\n '62',\n '63',\n '64',\n '65',\n '66',\n '67',\n '68',\n '69',\n '70',\n '71',\n '72',\n '73',\n '74',\n '75',\n '76',\n '77',\n '78',\n '79',\n '80',\n '81',\n '82',\n '83',\n '84',\n '85',\n '86',\n '87',\n '88',\n '89',\n '90',\n '91',\n '92',\n '93',\n '94',\n '95',\n '96',\n '97',\n '98',\n '99'] == ['0',\n '2',\n '4',\n '6',\n '8',\n '10',\n '12',\n '14',\n '16',\n '18',\n '20',\n '22',\n '24',\n '26',\n '28',\n '30',\n '32',\n '34',\n '36',\n '38',\n '40',\n '42',\n '44',\n '46',\n '48',\n '50',\n '52',\n '54',\n '56',\n '58',\n '60',\n '62',\n '64',\n '66',\n '68',\n '70',\n '72',\n '74',\n '76',\n '78',\n '80',\n '82',\n '84',\n '86',\n '88',\n '90',\n '92',\n '94',\n '96',\n '98']
E         At index 1 diff: '1' != '2'
E         Left contains 50 more items, first extra item: '50'
E         Full diff:
E           [
E            '0',
E         +  '1',
E            '2',
E         +  '3',
E            '4',
E         +  '5',
E            '6',
E         +  '7',
E            '8',
E         +  '9',
E            '10',
E         +  '11',
E            '12',
E         +  '13',
E            '14',
E         +  '15',
E            '16',
E         +  '17',
E            '18',
E         +  '19',
E            '20',
E         +  '21',
E            '22',
E         +  '23',
E            '24',
E         +  '25',
E            '26',
E         +  '27',
E            '28',
E         +  '29',
E            '30',
E         +  '31',
E            '32',
E         +  '33',
E            '34',
E         +  '35',
E            '36',
E         +  '37',
E            '38',
E         +  '39',
E            '40',
E         +  '41',
E            '42',
E         +  '43',
E            '44',
E         +  '45',
E            '46',
E         +  '47',
E            '48',
E         +  '49',
E            '50',
E         +  '51',
E            '52',
E         +  '53',
E            '54',
E         +  '55',
E            '56',
E         +  '57',
E            '58',
E         +  '59',
E            '60',
E         +  '61',
E            '62',
E         +  '63',
E            '64',
E         +  '65',
E            '66',
E         +  '67',
E            '68',
E         +  '69',
E            '70',
E         +  '71',
E            '72',
E         +  '73',
E            '74',
E         +  '75',
E            '76',
E         +  '77',
E            '78',
E         +  '79',
E            '80',
E         +  '81',
E            '82',
E         +  '83',
E            '84',
E         +  '85',
E            '86',
E         +  '87',
E            '88',
E         +  '89',
E            '90',
E         +  '91',
E            '92',
E         +  '93',
E            '94',
E         +  '95',
E            '96',
E         +  '97',
E            '98',
E         +  '99',
E           ]

test_assert_3.py:20: AssertionError
__________________________________ test_long __________________________________

    def test_long():
>       assert [12]*50 == [11]*50
E       assert [12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12,\n 12] == [11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11,\n 11]
E         At index 0 diff: 12 != 11
E         Full diff:
E           [
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E         -  11,
E         ?   ^
E         +  12,
E         ?   ^
E           ]

test_assert_3.py:23: AssertionError
=========================== short test summary info ===========================
FAILED test_assert_3.py::test_true - assert (5 > 3) is False
FAILED test_assert_3.py::test_in_dict - assert 'linda1' in "I'm linda"
FAILED test_assert_3.py::test_long_list - AssertionError: assert ['0',\n '1',...
FAILED test_assert_3.py::test_long - assert [12,\n 12,\n 12,\n 12,\n 12,\n 12...
========================= 4 failed, 2 passed in 0.38s =========================

Process finished with exit code 1

4.不同数据类型的比较断言

pytest当遇到比较断言时,它提供了具有丰富细节且差异化的支持。同时,当断言失败时,pytest也提供了非常人性化的失败说明,中间往往夹杂着相应变量的introspection(自己反省)信息,这个被称为断言的自省。那么,pytest是如何做到的呢?首先pytest发现测试模块,然后引入它们,与此同时,pytest会复写断言语句,但是,不是测试模块的断言语句并不会被复写。

下面情况执行时有着丰富的细节比对:

(1)比较字符串:显示上下文差异。

(2)比较列表:显示第一个失败的索引。

(3)比较字典:显示不同的条目。

(4)比较集合:显示不同的元素。

def test_long_str_comparison():
    str3 = 'abcdef'
    str4 = 'adcdef'
    assert str3 == str4

def test_eq_list():
    assert [0,1,2] == [0,1,3]

def test_dict_comparison():
    dict1 = {
        'name': 'linda',
        'age': 18,
    }
    dict2 = {
        'name': 'linda',
        'age': 88,
    }
    assert dict1 == dict2

def test_set_comparison():
    set1 = set("1308")
    set2 = set("8035")
    assert set1 == set2

执行结果如图2-4~图2-7所示,分别比较的是字符串、列表、字典和集合,比对的细节很明确。

字符串的比较会具体到每个不同的字符,如图2-4所示。

列表中元素的比较会具体到每个索引,如图2-5所示。

字典类型的比较会具体到每个键值,相同的不显示,只显示不同的键,如图2-6所示。

集合类型会从左右两边显示不同,如图2-7所示。

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_assert_1.py 
Testing started at 8:55 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_assert_1.py in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 4 items

test_assert_1.py::test_long_str_comparison FAILED                        [ 25%]
test_assert_1.py:0 (test_long_str_comparison)
'abcdef' != 'adcdef'

Expected :'adcdef'
Actual   :'abcdef'
<Click to see difference>

def test_long_str_comparison():
        str3 = 'abcdef'
        str4 = 'adcdef'
>       assert str3 == str4
E       AssertionError: assert 'abcdef' == 'adcdef'
E         - adcdef
E         ?  ^
E         + abcdef
E         ?  ^

test_assert_1.py:4: AssertionError

test_assert_1.py::test_eq_list FAILED                                    [ 50%]
test_assert_1.py:5 (test_eq_list)
[0, 1, 2] != [0, 1, 3]

Expected :[0, 1, 3]
Actual   :[0, 1, 2]
<Click to see difference>

def test_eq_list():
>       assert [0,1,2] == [0,1,3]
E       assert [0, 1, 2] == [0, 1, 3]
E         At index 2 diff: 2 != 3
E         Full diff:
E         - [0, 1, 3]
E         ?        ^
E         + [0, 1, 2]
E         ?        ^

test_assert_1.py:7: AssertionError

test_assert_1.py::test_dict_comparison FAILED                            [ 75%]
test_assert_1.py:8 (test_dict_comparison)
{'age': 18, 'name': 'linda'} != {'age': 88, 'name': 'linda'}

Expected :{'age': 88, 'name': 'linda'}
Actual   :{'age': 18, 'name': 'linda'}
<Click to see difference>

def test_dict_comparison():
        dict1 = {
            'name': 'linda',
            'age': 18,
        }
        dict2 = {
            'name': 'linda',
            'age': 88,
        }
>       assert dict1 == dict2
E       AssertionError: assert {'age': 18, 'name': 'linda'} == {'age': 88, 'name': 'linda'}
E         Common items:
E         {'name': 'linda'}
E         Differing items:
E         {'age': 18} != {'age': 88}
E         Full diff:
E         - {'age': 88, 'name': 'linda'}
E         ?         ^
E         + {'age': 18, 'name': 'linda'}
E         ?         ^

test_assert_1.py:18: AssertionError

test_assert_1.py::test_set_comparison FAILED                             [100%]
test_assert_1.py:19 (test_set_comparison)
{'0', '8', '3', '1'} != {'5', '3', '8', '0'}

Expected :{'5', '3', '8', '0'}
Actual   :{'0', '8', '3', '1'}
<Click to see difference>

def test_set_comparison():
        set1 = set("1308")
        set2 = set("8035")
>       assert set1 == set2
E       AssertionError: assert {'0', '8', '3', '1'} == {'5', '3', '8', '0'}
E         Extra items in the left set:
E         '1'
E         Extra items in the right set:
E         '5'
E         Full diff:
E         - {'5', '3', '8', '0'}
E         + {'0', '8', '3', '1'}

test_assert_1.py:23: AssertionError


================================== FAILURES ===================================
__________________________ test_long_str_comparison ___________________________

    def test_long_str_comparison():
        str3 = 'abcdef'
        str4 = 'adcdef'
>       assert str3 == str4
E       AssertionError: assert 'abcdef' == 'adcdef'
E         - adcdef
E         ?  ^
E         + abcdef
E         ?  ^

test_assert_1.py:4: AssertionError
________________________________ test_eq_list _________________________________

    def test_eq_list():
>       assert [0,1,2] == [0,1,3]
E       assert [0, 1, 2] == [0, 1, 3]
E         At index 2 diff: 2 != 3
E         Full diff:
E         - [0, 1, 3]
E         ?        ^
E         + [0, 1, 2]
E         ?        ^

test_assert_1.py:7: AssertionError
____________________________ test_dict_comparison _____________________________

    def test_dict_comparison():
        dict1 = {
            'name': 'linda',
            'age': 18,
        }
        dict2 = {
            'name': 'linda',
            'age': 88,
        }
>       assert dict1 == dict2
E       AssertionError: assert {'age': 18, 'name': 'linda'} == {'age': 88, 'name': 'linda'}
E         Common items:
E         {'name': 'linda'}
E         Differing items:
E         {'age': 18} != {'age': 88}
E         Full diff:
E         - {'age': 88, 'name': 'linda'}
E         ?         ^
E         + {'age': 18, 'name': 'linda'}
E         ?         ^

test_assert_1.py:18: AssertionError
_____________________________ test_set_comparison _____________________________

    def test_set_comparison():
        set1 = set("1308")
        set2 = set("8035")
>       assert set1 == set2
E       AssertionError: assert {'0', '8', '3', '1'} == {'5', '3', '8', '0'}
E         Extra items in the left set:
E         '1'
E         Extra items in the right set:
E         '5'
E         Full diff:
E         - {'5', '3', '8', '0'}
E         + {'0', '8', '3', '1'}

test_assert_1.py:23: AssertionError
=========================== short test summary info ===========================
FAILED test_assert_1.py::test_long_str_comparison - AssertionError: assert 'a...
FAILED test_assert_1.py::test_eq_list - assert [0, 1, 2] == [0, 1, 3]
FAILED test_assert_1.py::test_dict_comparison - AssertionError: assert {'age'...
FAILED test_assert_1.py::test_set_comparison - AssertionError: assert {'0', '...
============================== 4 failed in 0.52s ==============================

Process finished with exit code 1

触发一个指定异常的断言

在进行异常测试时,会有这样的场景,程序希望在某时某地抛出一个指定的异常,如果的确抛出这个指定的异常,则程序是正确的。如果抛出的异常不是指定的那个异常或者不抛出异常,则表示程序是错误的。

使用raises引起一个指定的异常,再通过测试方法检查代码是否可抛出这个异常,如果抛出此异常,则表示程序是正确的,如果不抛出或者抛出的不正确,则表示程序是错误的。这样我们就可以检查代码是否抛出一个指定的异常。

引起一个解释器请求退出的异常,通过test_mytest测试方法实现断言并判断是否是指定的异常,示例代码如下:

import pytest

def f():
    raise SystemExit(1)

def test_mytest():
    with pytest.raises(SystemExit):
        f()

运行的结果是正常的,但如果把异常的类型修改就会出现执行测试不通过。大家可以动手试试,具体执行自己体会。

import pytest

def f():
    raise SystemExit(1)

def test_mytest():
    with pytest.raises(ValueError):
        f()

同时程序可以在抛出指定异常时,断言属性中的值是否正确。

其中,excinfo是ExceptionInfo的一个实例,它封装了异常的信息。常用的属性包括:.type、.value和.traceback。

import pytest

def myfunc():
    raise ValueError("返回40013支付错误")

def test_match():
    with pytest.raises(ValueError) as excinfo:
        myfunc()
    assert '40013' in str(excinfo.value)

注意:在上下文管理器的作用域中,raises代码必须是最后一行,否则其后面的代码将不会被执行。

所以,如果上述例子缩进到与函数调用为同一个层级,则测试将永远成功,代码如下:

import pytest

def myfunc():
    raise ValueError("返回40013支付错误")

def test_match():
    with pytest.raises(ValueError) as excinfo:
        myfunc()
        assert '456' in str(excinfo.value)

这是因为assert '456' in str(excinfo.value)并不会被执行。

也可以给pytest.raises()传递一个关键字参数match,来测试异常的字符串表示str(excinfo.value)是否符合给定的正则表达式(和Unittest中的TestCase.assertRaisesRegexp方法类似),代码如下:

import pytest

def myfunc():
    raise ValueError("返回40013支付错误")

def test_match():
    with pytest.raises((ValueError, RuntimeError),match=r'.*40011.*'):
        myfunc()

pytest实际调用的是re.search()方法,用来进行上述检查,match=r'.*40011.*',r表示原始,*表示所有,这个正则表达式的意思是字符串中包含40011,同时pytest.raises()也支持检查多个期望异常(以元组的形式传递参数),这时只需触发其中任意一个。

把要断言的属性内容写成40011而不是40013,执行结果如下:

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_test.py 
Testing started at 9:14 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_test.py in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 1 item

test_test.py::test_match FAILED                                          [100%]
test_test.py:5 (test_match)
def test_match():
        with pytest.raises((ValueError, RuntimeError),match=r'.*40011.*'):
>           myfunc()

test_test.py:8: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def myfunc():
>       raise ValueError("返回40013支付错误")
E       ValueError: 返回40013支付错误

test_test.py:4: ValueError

During handling of the above exception, another exception occurred:

    def test_match():
        with pytest.raises((ValueError, RuntimeError),match=r'.*40011.*'):
>           myfunc()
E           AssertionError: Pattern '.*40011.*' does not match '返回40013支付错误'

test_test.py:8: AssertionError


================================== FAILURES ===================================
_________________________________ test_match __________________________________

    def test_match():
        with pytest.raises((ValueError, RuntimeError),match=r'.*40011.*'):
>           myfunc()

test_test.py:8: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def myfunc():
>       raise ValueError("返回40013支付错误")
E       ValueError: 返回40013支付错误

test_test.py:4: ValueError

During handling of the above exception, another exception occurred:

    def test_match():
        with pytest.raises((ValueError, RuntimeError),match=r'.*40011.*'):
>           myfunc()
E           AssertionError: Pattern '.*40011.*' does not match '返回40013支付错误'

test_test.py:8: AssertionError
=========================== short test summary info ===========================
FAILED test_test.py::test_match - AssertionError: Pattern '.*40011.*' does no...
============================== 1 failed in 0.56s ==============================

Process finished with exit code 1

2.3.5 为失败断言添加自定义的说明

有时需要在断言时显示自定义的说明,可以直接在断言后面添加提示信息,代码如下:

def test_num():
    assert 1==2, '这两个值不相等'

执行的结果如下:

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --target test_fail_cus.py::test_num 
Testing started at 9:19 ...
Launching pytest with arguments test_fail_cus.py::test_num in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 1 item

test_fail_cus.py::test_num FAILED                                        [100%]
test_fail_cus.py:0 (test_num)
1 != 2

Expected :2
Actual   :1
<Click to see difference>

def test_num():
>       assert 1==2, '这两个值不相等'
E       AssertionError: 这两个值不相等
E       assert 1 == 2
E         +1
E         -2

test_fail_cus.py:2: AssertionError


================================== FAILURES ===================================
__________________________________ test_num ___________________________________

    def test_num():
>       assert 1==2, '这两个值不相等'
E       AssertionError: 这两个值不相等
E       assert 1 == 2
E         +1
E         -2

test_fail_cus.py:2: AssertionError
=========================== short test summary info ===========================
FAILED test_fail_cus.py::test_num - AssertionError: 这两个值不相等
============================== 1 failed in 0.09s ==============================

Process finished with exit code 1

上述代码是比较两个基本数据,当比较两个对象实例时,可通过重写它的__eq__()方法比对实现。

代码如下:

class Foo:
    def __init__(self, val):
        self.val = val
    def __eq__(self, other):
        return self.val == other.val

def test_foo_compare():
    f1 = Foo(1)
    f2 = Foo(2)
    assert f1 == f2

但当执行这个用例时其结果并不能直观地从中看出来失败的原因assert <test_fail_cus.Foo object at 0x109a7de10>==<test_fail_cus.Foo object at 0x109a7dc88>。

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_fail_cus.py 
Testing started at 9:34 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_fail_cus.py in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 1 item

test_fail_cus.py::test_foo_compare FAILED                                [100%]
test_fail_cus.py:6 (test_foo_compare)
<test_fail_cus.Foo object at 0x0000021A3A53D7C8> != <test_fail_cus.Foo object at 0x0000021A3A53DAC8>

Expected :<test_fail_cus.Foo object at 0x0000021A3A53DAC8>
Actual   :<test_fail_cus.Foo object at 0x0000021A3A53D7C8>
<Click to see difference>

def test_foo_compare():
        f1 = Foo(1)
        f2 = Foo(2)
>       assert f1 == f2
E       assert <test_fail_cus.Foo object at 0x0000021A3A53D7C8> == <test_fail_cus.Foo object at 0x0000021A3A53DAC8>
E         +<test_fail_cus.Foo object at 0x0000021A3A53D7C8>
E         -<test_fail_cus.Foo object at 0x0000021A3A53DAC8>

test_fail_cus.py:10: AssertionError


================================== FAILURES ===================================
______________________________ test_foo_compare _______________________________

    def test_foo_compare():
        f1 = Foo(1)
        f2 = Foo(2)
>       assert f1 == f2
E       assert <test_fail_cus.Foo object at 0x0000021A3A53D7C8> == <test_fail_cus.Foo object at 0x0000021A3A53DAC8>
E         +<test_fail_cus.Foo object at 0x0000021A3A53D7C8>
E         -<test_fail_cus.Foo object at 0x0000021A3A53DAC8>

test_fail_cus.py:10: AssertionError
=========================== short test summary info ===========================
FAILED test_fail_cus.py::test_foo_compare - assert <test_fail_cus.Foo object ...
============================== 1 failed in 0.08s ==============================

Process finished with exit code 1

在这种情况下,如何比较两个实例呢?

通常有两种方式:第一种是重写__repr__()方法,第二种是用钩子的方法。下面将详细说明具体如何实现。

第一种方法:通过重写Foo的__repr__()方法,把详细信息显示出来。

在上述代码中添加下面的方法,代码如下:

class Foo:
    def __init__(self, val):
        self.val = val
    def __eq__(self, other):
        return self.val == other.val
    def __repr__(self):
        return str(self.val)

def test_foo_compare():
    f1 = Foo(1)
    f2 = Foo(2)
    assert f1 == f2

当执行用例时,能看到失败的原因,其原因是因为1==2不成立。

代码如下:

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_fail_cus.py 
Testing started at 9:36 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_fail_cus.py in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 1 item

test_fail_cus.py::test_foo_compare FAILED                                [100%]
test_fail_cus.py:8 (test_foo_compare)
1 != 2

Expected :2
Actual   :1
<Click to see difference>

def test_foo_compare():
        f1 = Foo(1)
        f2 = Foo(2)
>       assert f1 == f2
E       assert 1 == 2
E         +1
E         -2

test_fail_cus.py:12: AssertionError


================================== FAILURES ===================================
______________________________ test_foo_compare _______________________________

    def test_foo_compare():
        f1 = Foo(1)
        f2 = Foo(2)
>       assert f1 == f2
E       assert 1 == 2
E         +1
E         -2

test_fail_cus.py:12: AssertionError
=========================== short test summary info ===========================
FAILED test_fail_cus.py::test_foo_compare - assert 1 == 2
============================== 1 failed in 0.13s ==============================

Process finished with exit code 1

第二种方法:使用钩子的方法加断言的详细信息。

在当前目录下新建conftest.py文件,在conftest.py文件中使用pytest_assertrepr_compare这个钩子方法添加自定义的失败说明,在本目录下创建conftest.py文件(这个文件中的内容会供本目录中的所有文件共享,pytest执行时先执行这个文件中的内容再执行测试用例,在3.2节和5.4节将分别详细讲解conftest.py和钩子的用法),在文件中输入下面代码。自定义失败说明后再执行test_fail_cus.py。

代码如下:

from test_fail_cus import Foo

def pytest_assertrepr_compare(op, left, right):
    if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
        return [
            "比较两个Foo实例:",
            "值: {} != {}".format(left.val, right.val),
        ]

执行后的结果如下,此时会看到一个更友好的失败说明,在出现错的代码行下面有具体的自定义的提示信息。

代码如下:

D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_fail_cus.py 
Testing started at 9:45 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_fail_cus.py in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 1 item

test_fail_cus.py::test_foo_compare FAILED                                [100%]
test_fail_cus.py:8 (test_foo_compare)
1 != 2

Expected :2
Actual   :1
<Click to see difference>

def test_foo_compare():
        f1 = Foo(1)
        f2 = Foo(2)
>       assert f1 == f2
E       assert 比较两个Foo实例:
E         值: 1 != 2

test_fail_cus.py:12: AssertionError


================================== FAILURES ===================================
______________________________ test_foo_compare _______________________________

    def test_foo_compare():
        f1 = Foo(1)
        f2 = Foo(2)
>       assert f1 == f2
E       assert 比较两个Foo实例:
E         值: 1 != 2

test_fail_cus.py:12: AssertionError
=========================== short test summary info ===========================
FAILED test_fail_cus.py::test_foo_compare - assert 比较两个Foo实例:
============================== 1 failed in 0.04s ==============================

Process finished with exit code 1

Assert各种类型断言

为了能更好地理解assert的意义和使用,下面是实践部分,大家可以输入代码并执行,查看执行结果。test_mag方法断言调用函数进行返回值的判断;test_simple_math方法断言比较运算符,在计算机中小数的计算会有误差。我们当然希望像现实世界对小数进行计算那样,例如0.1+0.2=0.3,test_approx_simple方法中的approx函数解决了上述问题。

test_warrior_long_description方法进行长文本断言。test_get_starting_equiment方法是防御性编程的例子。test_isinstance方法用于测试返回类型是否一致。

代码如下:

import textwrap
from math import sqrt
from pytest import approx

def magnitude(x, y):
    return sqrt(x*x + y*y)

def test_mag():
    assert magnitude(3, 4) == 5
    
def test_simple_math():
    assert abs(0.1 + 0.2) - 0.3 < 0.0001
    
def test_approx_simple():
    assert 0.1 + 0.2 == approx(0.3)
    
def test_approx_simple_fail():
    assert 0.1 + 0.2 == approx(0.35)
    
def get_long_class_description(class_name):
    assert class_name == "warrior"
    return textwrap.dedent(
        """\
        A seasoned veteran of many battles. High Strength and Dexterity
        allow to yield heavy armor and weapons, as well as carry
        more equipment while keeping a light roll. Weak in magic.
        """
    )

def test_warrior_long_description():
    desc = get_long_class_description("warrior")
    assert (
        desc
        == textwrap.dedent(
        """\
        A seasoned veteran of many battles. Strength and Dexterity
        allow to yield heavy armor and weapons, as well as carry
        more equipment. Weak in magic.
        """
    )
    )
    
def get_starting_equipment(class_name):
    assert class_name == "战士"
    return ["长剑", "战士装备", "盾"]

def test_get_starting_equiment():
    expected = ["长剑", "战士装备"]
    assert get_starting_equipment("战士") == expected, "装备不符"
    
def test_isinstance():
    task_id = 10
    assert isinstance(task_id, int)
D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_assert_sample.py 
Testing started at 9:57 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest_learning\test_assert_sample.py in D:\SynologyDrive\CodeLearning\WIN\pytest_learning

============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest_learning\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest_learning
collecting ... collected 7 items

test_assert_sample.py::test_mag PASSED                                   [ 14%]
test_assert_sample.py::test_simple_math PASSED                           [ 28%]
test_assert_sample.py::test_approx_simple PASSED                         [ 42%]
test_assert_sample.py::test_approx_simple_fail FAILED                    [ 57%]
test_assert_sample.py:16 (test_approx_simple_fail)
0.30000000000000004 != 0.35 ± 3.5e-07

Expected :0.35 ± 3.5e-07
Actual   :0.30000000000000004
<Click to see difference>

def test_approx_simple_fail():
>       assert 0.1 + 0.2 == approx(0.35)
E       assert 0.30000000000000004 == 0.35 ± 3.5e-07
E         +0.30000000000000004
E         -0.35 ± 3.5e-07

test_assert_sample.py:18: AssertionError


test_assert_sample.py::test_warrior_long_description FAILED              [ 71%]
test_assert_sample.py:29 (test_warrior_long_description)
('A seasoned veteran of many battles. High Strength and Dexterity\n'
 'allow to yield heavy armor and weapons, as well as carry\n'
 'more equipment while keeping a light roll. Weak in magic.\n') != ('A seasoned veteran of many battles. Strength and Dexterity\n'
 'allow to yield heavy armor and weapons, as well as carry\n'
 'more equipment. Weak in magic.\n')

<Click to see difference>

def test_warrior_long_description():
        desc = get_long_class_description("warrior")
>       assert (
            desc
            == textwrap.dedent(
            """\
            A seasoned veteran of many battles. Strength and Dexterity
            allow to yield heavy armor and weapons, as well as carry
            more equipment. Weak in magic.
            """
        )
        )
E       AssertionError: assert ('A seasoned veteran of many battles. High Strength and Dexterity\n'\n 'allow to yield heavy armor and weapons, as well as carry\n'\n 'more equipment while keeping a light roll. Weak in magic.\n') == ('A seasoned veteran of many battles. Strength and Dexterity\n'\n 'allow to yield heavy armor and weapons, as well as carry\n'\n 'more equipment. Weak in magic.\n')
E         - A seasoned veteran of many battles. Strength and Dexterity
E         + A seasoned veteran of many battles. High Strength and Dexterity
E         ?                                     +++++
E           allow to yield heavy armor and weapons, as well as carry
E         - more equipment. Weak in magic.
E         + more equipment while keeping a light roll. Weak in magic.

test_assert_sample.py:32: AssertionError


test_assert_sample.py::test_get_starting_equiment FAILED                 [ 85%]
test_assert_sample.py:46 (test_get_starting_equiment)
['长剑', '战士装备', '盾'] != ['长剑', '战士装备']

Expected :['长剑', '战士装备']
Actual   :['长剑', '战士装备', '盾']
<Click to see difference>

def test_get_starting_equiment():
        expected = ["长剑", "战士装备"]
>       assert get_starting_equipment("战士") == expected, "装备不符"
E       AssertionError: 装备不符
E       assert ['长剑', '战士装备', '盾'] == ['长剑', '战士装备']
E         Left contains one more item: '盾'
E         Full diff:
E         - ['长剑', '战士装备']
E         + ['长剑', '战士装备', '盾']
E         ?              +++++

test_assert_sample.py:49: AssertionError


test_assert_sample.py::test_isinstance PASSED                            [100%]

================================== FAILURES ===================================
___________________________ test_approx_simple_fail ___________________________

    def test_approx_simple_fail():
>       assert 0.1 + 0.2 == approx(0.35)
E       assert 0.30000000000000004 == 0.35 ± 3.5e-07
E         +0.30000000000000004
E         -0.35 ± 3.5e-07

test_assert_sample.py:18: AssertionError
________________________ test_warrior_long_description ________________________

    def test_warrior_long_description():
        desc = get_long_class_description("warrior")
>       assert (
            desc
            == textwrap.dedent(
            """\
            A seasoned veteran of many battles. Strength and Dexterity
            allow to yield heavy armor and weapons, as well as carry
            more equipment. Weak in magic.
            """
        )
        )
E       AssertionError: assert ('A seasoned veteran of many battles. High Strength and Dexterity\n'\n 'allow to yield heavy armor and weapons, as well as carry\n'\n 'more equipment while keeping a light roll. Weak in magic.\n') == ('A seasoned veteran of many battles. Strength and Dexterity\n'\n 'allow to yield heavy armor and weapons, as well as carry\n'\n 'more equipment. Weak in magic.\n')
E         - A seasoned veteran of many battles. Strength and Dexterity
E         + A seasoned veteran of many battles. High Strength and Dexterity
E         ?                                     +++++
E           allow to yield heavy armor and weapons, as well as carry
E         - more equipment. Weak in magic.
E         + more equipment while keeping a light roll. Weak in magic.

test_assert_sample.py:32: AssertionError
_________________________ test_get_starting_equiment __________________________

    def test_get_starting_equiment():
        expected = ["长剑", "战士装备"]
>       assert get_starting_equipment("战士") == expected, "装备不符"
E       AssertionError: 装备不符
E       assert ['长剑', '战士装备', '盾'] == ['长剑', '战士装备']
E         Left contains one more item: '盾'
E         Full diff:
E         - ['长剑', '战士装备']
E         + ['长剑', '战士装备', '盾']
E         ?              +++++

test_assert_sample.py:49: AssertionError
=========================== short test summary info ===========================
FAILED test_assert_sample.py::test_approx_simple_fail - assert 0.300000000000...
FAILED test_assert_sample.py::test_warrior_long_description - AssertionError:...
FAILED test_assert_sample.py::test_get_starting_equiment - AssertionError: 装...
========================= 3 failed, 4 passed in 0.13s =========================

Process finished with exit code 1
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值