Python 3.x 如何重定向硬编码调用以打开自定义文件?
我编写了一些python代码,需要读取/etc/myapp/config.conf中的配置文件。我想写一个单元测试,看看如果文件不存在,或者包含坏值,通常会发生什么。让我们假设它看起来像这样Python 3.x 如何重定向硬编码调用以打开自定义文件?,python-3.x,unit-testing,Python 3.x,Unit Testing,我编写了一些python代码,需要读取/etc/myapp/config.conf中的配置文件。我想写一个单元测试,看看如果文件不存在,或者包含坏值,通常会发生什么。让我们假设它看起来像这样 """ myapp.py """ def readconf() """ Returns string of values read from file """ s = '' with open('/etc/myapp/config.conf', 'r') as f:
""" myapp.py
"""
def readconf()
""" Returns string of values read from file
"""
s = ''
with open('/etc/myapp/config.conf', 'r') as f:
s = f.read()
return s
然后我还有其他代码,它解析s
的值
我可以通过一些神奇的Python功能,调用
readconf
对open
重定向到作为测试环境一部分设置的自定义位置吗 您可以将配置文件外部化或参数化,而不是硬编码配置文件。有两种方法可以做到这一点:
$CONFIG
环境变量。您可以使用可以使用os.environ['CONFIG']
设置的环境变量运行测试李>
为了在函数中模拟对
open
的调用,而不是像Nf4r的回答那样用助手函数替换调用,您可以使用自定义补丁上下文管理器:
from contextlib import contextmanager
from types import CodeType
@contextmanager
def patch_call(func, call, replacement):
fn_code = func.__code__
try:
func.__code__ = CodeType(
fn_code.co_argcount,
fn_code.co_kwonlyargcount,
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
fn_code.co_code,
fn_code.co_consts,
tuple(
replacement if call == name else name
for name in fn_code.co_names
),
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab,
fn_code.co_freevars,
fn_code.co_cellvars,
)
yield
finally:
func.__code__ = fn_code
现在,您可以修补您的功能:
def patched_open(*args):
raise FileNotFoundError
with patch_call(readconf, "open", "patched_open"):
...
您可以使用mock来修补内置“open”模块的实例,以重定向到自定义函数
""" myapp.py
"""
def readconf():
s = ''
with open('./config.conf', 'r') as f:
s = f.read()
return s
""" test_myapp.py
"""
import unittest
from unittest import mock
import myapp
def my_open(path, mode):
return open('asdf', mode)
class TestSystem(unittest.TestCase):
@mock.patch('myapp.open', my_open)
def test_config_not_found(self):
try:
result = myapp.readconf()
assert(False)
except FileNotFoundError as e:
assert(True)
if __name__ == '__main__':
unittest.main()
@mock.patch('myapp.open', lambda path, mode: open('asdf', mode))
def test_config_not_found(self):
...
如果您不想声明另一个函数,也可以使用这样的lambda
""" myapp.py
"""
def readconf():
s = ''
with open('./config.conf', 'r') as f:
s = f.read()
return s
""" test_myapp.py
"""
import unittest
from unittest import mock
import myapp
def my_open(path, mode):
return open('asdf', mode)
class TestSystem(unittest.TestCase):
@mock.patch('myapp.open', my_open)
def test_config_not_found(self):
try:
result = myapp.readconf()
assert(False)
except FileNotFoundError as e:
assert(True)
if __name__ == '__main__':
unittest.main()
@mock.patch('myapp.open', lambda path, mode: open('asdf', mode))
def test_config_not_found(self):
...
查看
unittest.mock.patch
如@l3via所述,使用unittest.mock.patch
。最好将带有open()的作为f.read():
代码移动到分离函数,如读取文件(路径)
,然后模拟此特定函数,在调用时引发FileNotFound
异常。