Pytest04-编写测试函数

4.编写测试函数

4.1 使用assert声明

    使用pytest编写测试时,若需要传递测试失败信息,可以直接使用Pytho自带的assert关键字。pytest与其他测试框架如unittest的区别如下所示:

pytest unittest
assert something assertTrue(something)
assert a==b assertEqual(a,b)
assert a<=b assertLessEqual(a,b)
... ...

    pytest中assert主要有以下特点

  • 1.assert 后面可以添加任何表达式,若表达式的结果通过bool转换结果为True,则代表测试通过
  • 2.pytest 可以重写assert关键字。pytest可截断对原生assert的调用,替换为pytest定义的assert,从而提供更多的详细失败信息
import pytest

def addItemToList(item):
    tmpList=[]
    tmpList.append(item)
    return tmpList


def test_addItemToList():
    t1=addItemToList("a")
    t2=addItemToList("b")
    assert t1==t2

运行结果如下所示:

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提供的额外判定信息,用于帮助了解异常的具体信息。

4.2 预期异常

    在Python后来的版本,增加了函数参数注解功能,即在定义一个参数后,后面可直接声明参数的数据类型,方便其他人员知道如何调用这个函数,如下所示:

def add(x:int,y:int)->int:
    return x+y

    为确保像这样的函数,在发生类型错误时,可以抛出异常,可以使用with pytest.raises(< expected exception >),如下所示:

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")

运行结果如下所示:

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 ================================

4.3 测试函数标记

    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

4.4 跳过测试

    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 ========================

4.5 标记预期失败的测试

    使用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 ===============================
  • x代表XFAIL,意味着expect to fail(预期失败,实际上也失败)
  • X代表XPASS,意味着expect to fail but passed(预期失败,实际上成功)

    对于标记为xfail,但实际运行结果为XPASS的测试,可以在pytest配置中强制指定为FAIL,在pytest.ini文件按如下修改即可:

[pytest]
xfail_strict=true

4.6 运行测试子集

    前面主要介绍对测试函数进行标记及如何运行。而运行测试子集可以按目录文件类中的测试,还可以选择运行某一个测试用例(可能在文件中,也可以在类中)**。

4.6.1 单个目录

    运行单个目录下的所有测试,以目录做为参数即可,如下所示:

pytest --tb=no .\PytestStudy\

4.6.2 单个测试文件/模块

    运行单个测试文件/模块,以路径名加文件名做为参数即可,如下所示:

pytest --tb=no .\PytestStudy\Lesson02\test_01.py

4.6.3 单个测试函数

    运行单个测试函数,只需要在文件名后面添加::符号和函数名即可,如下所示:

pytest .\test_02.py::test_addTypeError

4.6.4 单个测试类

    测试类用于将某些相似的测试函数组合在一起,来看看以下示例:

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 ============================================

4.6.5 单个测试类中的测试方法

    如果不希望运行测试类中的所有测试方法,可以指定运行的测试方法名即,可以在文件名后面添加::类名::类中方法名即可,如下所示:

>>> 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 ===============================

4.6.6 用测试名划分测试集合

    -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 ====================

4.7 参数化测试

4.7.1 参数化方式

    向函数传值并校验其输出是软件测试的常用手段,但大部分情况下,仅使用一组数据是无法充分测试函数功能。参数化测试允许传递多组数据,一旦发现测试失败,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 ======================================

4.7.2 添加额外参数ids

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不能被参数批量生成,需要自定义时,这个方法非常适用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270