Pytest测试实战第1至4章

Pytest测试实战

一、使用约束

在执行pytest命令时,会自动从当前目录及子目录中寻找符合以下约束的测试函数来执行。可以在pytest.ini中修改测试目录、测试模块、测试类、测试方法扫描进行默认修改。

  1. 在单测文件中,所有测试类以Test开头,并且不能带’init’方法

  2. 在单测类中,可以包含一个或者多个以’test_'开头的函数

二、常用命令

  1. pytest -v test_xxx.py —> -v参数可以得到更完整的前后对比信息。
  2. pytest -collect-only test_xxx.py —> 用来显示给定配置下会有哪些测试用例被运行。也可以用**’–co’**参数。
  3. pytest -k “add or passing” --co —> 使表达式指定特定的测试用例运行。
  4. pytest -m test_xxx.py —> 该选项用于运行做过标记的用例。标记使用装饰器:@pytest.mark
  5. pytest -x test_xxx.py —> 用于断言失败或者触发了异常时立即停止整个会话。方便调试。
  6. pytest –maxfail=1 test_xxx.py —> 指定失败几次后停止,num等于1时相当于-x。
  7. pytest -s test_xxx.py —> 允许测试时输出任何符合标准的输出流信息,例如print。
  8. pytest -lf test_xxx.py —> 用于重新运行之前失败的用例,如果没有任何失败,则全部重跑。
  9. pytest -ff test_xxx.py —> 先运行失败的用例再运行其他的。
  10. pytest -q test_xxx.py —> 与 -v 相反,安静模式,只显示关键的信息。
  11. pytest -l test_xxx.py —> 用来失败时显示局部变量。
  12. pytest –duration test_xxx.py —> 用来显示哪个阶段占用时间最长。
  13. pytest --setup-show test_xxx.py —> 用来查看fixture的执行过程。

三、标记操作

  1. @pytest.mark.skip(reason=’’) —> 跳过一些不想执行的测试用例。

  2. @pytest.mark.skipif(version==5, reason=’’) —> 满足某个条件时跳过一些不想执行的测试用例。

  3. @pytest.mark.xfail() —> 用来标记一些预计原本就不会执行通过的测试用例,如果预计失败运行失败,显示x,预计失败运行成功,显示X。

  4. @pytest.mark.parametrize(“name”, [“Jone”, “Mark”]) —> 用来参数化测试

    @pytest.mark.parametrize("name, age", [("Mark", 18), ("Joe", 20)])
    def test_user_name(name, age):
        assert name == "Joe" and age == 20
    
  5. @pytest.mark.usefixtures(‘fixture1’, ‘fixture2’) 标记测试函数或者类。使用usefixtures需要在参数列表中指定一个或者多个fixture字符串。非常适合测试类。

四、组织测试用例合集

  1. 执行某个目录下的单元测试:cd path && pytest
  2. 执行单个文件:pytest test_xx.py
  3. 执行单个测试类:pytest test_xx.py::TestClass
  4. 执行某个测试类中的某个用例:pytest test_xx.py::TestClass::test_class_01

五、fixture

fixture是在测试函数运行前后,由pytest执行的外壳函数。fixture中的代码可以定制,满足多变的测试需求,包括定义传入测试中的数据集、配置测试前系统的初始状态、为批量测试提供数据源等。

**@pytest.fixture()**用于指定函数是一个fixture。如果测试函数的参数列表中包含fixture名,那么会被pytest检测到,并在函数运行之前运行该fixture。fixture可以完成任务,也可以返回数据给测试函数。

"""使用fixture执行配置及销毁逻辑"""

import pytest

@pytest.fixture()
def some_data():
    """Return answer to ultimate question"""
    return 42


def test_some_data(some_data):
    """User fixture return value in a test"""
    assert some_data == 42
    
# 使用pytest test_task.py::test_some_data --setup-show 命令执行以上测试用例,会看到如下结果,其中fixture名称前的F和S代表的是fixture的作用范围,F代表函数级别,S代表会话级别。
'''
========================================================================================== test session starts ==========================================================================================
platform win32 -- Python 3.6.8, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\yuyy\PycharmProjects\djangoProject\tests, configfile: pytest.ini
plugins: html-3.1.1, metadata-1.11.0
collected 1 item                                                                                                                                                                                         

test_task.py
        SETUP    F some_data
        unit/test_task.py::test_some_data (fixtures used: some_data).
        TEARDOWN F some_data

=========================================================================================== 1 passed in 0.03s ===========================================================================================
'''
    

fixture包含一个叫scope的可选参数,用于控制fixture执行配置和销毁逻辑的频率。它有4个待选值:functionclassmodulesession(默认值为session)。

· scope=‘function’ —> 函数级别的fixture每个测试函数只需要运行一次。配置代码在测试用例运行之前运行,销毁代码在测试用例运行之后运行。function是scope参数的默认值。

· scope=‘class’ —> 类级别的fixture每个测试类只需要运行一次,无论测试类里有多少类方法都可以共享这个fixture。

· scope=‘module’ —> 模块级别的fixture每个模块只需要运行一次,无论模块里有多少个测试函数、类或者其他fixture都可以共享这个fixture。

· scope=‘session’ —> 会话级别的fixture每次会话只需要运行一次。一次pytest会话中的所有测试函数、方法都可以共享这个fixture。

使用示例如下:

import pytest

@pytest.fixture(scope='class')
def class_scope():
    print("a class scope.")
    
@pytest.fixture(scope='module')
def module_scope():
    print("a module scope.")

fixture作为测试函数的参数,也会被堆栈跟踪并纳入测试报告。如果fixture发生异常,则使用该fixture的测试用例会报ERROR而不是FAIL,这样用户就知道失败不是发生在核心测试函数内,而是在测试依赖的fixture。fixture传递数据的应用示例如下:

"""使用fixture来传递数据"""

import pytest

@pytest.fixture()
def a_tuple():
    return 1, 'foo', None, {'bar': 23}

def test_a_tuple(a_tuple):
    assert a_tuple[3]['bar'] == 32

使用usefixtures和在测试方法中添加fixture参数,二者大体上是差不多的。区别之一在于只有后者才能够使用fixture的返回值。使用示例如下:

import pytests

@pytests.mark.usefixtures('use_one', 'use_two')
class TestSomething():
    def test_1():
        assert 1 == 1

@pytest.fixture()
def use_one(scope='class'):
    print("class fixture 1")
    
@pytest.fixture()
def use_two(scope='class'):
    print("class fixture 2")

fixture的名字展示在使用它的测试或者其他fixture函数的参数列表上,通常和fixture函数名保持一致,但是也可以使用name参数对fixture重新命名。示例如下:

import pytest

@pytest.fixture(name='lue')
def ultimate_answer_to_lift_the_universe_and_everything():
    return 'love'

def test_everything(lue):
    assert lue == 'love'

pytest有一些内置的fixture,比如tmpdir、tmpdir_factory。其应用示例如下:

import pytest

# 使用内置的fixturetmpdir,它的返回值是py.path.local类型的一个对象。它只能作用于函数级别,所以只能针对测试函数使用tmpdir创建文件或目录
# 如果需要fixture作用范围高于函数级别,应使用tmpdir_factory
def test_tmpdir(tmpdir):
    a_file = tmpdir.join("something.txt")
    a_sub_dir = tmpdir.mkdir('anything')
    another_file = a_sub_dir.join('something_else.txt')
    a_file.write("contents may settle during shipping.")
    another_file.write("something different")
    assert a_file.read() == 'contents may settle during shipping.'
    assert another_file.read() == 'something different'


# tmpdir_factory的作用范围是会话级别的,如果需要模块或者类级别作用范围目录,可以利用tmpdir_factory再创建一个fixture
def test_tmpdir_factory(tmpdir_factory):
    a_dir = tmpdir_factory.mktemp("mydir")  # 创建a_dir目录,在此函数内,它的效果与使用tmpdir是一样的。
    base_temp = tmpdir_factory.getbasetemp()  # getbasetemp()函数返回了该会话使用的根目录。
    print("base: ", base_temp)

    a_file = a_dir.join('something.txt')
    a_sub_dir = a_dir.mkdir('anything')
    another_file = a_sub_dir.join('something_else.txt')

    a_file.write("contents may settle during shipping.")
    another_file.write("something different")
    assert a_file.read() == 'contents may settle during shipping.'
    assert another_file.read() == 'something different'


@pytest.fixture(scope='module')
def author_file_json(tmpdir_factory):
    """
    创建一个临时目录data,并在此目录下创建了一个author_file.json。然后将python_author_data字典数据写入JSON文件中。
    """
    python_author_data = {
        'Ned': {'City': 'Boston'},
        'Brian': {'City': 'Portland'}
    }
    file = tmpdir_factory.mktemp('data').join('author_file.json')

    with file.open('w') as f:
        json.dump(python_author_data, f)
    return file


def test_brain_in_poland(author_file_json):
    with author_file_json.open() as f:
        authors = json.loads(f)
    assert authors['Brain']['City'] == 'Portland'


def test_all_have_cities(author_file_json):
    with author_file_json.open() as f:
        authors = json.loads(f)
    for a in authors:
        assert len(authors[a]['City']) > 0

六、Exit Code含义

  1. Exit code 0:所有用例执行完毕,全部通过

  2. Exit code 1:所有用例执行完毕,存在Failed的测试用例

  3. Exit code 2:用户中断了测试执行

  4. Exit code 3:测试过程中发生了内部错误

  5. Exit code 4:pytest 命令行使用错误

  6. Exit code 5:未采集到可用测试用例文件

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值