使用pytest编写测试时,若须要传递测试失败信息,能够直接使用Pytho自带的assert关键字。pytest与其余测试框架如unittest的区别以下所示:html
pytest | unittest |
---|---|
assert something | assertTrue(something) |
assert a==b | assertEqual(a,b) |
assert a<=b | assertLessEqual(a,b) |
... | ... |
pytest中assert主要有如下特色node
import pytest def addItemToList(item): tmpList=[] tmpList.append(item) return tmpList def test_addItemToList(): t1=addItemToList("a") t2=addItemToList("b") assert t1==t2
运行结果以下所示:python
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_01.py =============================test session starts ==================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 1 item test_01.py F [100%] ================================== FAILURES ====================================== ___________________________test_addItemToList ____________________________________ def test_addItemToList(): t1=addItemToList("a") t2=addItemToList("b") > assert t1==t2 E AssertionError: assert ['a'] == ['b'] E At index 0 diff: 'a' != 'b' E Use -v to get the full diff test_01.py:12: AssertionError =================================== short test summary info ========================= FAILED test_01.py::test_addItemToList - AssertionError: assert ['a'] == ['b'] ====================================1 failed in 0.29s ==============================
在上面的运行结果中,失败的测试用例在行首会有一个 > 来标识。以E开头的行是pytest提供的额外断定信息,用于帮助了解异常的具体信息。数组
在Python后来的版本,增长了函数参数注解功能,即在定义一个参数后,后面可直接声明参数的数据类型,方便其余人员知道如何调用这个函数,以下所示:微信
def add(x:int,y:int)->int: return x+y
为确保像这样的函数,在发生类型错误时,能够抛出异常,可使用with pytest.raises(< expected exception >),以下所示:session
def add(x:int,y:int)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)): raise TypeError("args must be int") else: return x+y def test_add(): with pytest.raises(TypeError): add("1","2")
运行结果以下所示:app
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_01.py ==============================test session starts ================================ platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 1 item test_01.py . [100%] =================================1 passed in 0.08s ===============================
测试用例中test_add()有with pytest.raises(TypeError)声明,则意味着不管with中的内容是什么,都至少会产生TypeError异常。若是测试经过,则说明确实发生了预期的TypeError异常,若是抛出的是其余类型的异常,则与预期不一致,测试失败。框架
在上面的测试用例中,仅检验了传参数据的类型异常,也能够检查值异常,好比在检验一个参数的数据类型以后,也能够再检验其内容,为校验异常消息是否符合预期,能够增长as exInfo语句获得异常消息的值,再进行校验,示例以下所示:函数
def add(x:int,y:int,operator:str)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") def test_add(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo
运行结果以下所示:测试
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_01.py =============================test session starts ============================ platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 1 item test_01.py . [100%] ============================1 passed in 0.13s ================================
pytest提供了标记机制,容许使用marker对测试函数进行标记,一个测试函数能够有多个marker,一个marker也能够用来标记多个测试函数。
对测试函数进行标记,一般使用的场景为冒烟测试,通常状况下冒烟测试不须要跑所有的测试用例,只须要选择关键的点进行测试,这个时候只跑被标记为冒烟测试的测试用例,会省不少时间。
示例以下所示:
import pytest def add(x:int,y:int,operator:str)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.testValueError @pytest.mark.smoke def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo @pytest.mark.testTypeErrorTest @pytest.mark.smoke def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
如今只须要在命令中指定-m markerName,就能够运行指定的测试用例,以下所示:
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest -m smoke .\test_02.py =========================test session starts =============================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 2 items test_02.py .. [100%] ============================ 2 passed, 4 warnings in 0.12s ==================================== PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest -m testValueError .\test_02.py ==============================test session starts ============================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 2 items / 1 deselected / 1 selected test_02.py . [100%] ========================= 1 passed, 1 deselected, 4 warnings in 0.04s ===========================
针对上面示例,能够得出如下结论
1.pytest.mark.markName:markName由用户自行定义,并不是pytest内置
2.-m:后面也可使用表达式,能够在标记之间添加and、or、not等关键字,以下所示:
pytest -m "testValueError and testTypeError" .\test_02.py pytest -m "testValueError and not testTypeError" .\test_02.py
pytest内置了一些标记,如skip、skipif、xfail。其中skip、skipif容许跳过不但愿运行的测试。示例以下所示:
1.要直接跳过某一个测试函数,可使用pytest.mark.skip()装饰器便可
import pytest def add(x:int,y:int,operator:str)->int: if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.testValueError @pytest.mark.smoke def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo @pytest.mark.skip(reason="skip this case") def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
运行结果以下所示:
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest -v .\test_02.py ==========================test session starts ================================================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 2 items test_02.py::test_addValueError PASSED [ 50%] test_02.py::test_addTypeError SKIPPED [100%]
2.添加跳过的缘由和条件,可使用pytest.mark.skipif()装饰器便可
若是有一些测试,只有在知足特定条件的状况下,才被跳过,这时候则可使用pytest.mark.skipif(),示例以下所示:
import pytest def add(x:int,y:int,operator:str)->int: """this is sample case""" if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.testValueError @pytest.mark.smoke def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo== exInfo @pytest.mark.skipif(add.__doc__=="this is sample case",reason="skip this case") def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
运行结果以下所示:
platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 2 items test_02.py::test_addValueError PASSED [ 50%] test_02.py::test_addTypeError SKIPPED [100%]
尽管跳过的缘由不是必须写,但仍是建议在实际项目尽量的写上,方便知道跳过的缘由,若是想知道跳过的详细缘由,可以使用参数 -rs
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest -rs -v .\test_02.py ======================test session starts ==================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 2 items test_02.py::test_addValueError PASSED [ 50%] test_02.py::test_addTypeError SKIPPED [100%] ==================================short test summary info ================================= SKIPPED [1] test_02.py:23: skip this case ==========================1 passed, 1 skipped, 2 warnings in 0.04s ========================
使用skip和skipif,在知足条件的下,会直接跳过,而不会执行。使用xfail标记,则是预期运行失败,以下所示:
import pytest def add(x:int,y:int,operator:str)->int: """this is sample case""" if not isinstance(x,(int,)) or not isinstance(y,(int,)) or not isinstance(operator,(str,)): raise TypeError("args must be int") if operator=="+": return x + y elif operator=="-": return x - y else: raise ValueError("operator must be '+' or '-' ") @pytest.mark.xfail def test_addValueError(): with pytest.raises(ValueError) as exInfo: add(1,2,"*") exInfo=exInfo.value.args[0] expectInfo="operator must be '+' or '-' " assert expectInfo!= exInfo @pytest.mark.xfail(add.__doc__=="this is sample case",reason="skip this case") def test_addTypeError(): with pytest.raises(TypeError): add("1","2","+")
运行结果以下所示:
PS C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02> pytest .\test_02.py ============================test session starts =================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 2 items test_02.py xX [100%] ======================= 1 xfailed, 1 xpassed in 0.19s ===============================
对于标记为xfail,但实际运行结果为XPASS的测试,能够在pytest配置中强制指定为FAIL,在pytest.ini文件按以下修改便可:
[pytest] xfail_strict=true
前面主要介绍对测试函数进行标记及如何运行。而运行测试子集能够按目录、文件、类中的测试,还能够选择运行某一个测试用例(可能在文件中,也能够在类中)**。
运行单个目录下的全部测试,以目录作为参数便可,以下所示:
pytest --tb=no .\PytestStudy\
运行单个测试文件/模块,以路径名加文件名作为参数便可,以下所示:
pytest --tb=no .\PytestStudy\Lesson02\test_01.py
运行单个测试函数,只须要在文件名后面添加::符号和函数名便可,以下所示:
pytest .\test_02.py::test_addTypeError
测试类用于将某些类似的测试函数组合在一块儿,来看看如下示例:
import pytest class Person: _age=28 _name="Surpass" def __init__(self,age,name): self._name=name self._age=age @classmethod def getAge(cls): if not isinstance(cls._age,(int,)): raise TypeError("age must be integer") else: return cls._age @classmethod def getName(cls): if not isinstance(cls._name,(str,)): raise TypeError("name must be string") else: return cls._name class TestPerson: def test_getAge(self): with pytest.raises(TypeError): Person.getAge("28") def test_getName(self): with pytest.raises(TypeError): Person.getName(123)
以上测试类中的方法都是测试Person中的方法,所以能够放在一个测试类中,要运行该类,能够在文件名后面添加::符号和类名便可,与运行单个测试函数相似,以下所示:
pytest .\test_03.py::TestPerson -v ========================== test session starts =========================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 2 items test_03.py::TestPerson::test_getAge PASSED [ 50%] test_03.py::TestPerson::test_getName PASSED [100%] ============================= 2 passed in 0.04s ============================================
若是不但愿运行测试类中的全部测试方法,能够指定运行的测试方法名即,能够在文件名后面添加::类名::类中方法名便可,以下所示:
>>> pytest -v .\test_03.py::TestPerson::test_getName =========================== test session starts ================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 1 item test_03.py::TestPerson::test_getName PASSED [100%] ============================= 1 passed in 0.04s ===============================
-k选项容许用一个表达式指定须要运行的测试,表达式能够匹配测试名或其子串,表达式中也能够包含and、or、not等。
例如想运行目录中,全部文件中测试函数名包含_add的测试函数,可按以下方式进行操做:
>>> pytest -v -k _add ======================== test session starts =============================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 5 items / 2 deselected / 3 selected test_01.py::test_add PASSED [ 33%] test_02.py::test_addValueError XFAIL [ 66%] test_02.py::test_addTypeError XPASS [100%] ==================== 1 passed, 2 deselected, 1 xfailed, 1 xpassed in 0.14s ====================
向函数传值并校验其输出是软件测试的经常使用手段,但大部分状况下,仅使用一组数据是没法充分测试函数功能。参数化测试容许传递多组数据,一旦发现测试失败,pytest会及时抛出信息。
要使用参数化测试,须要使用装饰器pytest.mark.parametrize(args,argsvaules)来传递批量的参数。示例以下所示:
1.方式一
import pytest def add(x:int,y:int)->int: return x+y @pytest.mark.parametrize("paras",[(1,2),(3,5),(7,8),(10,-98)]) def test_add(paras): res=add(paras[0],paras[1]) expect=paras[0]+paras[1] assert res==expect
运行结果以下所示:
>>> pytest -v .\test_04.py ================================================= test session starts ================================================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 4 items test_04.py::test_add[paras0] PASSED [ 25%] test_04.py::test_add[paras1] PASSED [ 50%] test_04.py::test_add[paras2] PASSED [ 75%] test_04.py::test_add[paras3] PASSED [100%] =====================4 passed in 0.10s ====================================
在parametrize()中第一个参数字符串列表(paras),第二个参数是一个值列表,pytest会轮流对每一个paras作测试,并分别报告每一个测试用例的结果。
parametrize()函数工做正常,那若是把paras替换为键值对形式了,可否达到一样的效果,以下所示:
2.方式二
import pytest def add(x:int,y:int)->int: return x+y @pytest.mark.parametrize(("x","y"),[(1,2),(3,5),(7,8),(10,-98)]) def test_add(x,y): res=add(x,y) expect=x+y assert res==expect
运行结果以下所示:
>>> pytest -v .\test_04.py ====================test session starts ================================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 4 items test_04.py::test_add[1-2] PASSED [ 25%] test_04.py::test_add[3-5] PASSED [ 50%] test_04.py::test_add[7-8] PASSED [ 75%] test_04.py::test_add[10--98] PASSED [100%] ======================4 passed in 0.16s =============================
若是传入的参数具备标识性,则在输出结果中也一样具有可标识性,加强可读性。也可使用完整的测试标识(pytest术语为node),若是标识符中包含空格,则须要使用引号。从新运行指定的测试以下所示:
>>> pytest -v test_04.py::test_add[1-2] ================================ test session starts ======================================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 1 item test_04.py::test_add[1-2] PASSED [100%] ========================== 1 passed in 0.04s ===================================
3.方式三
除了以上两种方式以外,还可使用以下方式进行参数化,以下所示:
import pytest def add(x:int,y:int)->int: return x+y paras=((1,2),(3,5),(7,8),(10,-98)) @pytest.mark.parametrize("p",paras) def test_add(p): res=add(p[0],p[1]) expect=p[0]+p[1] assert res==expect
运行结果以下所示
>>> pytest -v test_04.py ================================ test session starts ====================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 4 items test_04.py::test_add[p0] PASSED [ 25%] test_04.py::test_add[p1] PASSED [ 50%] test_04.py::test_add[p2] PASSED [ 75%] test_04.py::test_add[p3] PASSED [100%] ==============================4 passed in 0.14s ======================================
1.方式一
以上几种虽然能够达到参数化的目的,但可读性不太友好,为改善可读性,能够为parametrize()引入一个额外的参数ids,使列表中每一个元素都被标识。ids是一个字符串列表和数据对象列表和长度一致。以下所示:
import pytest def add(x:int,y:int)->int: return x+y paras=((1,2),(3,5),(7,8),(10,-98)) parasIds=[f"{x},{y}" for x,y in paras] @pytest.mark.parametrize("p",paras,ids=parasIds) def test_add(p): res=add(p[0],p[1]) expect=p[0]+p[1] assert res==expect
运行结果以下所示
>>> pytest -v test_04.py ========================= test session starts =================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 4 items test_04.py::test_add[1,2] PASSED [ 25%] test_04.py::test_add[3,5] PASSED [ 50%] test_04.py::test_add[7,8] PASSED [ 75%] test_04.py::test_add[10,-98] PASSED 100%] =================================== 4 passed in 0.11s ============================
parametrize()不只适于通常的测试函数,也能够适用类,在用于类中,则数据集会被传递给该类的全部方法,以下所示:
import pytest paras=((1,2),(3,5),(7,8),(10,-98)) parasIds=[f"{x},{y}" for x,y in paras] class Oper: _x=0 _y=0 def __int__(self,x,y): self._x=x self._y=y @classmethod def add(cls,x,y): return x+y @classmethod def sub(cls,x,y): return x-y @pytest.mark.parametrize("p",paras,ids=parasIds) class TestOper: def test_add(self,p): res=Oper.add(p[0],p[1]) expect=p[0]+p[1] assert res==expect def test_sub(self,p): res = Oper.sub(p[0], p[1]) expect = p[0] - p[1] assert res == expect
运行结果以下所示
>>> pytest -v test_04.py ============================= test session starts ============================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 8 items test_04.py::TestOper::test_add[1,2] PASSED [ 12%] test_04.py::TestOper::test_add[3,5] PASSED [ 25%] test_04.py::TestOper::test_add[7,8] PASSED [ 37%] test_04.py::TestOper::test_add[10,-98] PASSED [ 50%] test_04.py::TestOper::test_sub[1,2] PASSED [ 62%] test_04.py::TestOper::test_sub[3,5] PASSED [ 75%] test_04.py::TestOper::test_sub[7,8] PASSED [ 87%] test_04.py::TestOper::test_sub[10,-98] PASSED [100%] ============================ 8 passed in 0.13s ================================
2.方式二
在给@pytest.mark.parametrize()装饰器传入参数列表时,还能够在参数值中定义一个id作为标识,其语法格式以下所示:
@pytest.mark.parametrize(<value>,id="id")
示例以下所示:
import pytest class Oper: _x=0 _y=0 def __int__(self,x,y): self._x=x self._y=y @classmethod def add(cls,x,y): return x+y @classmethod def sub(cls,x,y): return x-y @pytest.mark.parametrize("p",[pytest.param((1,2),id="id-1"),pytest.param((3,5),id="id-2"), pytest.param((7,8),id="id-3"),pytest.param((10,-98),id="id-4")]) class TestOper: def test_add(self,p): res=Oper.add(p[0],p[1]) expect=p[0]+p[1] assert res==expect def test_sub(self,p): res = Oper.sub(p[0], p[1]) expect = p[0] - p[1] assert res == expect
运行结果以下所示
>>> pytest -v test_04.py ==============test session starts ==================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson02 collected 8 items test_04.py::TestOper::test_add[id-1] PASSED [ 12%] test_04.py::TestOper::test_add[id-2] PASSED [ 25%] test_04.py::TestOper::test_add[id-3] PASSED [ 37%] test_04.py::TestOper::test_add[id-4] PASSED [ 50%] test_04.py::TestOper::test_sub[id-1] PASSED [ 62%] test_04.py::TestOper::test_sub[id-2] PASSED [ 75%] test_04.py::TestOper::test_sub[id-3] PASSED [ 87%] test_04.py::TestOper::test_sub[id-4] PASSED [100%] =============================== 8 passed in 0.19s =============================
在id不能被参数批量生成,须要自定义时,这个方法很是适用。
原文地址:http://www.javashuo.com/article/p-qreunrvh-nn.html
本文同步在微信订阅号上发布,如各位小伙伴们喜欢个人文章,也能够关注个人微信订阅号:woaitest,或扫描下面的二维码添加关注: