2-pytest-测试函数

本文详细介绍了Python的单元测试框架pytest的使用,包括利用assert进行断言,捕获异常以测试错误处理,使用标记功能选择性执行测试,跳过未完成的测试,以及如何进行参数化测试以提高测试覆盖率。内容涵盖了pytest的raises函数、pytest.mark.*系列标记、pytest.param以及parametrize方法,旨在帮助开发者更好地理解和应用pytest进行高效测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

断言 assert

编写测试的最基本工具。

assert a == b

assert a <= b

捕获异常

测试的过程中,需要测试是否如期抛出异常,来确定异常处理模块是否生效。

使用 pytest.raises() 来进行异常捕获:

def test_raises():
    with pytest.raises(TypeError) as e:
        connect('localhost', '6379')
    exec_msg = e.value.args[0]
    assert exec_msg == 'port type must be int'

该段代码 PASSED。

localhost:6379 尝试连接 redis。

其中 Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。

标记函数

pytest 查找测试策略

默认情况下,pytest 会递归查找当前目录下所有以 test 开头或结尾的 python 脚本,并执行文件内的所有以 test 开头或结尾的函数和方法。

# test_no_mark.py
def test_func1():
	assert 1 == 1

def test_func2():
	assert 1 != 1

直接使用命令行输入 pytest,可以执行所有测试函数。

标记测试函数

由于某种原因(如 test_func2 的功能尚未开发完成),我们只想执行指定的测试函数。在 pytest 中有几种方式可以解决:

一、显式指定函数名,通过 :: 标记

C:\Users\Administrator\Desktop\test\20210407\test_no_mark>pytest test_no_mark.py::test_func1
========================================= test session starts =========================================
platform win32 -- Python 3.9.2, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\Administrator\Desktop\test\20210407\test_no_mark
collected 1 item

test_no_mark.py .                 [100%]

========================================== 1 passed in 0.01s =========================================

二、使用模糊匹配,使用 -k 选项标记

C:\Users\Administrator\Desktop\test\20210407\test_no_mark>pytest -k func1 test_no_mark.py
========================================= test session starts =========================================
platform win32 -- Python 3.9.2, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\Administrator\Desktop\test\20210407\test_no_mark 
collected 2 items / 1 deselected / 1 selected 

test_no_mark.py .         [100%] 

=================================== 1 passed, 1 deselected in 0.01s =================================

以上两种方法,第一种一次只能指定一个测试函数,当要进行批量测试时无能为力;第二种方法可以批量操作,但需要所有测试的函数名包含相同的模式,也不方便。

三、使用 pytest.mark 在函数上标记

带标记的测试函数为:

# test_with_mark.py

import pytest

@pytest.mark.finished
def test_func1():
	assert 1 == 1

@pytest.mark.unfinished
def test_func2():
	assert 1 != 1

测试的时候使用 -m 选择标记的测试函数

> pytest -m finished test_with_mark.py

使用 mark 可以给每个函数做上不同标记,测试时指定就可以允许所有被标记的函数。

注意:
一个函数可以打多个标记;多个函数也可以打相同的标记。

运行测试时使用 -m 选项可以加上逻辑,如:

cmd 输入> pytest -m "finished and commit"

cmd 输入> pytest -m "finished and not merged"

跳过测试

我们可以通过标记来使一些函数不被执行,对于那些开发未完成的函数,最好的处理方式为略过而不执行测试。

通过标记指定要跳过的测试

使用特定标记 pytest.mark.skip 实现

# test_skip.py

import pytest

@pytest.mark.skip(reason='out-of-date api')
def test_connect():
	pass

命令行输入 pytest test_skip.py 即可
在这里插入图片描述
其中,pytest 使用 s 表示测试被跳过
在这里插入图片描述

同时,还可以使用 pytest.mark.skipif 来为测试函数指定跳过的条件。

@pytest.mark.skipif(conn.__version__ < '0.2.0',
                    reason='not supported until v0.2.0')
def test_api():
    pass

预见的错误

如果我们事先知道测试函数会执行失败,但又不想直接跳过,而是希望显示的提示。

可使用的是 pytest.mark.xfail

# test_xfail.py

@pytest.mark.xfail(gen.__version__ < '0.2.0',
                   reason='not supported until v0.2.0')
def test_api():
    id_1 = gen.unique_id()
    id_2 = gen.unique_id()
    assert id_1 != id_2

使用 pytest test_xfail.py 来执行

注意:
pytest 使用 x 表示预见的失败(XFAIL)。

如果预见的是失败,但实际运行测试却成功通过,pytest 使用 X 进行标记(XPASS)。

参数化

当对一个测试函数进行测试时,通常会给函数传递多组参数。比如测试账号登陆,我们需要模拟各种千奇百怪的账号密码。

当然,我们可以把这些参数写在测试函数内部进行遍历。不过虽然参数众多,但仍然是一个测试,当某组参数导致断言失败,测试也就终止了。

通过异常捕获,我们可以保证程所有参数完整执行,但要分析测试结果就需要做不少额外的工作。

在 pytest 中,我们有更好的解决方法,就是参数化测试,即每组参数都独立执行一次测试。使用的工具就是 pytest.mark.parametrize(argnames, argvalues)

举个例子:
一个密码长度的测试函数,其中参数名为 passwd,其可选列表包含三个值:

# test_parametrize.py

import pytest

@pytest.mark.parametrize('passwd',
						['123456',
						 'abcdefdds',
						 'asfdfgfgvfgfg'])
def test_passwd_length(passwd):
	assert len(passwd) >= 8

可以看到执行了三次测试,第一次长度没有超过8,所以第一次 Failed
在这里插入图片描述

举例2:
多参数的例子,用于校验用户密码

# test_parametrize.py

import pytest

@pytest.mark.parametrize('user, passwd',
						[('jack', 'abcdefghijk'),
						 ('tom', 'adgdg23435')])
def test_passwd_md5(user,passwd):
	db = {
		'jack': '92b9cccc0b98c3a0b8d0df25a421c0e3',
		'tom': '78c9713a93986d00f9c489b2e4c53bba'
	}

	import hashlib
	
	assert hashlib.md5(passwd.encode()).hexdigest() == db[user]


if __name__ == '__main__':
	pytest.main('-v test_parametrize.py'.split())

注意:
hashlib 模块
hashlib是一个提供字符串加密功能的模块,包含MD5和SHA的算法,MD5和SHA是摘要算法,也可以称为哈希算法,离散算法。通过一个函数将任意长度的数据转化为一个长度固定的数据串。

比如

import hashlib         #导入hashlib模块

md = hashlib.md5()     #获取一个md5加密算法对象
md.update('adgdg23435'.encode('utf-8'))                   #制定需要加密的字符串
print(md.hexdigest())  #获取加密后的16进制字符串

结果:

78c9713a93986d00f9c489b2e4c53bba

***Repl Closed***

这个即为 tom 的加密字符串。

这样得到 jack 和 tom 的加密密码后,放在我们的用户密码验证的例子里,运行结果可以为:

============================= test session starts =============================
platform win32 -- Python 3.9.2, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- D:\Python\Python39\python.EXE
cachedir: .pytest_cache
rootdir: C:\Users\Administrator\Desktop\test\20210407\test_parametrize
collecting ... collected 2 items

test_parametrize.py::test_passwd_md5[jack-abcdefghijk] PASSED            [ 50%]
test_parametrize.py::test_passwd_md5[tom-adgdg23435] PASSED              [100%]

============================== 2 passed in 0.02s ==============================

***Repl Closed***

如果觉得每组测试的默认参数显示不清晰,可以使用 pytest.param 中的 id 参数来自定义。

# test_parametrize.py

import pytest

@pytest.mark.parametrize('user, passwd',
						[pytest.param('jack','abcdefghijk',id='User<Jack>'),
						pytest.param('tom','adgdg23435',id='User<Tom>')])
def test_passwd_md5(user,passwd):
	db = {
		'jack': '92b9cccc0b98c3a0b8d0df25a421c0e3',
		'tom': '78c9713a93986d00f9c489b2e4c53bba'
	}

	import hashlib

	assert hashlib.md5(passwd.encode()).hexdigest() == db[user]


if __name__ == '__main__':
	pytest.main('-v test_parametrize.py'.split())

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值