python—pytest入门学习

PyTest介绍:

PyTest是一个测试用例的管理框架,在UnitTest基础上做的一个全面的升级。

集成度更高,而且更加自由的一个测试框架,全程都是基于指令的形式运行。

PyTest-uniTest-RobotFramework-airtest

PyTest环境部署:

pip install pytest

输入pytest检查安装成功,输入pytest会自动编译,执行当前目录及子目录下是否有符合pytest规则的文件

pytest遵寻规则:

1、pytest默认运行以test开头或者结尾的文件
2、pytest默认不输出任何打印信息,如果要看打印信息,需要在运行时添加’-s’指令
在这里插入图片描述

在这里插入图片描述

3、pytest默认寻找当前路径下所有文件与子文件夹中以test开头或结尾的文件夹、文件、函数作为识别对象(逐层寻找,若文件并非以test开头,函数已test开头,则不会识别到)
4、多条指令一同运行时,需要通过空格进行区分,在main函数中,是通过’ , '进行分割
5、类的命名必须以Test开头,且不能有__init__初始化方法
6、方法必须以test_开头
7、断言必须使用assert

 import pytest
 class Test1:
 	def test_01(self):
 		assert 1==1
 	def test_02(self):
 		assert 1==1
 		
 if __name__ == "__main__":
 	# -s 打印出预期结果与实际结果不一致的print里面的内容,
    pytest.main(['-s','test_case.py::test_02'])
(venv) D:\workspace\code\pytestStudyCode\class01\test_demo>pytest -s test_case.py::test_02

console中的指令:

指令含义指令
用于详细显示日志信息-v
测试结果的简单统计-rA
只显示整体测试结果-q
帮助-h
显示print打印信息-s
输出html格式报告–html=path输出路径
生成log报告–resultlog=./log.txt
生成xml报告–junitxml=./log.xml
当错误达到num时,停止测试–maxfail=num
只运行有MARKEXPR(自定义)标记的测试-m MARKEXPR
生成简略的指定需求的报告-r option

pytest执行命令:

指令含义指令
运行所有test测试函数pytest
运行test_mod.py下的所有测试函数pytest test_mod.py
运行指定路径somepath下的测试函数pytest somepath
运行符合表达式stringexpr的函数,如stringexpr为“Myclass and not me”,则会运行TMyclass.tree,不会运行TMyclass.haha_me_hahapytest -k stringexpr
运行test_mod.py文件下的test_func测试函数pytest test_mod.py::test_func

pytest中的setup 和 teardown

1、一般通过一个配置文件直接进行管理:配置文件名一定要是conftest.py
2、在conftest中定义预置函数(用于前期的数据准备),使用@pytest.fixture(),使用@pytest.mark.usefixtures()作为装饰器(无法获取到fixture的返回值)

方法:fixture(scope=“function”, params=None, autouse=False, ids=None, name=None)
params:(list类型)提供参数数据,供调用标记方法的函数使用;一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它

param的使用:

import pytest

def data():
    return ['01','02','03']

@pytest.fixture(params=data())
def get_data(request):
    return request.param

def test_05(get_data):
    print("\ntest_"+get_data)

if __name__ == "__main__":
    pytest.main(['-s','test_case03.py::test_05'])

结果依次传参输出:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case03.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\Workspace\projects\Exercise\pytest_test, configfile: pytest.ini
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collected 3 items

test_case03.py 
test_01
.
test_02
.
test_03
.

============================== 3 passed in 0.05s ==============================

confest.py文件:

import pytest
'''
这是pytest中setup_class和teardown_class的配置文件
'''
@pytest.fixture()
def xuzhu():
    print('虚竹生病了但是很强')

@pytest.fixture()
def xuzhu1():
    return 1

测试用例:

import pytest

def test_01(xuzhu):
    print('test01')

def test_02(xuzhu1):
    assert xuzhu1 == 1, '失败'
    print('test02')
    
@pytest.mark.usefixtures("xuzhu")
def test_o3():
	print("test_03")


if __name__ == "__main__":
    # pytest.main(['-s','test_case.py::test_02'])
    pytest.main(['-s','test_case.py'])

fixture 中参数scope定义的4种等级: session:在每个session中只执行一次 module:在每个模块中只执行一次 class:在每个class中只执行一次 function:在每个函数中只执行一次

示例:

conftest.py预置文件,mike函数的scope为session:

import pytest
@pytest.fixture(scope='session')
def mike():
    print("mike is godfather")

@pytest.fixture()
def mike2():
    print("mike is miserable!!!")

test_case.py文件中的函数test_01和test_02都调用了mike这个预置函数:

import pytest

class Test01:
    @pytest.mark.website
    def test_01(self,mike):
        print("test_01")
        assert 1==1

    @pytest.mark.website
    def test_02(self,mike):
        print("test02")
        assert 2==2

if __name__ == "__main__":
    pytest.main(['-s','test_case.py'])

运行结果,只运行了一次mike函数:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\Workspace\projects\Exercise\pytest_test
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collected 3 items

test_case.py mike is godfather
---setup_class---
test_01
.test02

若将mike的scope设置为function,则运行两次mike函数:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\Workspace\projects\Exercise\pytest_test
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collected 3 items

test_case.py ---setup_class---
mike is godfather
test_01
.mike is godfather
test02

setup:在测试函数或类之前执行,完成准备工作,如;数据库连接、测试数据、打开文件等,setup_class、setup_function、setup_module
teardown:在测试函数或类结束后执行,完成收尾工作,如数据库断开、资源回收等,teardown_class、teardown_function、teardown_module
示例:

import pytest

class Test01:
    def setup_class(self):
        print("---setup_class---")

    def teardown_class(self):
        print("---teardown_class----")

    @pytest.mark.website
    def test_01(self,mike):
        print("test_01")
        assert 1==1

    @pytest.mark.website
    def test_02(self,mike):
        print("test02")
        assert 2==2

    def test_03(self):
        print("test_03")
        assert 3==3

if __name__ == "__main__":
    pytest.main(['-s','test_case.py'])

运行结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\Workspace\projects\Exercise\pytest_test
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collected 3 items

test_case.py ---setup_class---
mike is godfather
test_01
.mike is godfather
test02
.test_03
.---teardown_class----

pytest.mark对test进行分类执行

1、只运行带有特定标记的测试函数,如website标记

import pytest

class Test01:
    @pytest.mark.website
    def test_01(self):
        print("test_01")
        assert 1==1

    @pytest.mark.website
    def test_02(self):
        print("test02")
        assert 2==2

    def test_03(self):
        print("test_03")
        assert 3==3

运行及结果:

D:\Workspace\projects\Exercise>pytest -s -m "website" test_case.py
========================================================================================================== test session starts ===========================================================================================================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\Workspace\projects\Exercise
plugins: allure-pytest-2.9.43
collected 3 items / 1 deselected / 2 selected                                                                                                                                                                                             

test_case.py test_01
.test02

2、不运行带有特定标记的函数,如website标记
运行结果:

D:\Workspace\projects\Exercise>pytest -s -m "not website" test_case.py
========================================================================================================== test session starts ===========================================================================================================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\Workspace\projects\Exercise
plugins: allure-pytest-2.9.43
collected 3 items / 2 deselected / 1 selected                                                                                                                                                                                             

test_case.py test_03

pytest.ini配置文件

markers :指定运行的markers标记文件
python_files :指定运行特定命名的python文件(多个类型文件用空格隔开)
python_classes:指定运行特定命名的class文件
python_functions:指定运行特定命名的函数
addopts:命令行参数(使用空格分开)
testpaths:只执行该设置下的文件夹下的test
norecursedirs:不执行所设置的文件夹下的test

跳过某些测试函数 skipif

跳过测试类,则在类外引用下列标签

@pytest.mark.skipif(condition, reason=None)
condition:不执行的情况(条件)
reason:不执行的理由

直接跳过执行测试函数:@pytest.mark.skip(reason=" ")

跳过测试函数:pytest.skip()

根据某些条件跳过模块中的所有测试用例如: pytestmark = pytest.mark.skipif(sys.platform == “win32”,reason=“tests for linux only”)

标记某些测试函数为失败函数 xfail

标记为测试预期失败,直接跳过该函数:

@pytest.mark.xfail(condition=None, reason=None, raises=None, run=True, strict=False)
condition:标记为失败的条件
reason:标记为失败的理由

强制标记为失败,并且不再执行测试函数中的代码:

pytest.xfail()

import pytest

def test_06():
    print("\n----test_06----")

@pytest.mark.xfail(2>1,reason='标记失败')
def tast_07():
    print("-------test_08---------")

def test_08():
    if 1<2:
        pytest.xfail("failing!")
    print("---------test_07----------")



if __name__ == "__main__":
    pytest.main(['-s','test_case04.py'])

结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case04.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: D:\Workspace\projects\Exercise\pytest_test, configfile: pytest.ini
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collected 2 items

test_case04.py 
----test_06----
.x

======================== 1 passed, 1 xfailed in 0.17s =========================

函数数据参数化

方便测试函数对参数的调用:

@pytest.mark.parametrize(argnames,argvalues, indirect=False, ids=None, scope=None)
argnames:参数名
argvalues:参数对应值,可传多个值,类型必须为list [(values1,values2,…),(value1,value2,…)]

1、参数传递
单参/多参传递:

import pytest

'''
parametrize
'''

class Test02:
    def setup_class(self):
        print("---------setup_class----------")
    def teardown_class(self):
        print("------------teardown_class-------------")

    # 传递单参数
    @pytest.mark.parametrize("a",[3,6])
    def test_09(self,a):
        print("a = %d" % a)
        assert a%3 == 0

    # 传递多参数
    @pytest.mark.parametrize('a,b',[(0,3),[1,2]])
    def test_10(self,a,b):
        print("%d + %d = %d" % (a,b,a+b))
        assert a+b == 3




if __name__ == "__main__":
    pytest.main(['-s','-v','test_case05.py::Test02::test_10'])

函数返回值传递参数

import pytest

def test_data():
    return [(1, 2), (0, 3)]


# 函数返回值传递参数
@pytest.mark.parametrize('a,b', test_data())
def test_11(a, b):
    print("%d + %d = %d" % (a, b, a + b))
    assert a + b == 3

if __name__ == "__main__":
    pytest.main(['-s','-v',"test_case06.py::test_11"])

ids的使用

def divsion(a,b):
    return int(a/b)


@pytest.mark.parametrize('a,b,c',[(3,1,3),(0,2,0),(6,7,0)],ids=['整除','被除数为0','非整除'])
def test_12(a,b,c):
    s = divsion(a,b)
    assert s == c

if __name__ == "__main__":
    pytest.main(['-s','-v',"test_case06.py::test_12"])

结果:产生乱码:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case06.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- E:\Python\Python37\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.9', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.4', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'allure-pytest': '2.9.43', 'html': '3.1.1', 'metadata': '1.11.0'}, 'JAVA_HOME': 'E:\\Java\\jdk'}
rootdir: D:\Workspace\projects\Exercise\pytest_test, configfile: pytest.ini
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collecting ... collected 3 items

test_case06.py::test_12[\u6574\u9664] PASSED
test_case06.py::test_12[\u88ab\u9664\u6570\u4e3a0] PASSED
test_case06.py::test_12[\u975e\u6574\u9664] PASSED

============================== 3 passed in 0.06s ==============================

Process finished with exit code 0

解决:在pytest.ini文件中加入:disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
在这里插入图片描述
结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case06.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- E:\Python\Python37\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.9', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.4', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'allure-pytest': '2.9.43', 'html': '3.1.1', 'metadata': '1.11.0'}, 'JAVA_HOME': 'E:\\Java\\jdk'}
rootdir: D:\Workspace\projects\Exercise\pytest_test, configfile: pytest.ini
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collecting ... collected 3 items

test_case06.py::test_12[整除] PASSED
test_case06.py::test_12[被除数为0] PASSED
test_case06.py::test_12[非整除] PASSED

============================== 3 passed in 0.03s ==============================

Process finished with exit code 0

3、叠加使用

def divsion(a,b):
    return int(a/b)

# 叠加使用
@pytest.mark.parametrize('a',[3,0,6])
@pytest.mark.parametrize('b,c',[(1,3),(2,0),(7,0)])
def test_13(a,b,c):
    s = divsion(a,b)
    assert s == c
if __name__ == "__main__":
    pytest.main(['-s','-v',"test_case06.py::test_13"])

结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case06.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- E:\Python\Python37\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.9', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.4', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'allure-pytest': '2.9.43', 'html': '3.1.1', 'metadata': '1.11.0'}, 'JAVA_HOME': 'E:\\Java\\jdk'}
rootdir: D:\Workspace\projects\Exercise\pytest_test, configfile: pytest.ini
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collecting ... collected 9 items

test_case06.py::test_13[1-3-3] PASSED
test_case06.py::test_13[1-3-0] FAILED
test_case06.py::test_13[1-3-6] FAILED
test_case06.py::test_13[2-0-3] FAILED
test_case06.py::test_13[2-0-0] PASSED
test_case06.py::test_13[2-0-6] FAILED
test_case06.py::test_13[7-0-3] PASSED
test_case06.py::test_13[7-0-0] PASSED
test_case06.py::test_13[7-0-6] PASSED

================================== FAILURES ===================================

python traceback

traceback object 用于打印异常信息出错路径,可帮助我们更清楚地定位出程序的异。打印出traceback object信息必须使用sys.exc_info()这个函数 ,可通过下面的代码运行结果看出,sys.exc_info()返回的是一个元组,分别为异常的类型、异常值、异常的路径对象,而这个路径对象就是traceback object:

import sys

def fun():
    raise Exception("--error--")
def operate():
    try:
        fun()
    except Exception as e :
        type,value,traceback_object = sys.exc_info()
        print("type: \t%s,\nvalue:\t%s,\ntraceback_object:\t%s"%(type,value,traceback_object))

if __name__ == "__main__":
    operate()

结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case07.py
type: 	<class 'Exception'>,
value:	--error--,
traceback_object:	<traceback object at 0x000001D8DD99B7C8>

Process finished with exit code 0

traceback模块输出异常信息的方法有很多种:print_tb、print_exception、print_exc、format_exc等
在这里插入图片描述

print_tb

traceback.print_tb(tb[, limit[, file]])
tb:traceback对象,通过sys.exc_info()获取到的
limit:设置打印stack trace的层级,不设置则默认打印所有
file:设置打印的输出流的,可以为文件,也可以是stdout之类的file-like object。如果不设或为None,则输出到sys.stderr

import traceback
import sys

def fun():
    raise Exception("--error--")
def operate():
    try:
        fun()
    except Exception as e :
        type,value,traceback_object = sys.exc_info()
        print("type: \t%s,\nvalue:\t%s,\ntraceback_object:\t%s"%(type,value,traceback_object))
        traceback.print_tb(traceback_object)

if __name__ == "__main__":
    operate()

结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case07.py
type: 	<class 'Exception'>,
value:	--error--,
traceback_object:	<traceback object at 0x000001B17D11B888>
  File "D:/Workspace/projects/Exercise/pytest_test/test_case07.py", line 9, in operate
    fun()
  File "D:/Workspace/projects/Exercise/pytest_test/test_case07.py", line 6, in fun
    raise Exception("--error--")

Process finished with exit code 0

print_exception

traceback.print_exception(etype, value, tb[, limit[, file]])
etype:sys.exc_info()返回的异常类型
value:sys.exc_info()返回的异常信息值
tb:traceback对象,通过sys.exc_info()获取到的
limit:设置打印stack trace的层级,不设置则默认打印所有
file:设置打印的输出流的,可以为文件,也可以是stdout之类的file-like object。如果不设或为None,则输出到sys.stderr
注:当异常为SyntaxError时,会有"^"来指示语法错误的位置

print_exc

traceback.print_exc([limit[, file]])
limit:设置打印stack trace的层级,不设置则默认打印所有
file:设置打印的输出流的,可以为文件,也可以是stdout之类的file-like object。如果不设或为None,则输出到sys.stderr
注:可自动获取到sys.exc_info()返回的三个值

format_exc
直接提字符串形式返回错误结果

logger = logging.getLogger("test_traceback")

def fun():
    raise Exception("--error--")
def operate():
    try:
        fun()
    except Exception as e :
        type,value,traceback_object = sys.exc_info()
        print("type: \t%s,\nvalue:\t%s,\ntraceback_object:\t%s"%(type,value,traceback_object))
        # traceback.print_tb(traceback_object)
        logger.error(traceback.format_exc(limit=1))


if __name__ == "__main__":
    operate()

结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case07.py
Traceback (most recent call last):
  File "D:/Workspace/projects/Exercise/pytest_test/test_case07.py", line 11, in operate
    fun()
Exception: --error--

type: 	<class 'Exception'>,
value:	--error--,
traceback_object:	<traceback object at 0x00000269D495BB88>

Process finished with exit code 0

pytest-指令输出traceback:

含义指令
traceback 输出当前变量值- -showlocals
输出当前变量值(shortcut)- - l
(default) ‘long’ tracebacks for the first and last–tb=auto
exhaustive, informative traceback formatting–tb=long
shorter traceback format–tb=short
only one line per failure–tb=line
python标准二进制格式Python standard library formatting
不输出异常路径–tb=no

执行失败时跳转PDB

使用pdb,在测试失败时开启调试:

含义指令
每次遇到失败都跳转到 PDB- -pdb
第一次遇到失败就跳转到 PDB,结束测试执行-x --pdb
只有前三次失败跳转到 PDB–pdb --maxfail=3

在这里插入图片描述
pdb调试符:

指令含义指令
输出变量 expr 的值p/print expr
美化输出 expr 的值pp expr
列出错误并显示错误之前和之后的5行代码l/list
列出错误,并显示指定行号之间的代码l/lsit begin, end
打印当前函数的所有参数和变量a/args
移动到堆栈的上一层u/up
移动到堆栈的下一层d/down
退出当前调试会话(也会退出测试会话)q/quit

更多pdb用法,参考:https://docs.python.org/3/library/pdb.html

设置断点

设置断点后,只会关闭当前test输出的执行,不会影响其它test:

import pdb

pdb.set_trace()

示例:

import pdb
import pytest

def test_09():
    return 3
def test_10():
    a = 1
    b = 2
    d = test_09()
    pdb.set_trace()
    c = a+b
    assert c == d

if __name__ == "__main__":
    pytest.main(['-v','-s','test_case08.py::test_10'])

运行了test_09(),test_10()断点停止运行下一步,结果:

E:\Python\Python37\python.exe D:/Workspace/projects/Exercise/pytest_test/test_case08.py
============================= test session starts =============================
platform win32 -- Python 3.7.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- E:\Python\Python37\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.9', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.4', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'allure-pytest': '2.9.43', 'html': '3.1.1', 'metadata': '1.11.0'}, 'JAVA_HOME': 'E:\\Java\\jdk'}
rootdir: D:\Workspace\projects\Exercise\pytest_test, configfile: pytest.ini
plugins: allure-pytest-2.9.43, html-3.1.1, metadata-1.11.0
collecting ... collected 1 item

test_case08.py::test_10 

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB set_trace >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> d:\workspace\projects\exercise\pytest_test\test_case08.py(13)test_10()
-> c = a+b
(Pdb) p d
3

获取用例执行数据

pytest --durations=10

生成JunitXML格式的结果文件

pytest --junitxml=path

禁用插件

如禁用docttest插件:

pytest -p no:doctest

从python代码中调用pytest

在这里插入图片描述

其它

1、多进程运行cases:pip install -U pytest-xdist
运行模式:pytest test_se.py -n NUM
Num为并发的进程数

2、生成html测试报告:pip install pytest-html
运行模式:pytest --html=./report.html
在当前目录会生成assets文件夹和report.html文件
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值