文章目录 1. 单元测试用例目录 2. 自动化测试用例编写步骤 3. 命名规则 4. 环境安装 5. pytest语法 5.1 unittest与pytest对比 5.2 pytest运行插件 5.3 fixture 5.4 装饰器 6. pytest.ini 7. conftest.py 8. 用例编写步骤 9. 单元测试示例 10. 运行 11. 覆盖率 12. 单个文件覆盖率详情 14. 测试报告 15.Mock 15.1 什么是Mock 15.2 Mock的定义 15.3 Mock的使用 15.4 Mock与MagicMock 15.5 Mock示例 16. MagicMock 17.Patch 17.1 patch能做什么 17.2 patch的目标 17.3 patch定义 17.3 patch的使用方式 17.3.1 装饰器方式 17.3.2 上下文管理器 17.3.3 手动方式
1. 单元测试用例目录
testPytest business .coveragerc # 覆盖率配置文件 pytest.ini # pytest测试框架配置文件,运行参数 run_unittest.py # 运行入口 tests assets # README.md图片目录 common # 公共方法 data # 单元测试用例数据目录 env-pytest3.6 # 测试工具环境 example # 示例 logs # 日志 business # 测试用例模块目录 conftest.py # 自定义hook程序 install_env.sh # python单元测试环境安装 README.md # 单元测试文档 requirements.txt # 单元测试工具安装
2. 自动化测试用例编写步骤
1. 初始化 - 用例执行前的动作
2. 执行 - 具体用例逻辑
3. 断言 - 校验用例执行结果
4. 清理 - 用例执行后的动作 - 一般是把用例修改的内容恢复到执行前的状态
3. 命名规则
pytest的命名规则:
1. 模块名(py文件)必须是以 test_ 开头或者 _test 结尾;
2. 测试类(class )必须是以 Test 开头,并且不能带 __init__ 方法;
3. 测试用例 - 类方法(method)必须是以 test_ 开头;
4. 测试用例 - 函数(function)必须是以 test_ 开头;
4. 环境安装
pip3 install env- pytest3. 6
pip3 install - r requirements. txt
env-pytest3.6:管软kos环境适配whl包
allure_pytest-2.9.45-py3-none-any.whl
allure_python_commons-2.9.45-py3-none-any.whl
atomicwrites-1.4.0-py2.py3-none-any.whl
attrs-21.4.0-py2.py3-none-any.whl
colorama-0.4.4-py2.py3-none-any.whl
importlib_metadata-4.8.3-py3-none-any.whl
iniconfig-1.1.1-py2.py3-none-any.whl
packaging-21.3-py3-none-any.whl
pluggy-1.0.0-py2.py3-none-any.whl
py-1.11.0-py2.py3-none-any.whl
pyparsing-3.0.7-py3-none-any.whl
pytest-7.0.1-py3-none-any.whl
pytest_assume-2.4.3-py3-none-any.whl
pytest_html-3.1.1-py3-none-any.whl
pytest_metadata-1.11.0-py2.py3-none-any.whl
pytest_repeat-0.9.1-py2.py3-none-any.whl
pytest_rerunfailures-10.2-py3-none-any.whl
setuptools-59.6.0-py3-none-any.whl
six-1.16.0-py2.py3-none-any.whl
tomli-1.2.3-py3-none-any.whl
typing_extensions-4.1.1-py3-none-any.whl
zipp-3.6.0-py3-none-any.whl
requirements.txt:python3.6版本单元测试包和版本
allure_pytest==2.9.45
allure_python_commons==2.9.45
atomicwrites==1.4.0
attrs==21.4.0
colorama==0.4.4
importlib_metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyparsing==3.0.7
pytest==7.0.1
pytest_assume==2.4.3
pytest_html==3.1.1
pytest_metadata==1.11.0
pytest_repeat==0.9.1
pytest_rerunfailures==10.2
setuptools==59.6.0
six==1.16.0
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0
5. pytest语法
5.1 unittest与pytest对比
5.2 pytest运行插件
5.3 fixture
fixture( scope, autouse, params, ids, name) :scope: 在什么层级下运行,它的值只有:session, package, module, class , function( 默认值) autouse: 代表的是否自动执行,若此参数不加或者设置为False 的话,代表不会自动运行,设置为True 自动执行,无需调用。params: 进行的数据参数化,参数化的数据就是通过此参数传入到测试用例中。ids: 它是给生成的结果的fixture进行重命名,主要是为了好理解,前提是必须要有参数params。name: 对fixture的函数名起别名,或者叫重命名,没啥大用处。session:如果等于此值,那么这个fixture在整个项目下只运行一次,这个特别适合于登录,登录在项目中只需要登录一次。package:如果设置为此值,那么这个fixture在这个包下只运行一次。module:如果设置为此值,那么这个fixture在这个文件中只运行一次,文件中可能既有函数又有类。class :如果设置为此值,那么这个fixture在这个类中只运行一次。function:它是这个函数的默认值,如果为此值,那么这个fixture在每个测试函数前运行一次。
True : 如果等于此值,此fixture将被自动调用,而且都是在测试用例前执行。False :该值是此参数的默认值,如果等于此值,此fixture将不会自动调用,需要主动调用。
它的值主要接受的是列表,而列表中的值存放的就是测试数据
它是给生成的结fixture进行重命名,因为它之前生成的模式是按照fixture_demo[ index] 进行命名的,其中这个index就是每次循环的数字,第一次为0 。如果这个不太好理解,就可以使用ids进行重命名,但是前提必须要有参数化: params。
它是给fixture函数重名名的,或者叫起别名,主要是为了调用时方便使用,一旦起别名,在测试用例中就必须的使用别名。
5.4 装饰器
6. pytest.ini
pytest. ini文件是pytest的主配置文件;
pytest. ini文件的位置一般放在项目的根目录下,不能随便放,也不能更改名字。
pytest. ini文件中都是存放的一些配置选项,这些选项都可以通过pytest - h查看到。
INI文件由节、键、值组成。
节 [ section]
选项/ 参数(键= 值) name= value
注释 注释使用分号表示(; )。在分号后面的文字,直到该行结尾都全部为注释。
# 例如:
[pytest]
;命令参数
addopts = -v ; 输出更加详细的信息-s ; 输出测试用例的打印信息,比如在用例中有print信息--reruns 3 ; 失败用例重跑,最多重试3次--reruns-delay 3 ; 重试前等待秒数--html=unittest_report.html ; 生成HTML报告--self-contained-html ; 合并CSS的HTML报告,分享报告样式不丢失--capture=sys ; 控制测试用例执行过程输出,sys表示捕获Python层级的sys.stdout和sys.stderr--cov=business --cov-branch --cov-config .coveragerc --cov-report=html:unittest_coverage_html --cov-report=xml:unittest_coverage.xml;指定测试用例跟目录,只有在pytest未指定文件目录或测试用例标识符时,该选项才有作用
;testpaths = ./tests;屏蔽不被检索单元测试用例的目录
;norecursedirs = .git;屏蔽告警
filterwarnings = ignore::DeprecationWarningasyncio_default_fixture_loop_scope = functionasyncio_mode = auto;解决测试用例参数话时ids中文乱码
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True;日志设置
log_cli = true
log_cli_level = INFO
log_cli_format = %(asctime)s - %(name)s - (%(levelname)s) - %(filename)s - %(lineno)d - %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
log_format = %(asctime)s - %(name)s - (%(levelname)s) - %(filename)s - %(lineno)d - %(message)s
log_date_format = %Y-%m-%d %H:%M:%S;测试用例匹配规则
python_files = test_*.py
python_classes = Test*
python_functions = test_*;注册自定义标签
markers =nas: file storagedos: object storagesan: block storagemix: unstructured storagesmoke: smoke test
7. conftest.py
conftest. py是fixture函数的一个集合,将一些前置动作放在fixture中,然后供其它测试用例调用。不同于普通被调用的模块,conftest. py使用时不需要导入,Pytest会首先查找这个文件。
调用系统之前你需要先做登录操作,获取token
执行测试用例前先需要搜集测试用例数据。
执行测试用例前先需要进行筛选数据等。
它的文件名是固定的,不可更改。
它可以放在项目的跟目录下,也可以放在每个测试用例的文件夹下。
每次执行时都是首先先运行此文件下的fixture函数,然后才去执行测试用例文件。
import os
import pytest
import datetime
from py. xml import html
from pytest_metadata. plugin import metadata_key"""
自定义hook程序
"""
global_data = { } def pytest_configure ( config) : temp_dir = '/tmp/pytest_temp' os. environ[ 'PYTEST_ADDOPTS' ] = f'--basetemp= { temp_dir} ' @pytest. fixture ( autouse= True , scope= "session" )
def fix_session ( ) : """会话级fixtureautouse:如果为 True,则对于可以看到它的所有测试,夹具函数将被激活。如果为 False(默认值),需要显式引用才能激活夹具。:return:""" print ( "fixture会话级:fix_session" ) @pytest. fixture ( autouse= True , scope= "module" )
def fix_module ( ) : """模块级fixture:return:""" print ( "fixture模块级:fix_module" ) @pytest. fixture ( autouse= True , scope= "class" )
def fix_class ( ) : """类级fixture:return:""" print ( "fixture类级:fix_class" ) @pytest. fixture ( autouse= True , scope= "function" )
def fix_func ( ) : """方法级fixture:return:""" print ( "fixture方法级:fix_func" ) @pytest. fixture
def set_global_data ( ) : """设置全局变量,用于关联参数:return:""" def _set_global_data ( key, value) : global_data[ key] = valuereturn _set_global_data@pytest. fixture
def get_global_data ( ) : """从全局变量global_data中取值:return:""" def _get_global_data ( key) : return global_data. get( key) return _get_global_datadef pytest_html_report_title ( report) : """报告标题""" report. title = "自动单元测试报告"
def pytest_configure ( config) : config. stash[ metadata_key] [ "项目名称" ] = "Gaia-V5 单元测试" config. stash[ metadata_key] [ "测试模块" ] = "XXX场景-用户-创建" config. stash[ metadata_key] [ "测试环境" ] = "集群环境:192.168.120.20" config. stash[ metadata_key] [ "测试版本" ] = "V7.1.10.3" config. stash[ metadata_key] [ "测试日期" ] = f" { datetime. datetime. now( ) . strftime( '%Y-%m-%d %H:%M:%S' ) } " def pytest_html_results_summary ( prefix, summary, postfix) : """报告摘要,测试人员信息""" prefix. extend( [ html. p( "所属部门: xxx研发部-研发五处" ) ] ) prefix. extend( [ html. p( "测试人员: 张三、李四" ) ] ) def pytest_html_results_table_header ( cells) : """处理结果表的表头""" cells. insert( 1 , html. th( "执行时间" , class_= "sortable time" , col= "time" ) ) cells. insert( 2 , html. th( "用例描述" , class_= "sortable description" , col= "description" ) ) cells. pop( - 1 ) def pytest_html_results_table_row ( report, cells) : """处理结果表的每一行""" cells. insert( 1 , html. td( datetime. datetime. now( ) . strftime( "%Y-%m-%d %H:%M:%S" ) , class_= "col-time" ) ) cells. insert( 2 , html. th( report. description, class_= "sortable description" , col= "description" ) ) cells. pop( - 1 ) @pytest. hookimpl ( hookwrapper= True )
def pytest_runtest_makereport ( item, call) : outcome = yield report = outcome. get_result( ) report. description = str ( item. function. __doc__) def pytest_html_results_table_html ( report, data) : """清除执行成功的用例logs""" if report. passed: del data[ : ] data. append( "这条用例测试通过!" ) @pytest. hookimpl ( optionalhook= True )
def pytest_metadata ( metadata) : """取到标题栏""" metadata. pop( "Platform" , None ) metadata. pop( "Packages" , None ) metadata. pop( "JAVA_HOME" , None ) metadata. pop( "Plugins" , None )
8. 用例编写步骤
testPytest
- - tests- - common - - data - - env- pytest3. 6 - - example - - logs - - sysmgt - - conftest. py - - README. md - - requirements. txt - - . coveragerc
- - pytest. ini
- - run_unittest. py
8.1 按照以下方式检查用例
from business. user_info import create_user
class TestCreateUser ( object ) : """xxx""" @classmethod def setup_class ( cls) : pass def setup_method ( self) : pass def test_create_user_id_success ( self) : """:return:创建用户成功:rtype:""" logger. info( f"模拟数据: { self. request_info} " ) result = create_user( self. request_info) logger. info( f"测试方法结果: { result} " ) assert json. loads( result) == RESULT_JSONassert xxx
9. 单元测试示例
"""
Author: xxx
date: 2025/3/3 10:53
Description:"""
import pytest
import random
from tests. common. logging_utils import CustomLogger
from business. funx import add
RESULT_JSON = { "version" : 0 , "operation_type" : 'op' , "error_code" : 0 }
logger = CustomLogger( "business" ) . loggerdef setup_module ( module) : """测试模块级别的fixture,模块开始时执行一次:param module::type module::return::rtype:""" logger. info( "<<<========测试模块执行前调用:setup_module========>>>" ) def teardown_module ( module) : """测试模块级别的fixture,模块结束时执行一次:param module::type module::return::rtype:""" logger. info( "<<<========测试模块执行后调用:teardown_module========>>>" ) def setup_function ( function) : """测试函数级别的fixture,函数开始时执行一次:param function::type function::return::rtype:""" logger. info( "<<<========测试函数执行前调用:setup_function========>>>" ) def teardown_function ( function) : """测试函数级别的fixture,函数结束后时执行一次:param function::type function::return::rtype:""" logger. info( "<<<========测试函数执行后调用:teardown_function========>>>" ) class TestFunx ( object ) : """业务代码,测试类""" @classmethod def setup_class ( cls) : """测试类级别的fixture,需要用@classmethod装饰器修饰:return::rtype:""" cls. a = random. randint( 1 , 100 ) cls. b = random. randint( 200 , 500 ) logger. info( "<<<========测试类执行前调用:setup_class========>>>" ) @classmethod def teardown_class ( cls) : """测试类级别的fixture,需要用@classmethod装饰器修饰:return::rtype:""" logger. info( "<<<========测试类执行后调用:teardown_class========>>>" ) def setup_method ( self) : """测试方法级别的fixture:return::rtype:""" logger. info( "<<<========测试方法执行前调用:setup_method========>>>" ) def teardown_method ( self) : """测试方法级别的fixture:return::rtype:""" logger. info( "<<<========测试方法执行后调用:teardown_method========>>>" ) def test_add_success ( self) : """求两个数的和:return::rtype:""" logger. info( f"模拟数据: { self. a} , { self. b} " ) result = add( self. a, self. b) assert result == self. a + self. bdef test_add_zero ( self) : """求两个数的和:return::rtype:""" self. a = 0 self. b = 0 logger. info( f"模拟数据: { self. a} , { self. b} " ) result = add( self. a, self. b) assert result == self. a + self. bdef test_add_negative ( self) : """求两个数的和,负数:return::rtype:""" self. a = - 1 self. b = - 2 logger. info( f"模拟数据: { self. a} , { self. b} " ) result = add( self. a, self. b) assert result == self. a + self. bif __name__ == '__main__' : pytest. main( )
10. 运行
python3 run_unittest. py
11. 覆盖率
unittest_coverage_html/ index. html
覆盖率:指的是代码被测试的程度。覆盖率越高,意味着代码被测试的部分越多,代码的质量通常也越高。
语句:指的是代码中的可执行语句。覆盖率报告会统计这些语句中有多少被执行了。
缺失:指的是代码中没有被测试到的语句。这些语句在测试过程中没有被执行到。
排除:指的是在覆盖率报告中被排除的语句。这些语句通常不会被测试,例如文档字符串、某些装饰器等。可以通过配置文件(如 . coveragerc)来指定哪些语句需要排除。
分支:指的是代码中的条件分支,例如 if 语句、for 循环等。覆盖率报告不仅会统计语句的覆盖率,还会统计分支的覆盖率,确保条件分支都被测试到。
部分:指的是部分分支被测试到的情况。例如,一个 if - else 语句中只有 if 分支被执行了,而 else 分支没有被执行,这种情况就被认为是部分覆盖率。
12. 单个文件覆盖率详情
14. 测试报告
unittest_report. html
15.Mock
参考官网进行学习并使用:26.6. unittest.mock 上手指南 — Python 3.6.15 文档
最新文档:unittest.mock — 新手入门 — Python 3.13.2 文档
15.1 什么是Mock
模块 功能 mock mock.Mock mock mock.patch mock mock.patch.object mock mock.multiple mock mock.dict
模块 功能 函数 描述 Mock 构造器 name mock对象的名称,是mock对象的标识 Mock 构造器 return_value mock对象被调用时的返回值 Mock 构造器 side_effect mock对象被调用时的返回,可以是函数,类等,覆盖return_value Mock 构造器 spec 将对象设置成mock的属性 Mock 构造器 spce_set 严格限制mock对象的属性访问,访问不存在的属性报错 Mock 构造器 wraps 模拟对象是装饰器的使用 Mock 构造器 unsafe 默认情况下,如果任何以assert或assret开头的属性都将引发错误,当unsafe=True时可以访问 Mock 断言方法 assert_not_called 模拟从未被调用过 Mock 断言方法 assert_called 至少调用了一次模拟 Mock 断言方法 assert_called_once 仅调用了一次模拟 Mock 断言方法 assert_called_with 使用指定的参数调用模拟 Mock 断言方法 assert_called_once_with 模拟被调用了一次,并且该调用使用了指定的参数 Mock 断言方法 assert_any_call 已使用指定的参数调用了模拟 Mock 管理方法 attach_mock 将一个mock对象添加到另一个mock对象中 Mock 管理方法 configure_mock 更改mock对象的return_value值 Mock 管理方法 mock_add_spec 给mock对象添加新的属性 Mock 管理方法 reset_mock 将mock对象回复到测试之前的状态 Mock 统计方法 called 标识是否调用过 Mock 统计方法 call_count 返回调用的次数 Mock 统计方法 call_args 获取调用时的参数 Mock 统计方法 call_args_list 获取调用的所有参数,结果是一个列表 Mock 统计方法 method_calls 测试一个mock对象都调用了哪些方法,返回一个列表 Mock 统计方法 mock_calls 测试对象对mock对象所有的调用,结果是一个列表
15.2 Mock的定义
class unittest . mock. Mock( spec= None , side_effect= None , return_value= DEFAULT, wraps= None , name= None , spec_set= None , unsafe= False , ** kwargs) spec: 参数可以把一个对象设置为 Mock 对象的属性。访问mock对象上不存在的属性或方法时,将会抛出属性错误。
side_effect :调用mock时的返回值,可以是函数,异常类,可迭代对象。使用side_effect可以将模拟对象的返回值变成函数,异常类,可迭代对象等。
当设置了该方法时,如果该方法返回值是DEFAULT,那么返回return_value的值,如果不是,则返回该方法的值。 return_value 和 side_effect 同时存在,side_effect会返回。
如果 side_effect 是异常类或实例时,调用模拟程序时将引发异常。
如果 side_effect 是可迭代对象,则每次调用 mock 都将返回可迭代对象的下一个值。
return_value :调用mock的返回值,模拟某一个方法的返回值。
wraps: 装饰器:模拟对象要装饰的项目。
如果wrapps不是None ,那么调用Mock将把调用传递给wrapped对象(返回实际结果)。
对mock的属性访问将返回一个mock对象,该对象装饰了包装对象的相应属性。
name :mock 的名称。 这个是用来命名一个mock对象,只是起到标识作用,当你print 一个mock对象的时候,可以看到它的name。
spec_set:更加严格的要求,spec_set= True 时,如果访问mock不存在属性或方法会报错
15.3 Mock的使用
使用mock. Mock( ) 可以创建一个mock对象,对象中的方法有两种设置途径:作为Mock类的参数传入。mock. Mock( return_value= 20 , side_effect= mock_fun, name= 'mock_sum' ) 实例化mock对象之后设置属性。mock_sum = mock. Mock( ) mock_sum. return_value = 20 mock_sum. side_effect = mock_fun
15.4 Mock与MagicMock
Mock主要模拟指定的方法和属性
MagicMock是Mock的子类,同时模拟了很多Magic方法(len , __str__等等)
如果执行代码逻辑中,某一个函数不想被真的调用的话,可以使用Mock方法
15.5 Mock示例
from unittest. mock import Mock
mock = Mock( )
mock. find_person. return_value = { "id" : 1 , "name" : "jack"
}
print ( mock. find_person( ) )
16. MagicMock
魔法方法:Python 中的类有一些特殊的方法。在python的类中,以两个下画线__开头和结尾的方法如__new__,__init__ 。
这些方法统称“魔术方法”(Magic Method)。任意自定义类都会拥有魔法方法。
使用魔术方法可以实现运算符重载,如对象之间使用 == 做比较时,其实是对象中 __eq__实现的。
魔法方法类似于对象默认提供的各种方法。__new__ 创建类并返回这个类的实例
__init__ 可理解为“构造函数”,在对象初始化的时候调用,使用传入的参数初始化该实例
__del__ 可理解为“析构函数”,当一个对象进行垃圾回收时调用
__class__
__delattr__
__dict__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init_subclass__
__le__
__lt__
__module__
__ne__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__weakref__Magic Mock 的默认值:
Magic Mock 实例化之后就会有一些初始值,是一些属性的实现。具体的默认值如下:
__lt__: NotImplemented
__gt__: NotImplemented
__le__: NotImplemented
__ge__: NotImplemented
__int__: 1
__contains__: False
__len__: 0
__iter__: iter ( [ ] )
__exit__: False
__aexit__: False
__complex__: 1j
__float__: 1.0
__bool__: True
__index__: 1
__hash__: default hash for the mock
__str__: default str for the mock
__sizeof__: default sizeof for the mock使用MagicMock和Mock的场景:
使用MagicMock:需要魔法方法的场景,如迭代
使用Mock:不需要魔法方法的场景可以用Mock
17.Patch
17.1 patch能做什么
Patch( ) 充当函数修饰器、类修饰器或上下文管理器。在函数体或with 语句中,使用patch中的new替换目标函数或方法。当function/ with 语句退出时,模拟程序被撤消。
功能 函数 描述 mock.patch target 模拟的目标对象 mock.patch new 默认对target的替换,模拟返回的结果 mock.patch new_callable 模拟返回的结果哦,是可调用对象,会覆盖new mock.patch spec 为mock对象添加属性 mock.patch spec_set 属性限制,访问mock没有的属性会报错 mock.patch autospec 标记mock对象属性全部被spce替换 mock.patch create 允许访问mock对象不存在的属性
autospec是一个更严格的规范。如果你设置了autospec= True ,将会使用spec替换对象的属性来创建一个mock对象。mock对象的所有属性都会被spec相应的属性替换。
被mock的方法和函数会检查他们的属性,如果调用它们没有属性会抛出 TypeError。它们返回的实例也会是相同属性的类。
默认情况下,patch( ) 将无法替换不存在的属性。如果你通过create= True ,当替换的属性不存在时,patch会创建一个属性,并且当函数退出时会删除掉属性。这对于针对生产代码在运行时创建的属性编写测试非常有用。它在默认情况下是关闭的,因为它可能是危险的,打开它后,您可以针对实际不存在的api编写通过测试的代码同时mock. patch也是mock的一个子类,所以可以用return_value 和 side_effect 直接使用 return_value
17.2 patch的目标
1. 目标必须是可以 import 的
2. 是在使用目标的地方替换而不是替换定义
17.3 patch定义
unittest. mock. patch( target, new= DEFAULT, spec= None , create= False , spec_set= None , autospec= None , new_callable= None , ** kwargs)
17.3 patch的使用方式
- 手动指定 装饰器 上下文管理器 优点 可以更精细控制mock的范文 方便mock多个对象 不足之处 需要手动start和stop 装饰器顺序和珊瑚参数相反容易混乱 一个with只能mock一个对象
patch有三种使用方法,最佳的使用实践是装饰器形态。手动指定方法需要start和stop控制,过于繁琐,虽然存在一个stopall的方法,但是仍然要逐个start
with 写法的缺点很明显,一次不可以mock多个目标,多个with 层层缩进很明显不可能。最佳实践:装饰器方法可以方便的mock多个对象,只需要熟悉装饰的顺序和函数参数的对应关系。
patch中可以return_value和new都可以改变结果,推荐patch中使用new属性,Mock中使用return_value.
17.3.1 装饰器方式
import unittest
from unittest. mock import Mock, patch
from myprj. service import student_serviceclass TestStudentService ( unittest. TestCase) : @patch ( "myprj.service.student_service.save_student" ) @patch ( "myprj.service.student_service.find_student_by_id" ) def test_change_name_detector ( self, find_student_mock, save_student_mock) : '''参数find_student_mock与save_student_mock上述@patch是逆序的方式''' student = Mock( id = 1 , name = 'Jack' ) find_student_mock. return_value = studentstudent_service. change_name( 1 , 'Tom' ) self. assertEqual( 'Tom' , student. name)
17.3.2 上下文管理器
def test_change_name_contextmanager ( self) : student = Mock( id = 1 , name = 'Jack' ) find_student_mock. return_value = studentwith patch( "myprj.service.student_service.find_student_by_id" ) as find_student_mock, \patch( "myprj.service.student_service.save_student" ) : find_student_mock. return_value = studentstudent_service. change_name( 1 , 'Tom' ) self. assertEqual( 'Tom' , student. name)
17.3.3 手动方式
@patch ( "myprj.service.student_service.find_student_by_id" ) def test_change_name_manual ( self, find_student_mock) : student = Mock( id = 1 , name = 'Jack' ) find_student_mock. return_value = studentpatcher = patch( "myprj.service.student_service.save_student" ) patcher. start( ) student_service. change_name( 1 , 'Tom' ) patcher. stop( ) self. assertEqual( 'Tom' , student. name)