当前位置:   article > 正文

Pytest单元测试_pytest用法

pytest用法

单元测试

1.定义

  • UT:(Unit Testing)模块
  • TDD模式:(test driven development) 测试驱动开发
  • first原则:fast,isolate,repeatable,self-validating,timely

2.目录结构

  • build:构建脚本,使用makefile管理安装,测试,发布等

  • docs:使用文档

  • src:核心代码,也可以和项目名相同

  • test:测试代码,与src目录结构一一对应,发布时不需要带该目录

    • Test_App

      • test_abc.py
      • pytest.ini
    • pyetst.ini配置文件内容

  • readme.md:项目介绍

  • requirements.txt:记录测试,编译和文档生成所有依赖的包信息,同maven的pom.xml文件,项目引入新包时

    更新:pip freeze > requirements.txt,其他开发者安装依赖pip install -r requirements.txt

3.测试框架

  • TestLoader
    • TestSuit(TestCase1)TestFixture1
    • TextTestRunner
    • TextTestResult

4.用例执行流程

setUpClass->setUp->Test Methond->tearDown
  • 1
  • setUpClassf方法在所有用例运行前执行
  • setUp方法在每个用例运行前执行
  • test_xxx测试用例按照ascii码顺序执行,各用例应独立,互不干扰
  • tearDown方法每个用例运行后执行

5.安装

pip install -U pytest # -U 是upgrade, 表示已安装就升级为最新版本
pip install pytest-cov # pytest代码覆盖率插件
pip install pytest-mock 
  • 1
  • 2
  • 3

6.执行

pytst--cov  --cov-report=html
  • 1

7.pytest配置文件

pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令运行时会使用该配置文件中的配置

  • 配置pytest命令行运行参数
[pytest]
# 配置pytest命令行运行参数 ,空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数
addopts = -s ...  
# 配置测试搜索的路径,当前目录下的scrip文件夹可自定义
testpaths = ./scripts 
# 配置测试搜索的文件名,当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件 -可自定义
python_files = test_*.py
# 配置测试搜索的测试类名,当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类
python_classes = Test_*
# 配置测试搜索的测试函数名,当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类内,以test_开头的⽅法 -可自定义
python_functions = test_*
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

8.mock

1.patch

object

import os


def rm(filename):
    os.remove(filename)


def test_rm(mocker):
    filename = 'test.file'
    mocker.patch('os.remove') # 输入被替换的函数字符串名,需要输入完整路径
    rm(filename)
    os.remove.assert_called_once_with(filename)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

method

class ForTest:
    field = 'origin'

    def method():
        pass


def test_for_test(mocker): # 仅仅需要mock一个object里的method,而无需mock整个object
    test = ForTest()
    mock_method = mocker.patch.object(test, 'method')
    test.method()
    assert mock_method.called

    assert 'origin' == test.field
    mocker.patch.object(test, 'field', 'mocked')
    assert 'mocked' == test.field
    
def test_patch_object_listdir(mocker):# 对一个给定module的function
    mock_listdir = mocker.patch.object(os, 'listdir')
    os.listdir()
    assert mock_listdir.called
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
object.path的参数含义
return_valuereturn_value修改了os.path.isfile的返回值,控制程序执行流,而无需在文件系统中生成文件
side_effectside_effect可以令某些函数抛出指定的异常
wrapswraps可以既把某些函数包装成MagicMock,又不改变它的执行效果(这一点类似spy),当然,也完全可以替换成另一个函数。
import os
import pytest


def name_length(filename):
    if not os.path.isfile(filename):
        raise ValueError('{} is not a file!'.format(filename))
    print(filename)
    return len(filename)


def test_name_length0(mocker):
    isfile = mocker.patch('os.path.isfile', return_value=True)
    assert 4 == name_length('test')
    isfile.assert_called_once()

    isfile.return_value = False
    with pytest.raises(ValueError):
        name_length('test')
    assert 2 == isfile.call_count


def test_name_length1(mocker):
    mocker.patch('os.path.isfile', side_effect=TypeError)
    with pytest.raises(TypeError):
        name_length('test')


def test_name_length2(mocker):
    mocker.patch('os.path.isfile', return_value=True)
    mock_print = mocker.patch('builtins.print', wraps=print)
    mock_len = mocker.patch(__name__ + '.len', wraps=len)
    assert 4 == name_length('test')
    assert mock_print.called
    assert mock_len.called
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

2.spy

def test_spy_listdir(mocker):
    mock_listdir = mocker.spy(os, 'listdir')
    os.listdir()
    assert mock_listdir.called
  • 1
  • 2
  • 3
  • 4

注意:

1.pytest中使用mock

9.setup和teardown函数

函数级别:运行测试方法的始末,即运行一次测试函数运行一次setup和teardown

类级别:在一个测试内只运行一次setup_class和teardown_class,不关心测试类 内有多少个测试函数

import pytest


class Test_ABC:
		def setup_class(self):
      		print("-----> setup_class")
       
    	def teardown_class(self):
      		print("----> teardown_class")
      		
		def setup(self):
			print("-----> setup_method")
		
		def teardown(self):
			print("----> teardown_method")
			
		def test_a(self):
			print("----> test_a")
			assert 1
		
		def test_b(self):
			print("----> test_b")

if __name__ == "__main__":
		pytest.main("-s test.py")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

10.fixture函数

1.定义

用于完成预置处理和重复操作

2.场景

  • 每个测试用例都要登录和退出,利用fixture就可以只做一次
  • 完成setup和teardown操作,处理数据库或文件的打开,关闭操作
  • 准备测试数据.将数据提前写入数据库或通过params返回给测试用例

3.使用方法

pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)
  • 1

4.作为参数应用

import pytest


@pytest.fixture()
def before():
    print("-----> 在每个函数前执行\n")


def test_1(before):
    print('-----> test_11\n')
    assert 1


def test_2(before):
    print("----> test_22\n")
    assert 1


if __name__ == "__main__":
    pytest.main("-s test.py")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

5.作为函数应用

import pytest


@pytest.fixture()
def before():
    print('\nbefore each test')


@pytest.mark.usefixtures("before")
def test_1():
    print('--->test_11')


@pytest.mark.usefixtures("before")
def test_2(before):
    print("----> test_22")


@pytest.mark.usefixtures("before")
class Test3:
    def test_5(self):
        print('test_31')

    def test_6(self):
        print('test_32')


if __name__ == "__main__":
    pytest.main("-s test.py")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

6.作用域

scope: 被标记方法的作用域;

  • “function”: 默认值,表示每个测试方法都要执行一次

  • “class”: 作用于整个类, 表示每个类的所有测试方法只运行一次

  • “module”: 作用于整个模块, 每个module的所有测试方法只运行一次.

  • “session”: 作用于整个session, 每次session只运行一次. (此方法慎用!!)

autouse: 是否自动运行,默认为false, 为true时此session中的所有测试函数都会调用fixture

import pytest

#作用于整个模块,表示这个模块的只运行一次fixture,例如所有的test都需要连接同一个数据库,可以使用此方法,对于此模块的所有test都有效
@pytest.fixture(scope="module", autouse=True)
def before():
    print('\nbefore each test')


@pytest.mark.usefixtures("before")
def test_1():
    print('--->test_1')

class Test2:
    def test_5(self):
        print('test_5')

    def test_6(self):
        print('test_6')

if __name__ == "__main__":
    pytest.main("-s test_1.py")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

7.返回值

  • params: list类型,默认None, 接收参数值,对于param里面的每个值,fixture都会去遍历执行一次
import pytest

@pytest.fixture(params=[1, 2, 3])
def need_data(request): # 传入参数request 系统封装参数
      return request.param # 取列表中单个值,默认的取值方式

class Test_ABC:
    def test_a(self,need_data):
        print("test_a 的值是 %s" % need_data)
        assert need_data != 3 # 断言need_data不等于3

if __name__ == '__main__':
    pytest.main("-s  test_abc.py")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

11.skipif函数

1.定义

根据特定条件,不执行标识的测试函数

2.使用方法

@pytest.mark.skipif(condition, reason='xxx')
  • 1

3.示例

import pytest

class Test_ABC:
    def setup_class(self):
        print("\nsetup")

    def teardown_class(self):
        print("\nteardown")

    def test_a(self):
        print("test_a")

    @pytest.mark.skipif(condition=2>1, reason="跳过")
    def test_b(self):
        print("test_b")
        assert 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

11.xfail函数

1.定义

标记某测试函数会失败

2.使用方法

@pytest.mark.xfail(condition, reason="xxx")
  • 1

3.示例

import pytest

class Test_ABC:
    def setup_class(self):
        print("\nsetup")

    def teardown_class(self):
        print("\nteardown")

    def test_a(self):
        print("test_a")

    @pytest.mark.xfail(condition=2>1, reason="预期失败")
    def test_b(self):
        print("test_b")
        assert 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

12.parametrize函数

1.定义

标记某测试函数会失败

2.使用方法

@pytest.mark.xfail(condition, reason="xxx")
  • 1

3.示例

import pytest

class Test_ABC:
    def setup_class(self):
        print("setup")

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

    def test_a(self):
        print("test_a")

    @pytest.mark.parametrize("a", [3,6])
    def test_b(self, a):
        print("test data:a=%d" % a)
        assert a%3 == 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

13.pytest-cov 插件

1.定义

统计代码覆盖率

2.安装

pip install pytest-cover
  • 1

3.使用

pytest-cov --cov-report=html
  • 1

注意:

pytest-cov是基于coverage插件使用的

14.pytest-ordering插件

1.定义

以函数修饰符的方式标记被测函数,通过参数控制函数执行顺序

2.安装

pip install pytest-ordering
  • 1

3.使用

import pytest


class Test_ABD:
		def setup_class(self):
      	print("-----> setup_class")
       
    def teardown_class(self):
      	print("----> teardown_class")
     
    @pytest.mark.run(order=2)
    def test_a(self):
      	print("----> test_a")
        assert 1
    
    @pytest.mark.run(order=1)
    def test_b(self):
      	print("----> test_b")
        assert 0

if __name__ == "__main__":
  	pytest.main("-s test_abc.py")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • order值全为正数或全为负数时, 值越小,优先级越高
  • 正数和负数同时存在,正数优先级高,0的优先级最高
  • 0 > 较小整数> 较大的整数 > 无标记 > 较小的负数 > 较大的负数

15.pytest-rerunfailures插件

1.定义

使用命令行方式,控制失败函数的重试次数

2.安装

pip install pytest-rerunfailures
  • 1

3.使用

pytest addopts = -s --reruns 2 --html=./report.html # -s是输出程序运行信息 -n为重试的次数
  • 1

16.pytest-asyncio插件

1.定义

编写和运行异步测试用例,轻松处理异步代码中的事件循环,协程和任务

2.安装

pip install  pytest-asyncio
  • 1

3.使用

import asyncio
import pytest


async def add_numbers(a, b):
    await asyncio.sleep(1)  # 模拟异步操作
    return a + b


@pytest.mark.asyncio
async def test_add_numbers():
    result = await add_numbers(2, 3)
    assert result == 5

async def calculate_by_tool(value) -> int:
    value += 999
    process = await asyncio.create_subprocess_shell(f'echo {value}', stdout=asyncio.subprocess.PIPE)
    out, err = await process.communicate()
    return int(out and out.decode() or '0')


@pytest.mark.asyncio
async def test_should_return_right_value_when_calculate_by_tool():
    value = await calculate_by_tool(1000)
    assert value == 1999
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号