初识Pytest
-
Pytest是Python实现的一个测试工具,可以用于所有类型和级别的软件测试。
-
Pytest是一个可以自动查找到你编写的用例并运行后输出结果的测试框架。
-
Pytest的特点:
- 是一个命令行工具,编写用例简单,可读性强
- 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
- 支持单元测试和功能测试
- 支持参数化
- 执行测试过程中可以将某些测试跳过,或者对某些预期失败的Case标记成失败
- 支持重复执行失败的Case
- 支持运行由unittest编写的测试Case
- 具有很多第三方插件,并且可以自定义扩展
- 方便的和持续集成工具集成
- 可以运行以test开头或test结尾的包、文件和方法
- 可以使用assert进行断言
-
Pytest 安装
pip install -U pytest -i https://pypi.tuna.tsinghua.edu.cn/simple
Pytest 第一个例子
- pytest 默认能够自动识别 test 开头函数和方法,以及 Test 开头的类。
import pytest
import requests
def test_one():
r = requests.get('https://www.baidu.com')
print(r.status_code)
def test_two():
r = requests.get('https://www.baidu.com')
print(r.encoding)
class TestTask(object):
def test_1(self):
print('test 1')
def test_2(self):
print('test 2')
if __name__ == '__main__':
pytest.main() # pytest
- 右键直接运行,结果如下
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaoyidemac/Desktop/测试
plugins: tavern-1.16.3, openfiles-0.3.2, arraydiff-0.3, allure-pytest-2.9.45, doctestplus-0.3.0, remotedata-0.3.1collected 4 items
pytest_code.py .200
.ISO-8859-1
[100%]
============================== 4 passed in 0.29s ===============================
Process finished with exit code 0
.test 1
.test 2
pytest运行方式
通过代码中pytest.main()可以右键直接运行,相当于python test_1.py;也可以通过命令行的方式启动运行
代码中运行的方式
- pytest.main()
会执行当前文件的同时,路径下所有以test开头的py文件都会被执行
pytest先获取所有文件中的测试函数后,再执行
不会显示代码中的标准输出(print、logging) - pytest.main(['-s'])
会执行当前文件的同时,路径下所有以test开头的py文件都会被执行
pytest先获取所有文件中的测试函数后,再执行
显示代码中的标准输出(print、logging) - pytest.main(['test_1.py'])
只会执行当前文件
不会显示代码中的标准输出(print、logging) - pytest.main(['-s', 'test_1.py'])
只会执行当前文件
显示代码中的标准输出(print、logging)
上述四种方式皆有对应的命令行运行的方式
- pytest
会执行当前文件的同时,路径下所有以test开头的py文件都会被执行
pytest先获取所有文件中的测试函数后,再执行
不会显示代码中的标准输出(print、logging) - pytest -s
会执行当前文件的同时,路径下所有以test开头的py文件都会被执行
pytest先获取所有文件中的测试函数后,再执行
显示代码中的标准输出(print、logging) - pytest test_1.py
只会执行当前文件
不会显示代码中的标准输出(print、logging) - pytest -s test_1.py
只会执行当前文件
显示代码中的标准输出(print、logging)
注意:
- 测试脚本中的测试类,如果不以Test作为名称开头,将无法执行
- 测试脚本中的函数,如果不以test作为名称开头,将无法执行
断言的处理
assert
什么是断言
- 断言就是用来检测程序是否正确, 若条件为假,就中断程序执行
- 编写代码时,总是会做出一些假设,断言就是用于在代码中捕捉这些假设
- 断言表示为一些布尔表达式
断言的使用场景
- 使用assert语句
- 断言预期的异常
- 断言预期的警告
assert断言语句
- 采用assert断言时,可添加备注信息
- 当断言预测失败时,备注信息会以AssertionError抛出,并在控制台输出
assert expression [, arguments]
# expression 为 True 则 pass
# expression 为 False 则 抛出异常,有 argument 则输出 argument
'''
expression:
1)比较运算
2)逻辑运算 and | or | not
3)身份运算 is | is not
4)成员运算 in | not in
'''
示例代码:
import requests
def test_assert():
r = requests.get('http://www.baidu.com')
assert r.status_code == 200, "没有返回200,断言失败"
异常断言
-
在测试过程中,对某些方法进行测试时,预测输入某些特定数据,会抛出某种异常,若出现该异常,则用例执行通过。
-
对这预期会出现的异常的断言,可以采用pytest中的pytest.raises()进行处理。
pytest.raises()的使用
- pytest.raises(expected_exception, match=None, kwargs)
expected_exception 是预期异常类或者的异常类元组
match 用于匹配异常的消息内容
import pytest
def is_leap_year(year):
# 先判断year是不是整型
if isinstance(year, int) is not True:
raise TypeError("传入的参数不是整数")
elif year == 0:
raise ValueError("公元元年是从公元一年开始!!")
elif abs(year) != year:
raise ValueError("传入的参数不是正整数")
elif (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
print("%d年是闰年" % year)
return True
else:
print("%d年不是闰年" % year)
return False
class TestAssert(object):
"""对判断是否是闰年的方法进行测试"""
def test_exception_typeerror(self):
# 使用 pytest.raises 作为上下文管理器,捕获给定异常类型TypeError
# is_leap_year('2020')抛出TypeError,被pytest.raises捕获到,则测试用例执行通过
with pytest.raises(TypeError):
# 传入字符串引发类型错误
is_leap_year('2020')
运行结果如下:
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/xiaoyidemac/Desktop/测试
plugins: tavern-1.16.3, openfiles-0.3.2, arraydiff-0.3, allure-pytest-2.9.45, doctestplus-0.3.0, remotedata-0.3.1collected 1 item
pytest_code.py [100%]
============================== 1 passed in 0.06s ===============================
把异常信息存储到变量
有时候我们可能需要在测试用到产生的异常信息,我们可以把异常信息存储到一个变量中,变量的类型为 异常类 ,包含异常的 type、value 或者 traceback 等信息。
import pytest
def is_leap_year(year):
# 先判断year是不是整型
if isinstance(year, int) is not True:
raise TypeError("传入的参数不是整数")
elif year == 0:
raise ValueError("公元元年是从公元一年开始!!")
elif abs(year) != year:
raise ValueError("传入的参数不是正整数")
elif (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
print("%d年是闰年" % year)
return True
else:
print("%d年不是闰年" % year)
return False
class TestAssert(object):
"""对判断是否是闰年的方法进行测试"""
def test_exception_typeerror(self):
# 预测到参数不符合要求,会抛出TypeError异常;若出现该异常,则测试用例执行通过
with pytest.raises(TypeError) as err_info:
# 传入字符串引发类型错误
is_leap_year('2020')
# 断言异常的类型是 TypeError,断言成功则不会抛出异常
assert err_info.type == TypeError, '错误类型不匹配'
通过异常的内容捕捉异常
import pytest
def is_leap_year(year):
# 先判断year是不是整型
if isinstance(year, int) is not True:
raise TypeError("传入的参数不是整数")
elif year == 0:
raise ValueError("公元元年是从公元一年开始!!")
elif abs(year) != year:
raise ValueError("传入的参数不是正整数")
elif (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
print("%d年是闰年" % year)
return True
else:
print("%d年不是闰年" % year)
return False
class TestAssert(object):
"""对判断是否是闰年的方法进行测试"""
def test_exception_typeerror(self):
# 通过异常的内容捕捉异常,但具体是何种异常不清楚
with pytest.raises(Exception, match='从公元一年开始') as err_info:
is_leap_year(0)
# 断言异常的类型是 ValueError,断言成功则不会抛出异常
assert err_info.type == ValueError
警告断言
- pytest 中对警告进行断言采用 pytest.warns() 方法,其断言的方法与 pytest.raises() 类似。
- pytest.warns() 除了能断言告警外,还能够捕获告警信息,并对捕获的告警信息进行分类处理,可以设定出现特定告警信息时,则用例执行失败。
- 程序中抛出警告时,并不会中断程序的执行,一般是打印警告信息,而程序抛出异常时可能直接中断程序的运行。因此对于警告类信息我们在捕获的时候,可以捕获到多个,而异常信息只能捕获到一个。
pytest.warns()的使用
- pytest.warns(expected_warning, *args, match, **kwargs)
--- expected_warning 预期的警告类或者警告类元组,例如:
DeprecationWarning 弃用警告
UserWarning 用户警告 - match 可以自定义的匹配警告内容
import pytest
import warnings
def make_warn():
# 抛出
warnings.warn("deprecated", DeprecationWarning)
def not_warn():
pass
def user_warn():
warnings.warn("user warn", UserWarning)
class TestWarns(object):
def test_make_warn(self):
with pytest.warns(DeprecationWarning):
make_warn()
def test_not_warn(self):
# 断言not_warn函数将会抛出警告
# 但实际是没有抛出警告的,所以无法通过测试
with pytest.warns(Warning):
not_warn()
def test_user_warn(self):
with pytest.warns(UserWarning):
user_warn()
运行结果如下:
================================================== test session starts ===================================================
platform linux -- Python 3.6.8, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/python/code/unit_testing/pytest_code
collected 3 items
test_7.py .F. [100%]
======================================================== FAILURES ========================================================
________________________________________________ TestWarns.test_not_warn _________________________________________________
self =
def test_not_warn(self):
with pytest.warns(Warning):
> not_warn()
E Failed: DID NOT WARN. No warnings of type (,) was emitted. The list of emitted warnings is: [].
test_7.py:24: Failed
================================================ short test summary info =================================================
FAILED test_7.py::TestWarns::test_not_warn - Failed: DID NOT WARN. No warnings of type (,) was emitted...
============================================== 1 failed, 2 passed in 0.04s ===============================================
把警告信息存储到变量
将告警信息存入一个变量中,通过读取这个变量中的信息进行断言,包括:告警的个数、告警信息参数等。
# pytest_code/test_8.py
import warnings
import pytest
def warn_message():
warnings.warn("user", UserWarning)
warnings.warn("runtime", RuntimeWarning)
def test_warn_match():
with pytest.warns((UserWarning, RuntimeWarning)) as record:
warn_message()
assert len(record) == 2
assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime"
assert record[0].category == UserWarning
assert record[1].category == RuntimeWarning
运行结果如下:
$ pytest test_8.py
================================================== test session starts ===================================================
platform linux -- Python 3.6.8, pytest-6.0.1, py-1.9.0, pluggy-0.13.1
rootdir: /home/python/code/unit_testing/pytest_code
collected 1 item
test_8.py . [100%]
=================================================== 1 passed in 0.01s ====================================================
通过警告的内容捕捉警告
# pytest_code/test_9.py
import warnings
import pytest
def make_warn():
# 抛出
warnings.warn("deprecated", DeprecationWarning)
def not_warn():
pass
def user_warn():
warnings.warn("user warn", UserWarning)
class TestWarns(object):
def test_make_warn(self):
# 捕获警告内容为deprecated的警告
with pytest.warns(Warning, match='deprecated'):
make_warn()
网友评论