如何正确测试执行文件操作的 python 函数?
How do I properly test python function doing file operation?
我有一个函数可以做一些文件操作和
在 /etc/hosts 文件中为该 IP 创建条目以进行 DNS 解析
def add_hosts_entry():
ip_addr = "1.2.3.4"
HOST_FILE_PATH = "/etc/hosts"
reg_svc_name = "SVC_NAME"
try:
with open(HOST_FILE_PATH, 'r+') as fp:
lines = fp.readlines()
fp.seek(0)
fp.truncate()
for line in lines:
if not reg_svc_name in line:
fp.write(line)
fp.write(f"{ip_addr}\t{reg_svc_name}\n")
except FileNotFoundError as ex:
LOGGER.error(f"Failed to read file. Details: {repr(ex)}")
sys.exit(1)
LOGGER.info(
f"Successfully made entry in /etc/hosts file:\n{ip_addr}\t{reg_svc_name}"
)
- 我想测试一下我在文件中确实有一个IP条目
制作。
- 并且只有 1 个 IP 地址映射到
reg_svc_name
我找到了如何模拟 open()
。
到目前为止我有这个但不确定如何检查以上两种情况:
@pytest.fixture
def mocker_etc_hosts(mocker):
mocked_etc_hosts_data = mocker.mock_open(read_data=etc_hosts_sample_data)
mocker.patch("builtins.open", mocked_etc_hosts_data)
def test_add_hosts_entry(mocker_etc_hosts):
with caplog.at_level(logging.INFO):
registry.add_hosts_entry()
# how to assert??
解决方案 1
不要模拟 open
功能,因为我们希望它实际更新我们可以检查的文件。相反,拦截它并打开一个测试文件而不是源代码中使用的实际文件。在这里,我们将使用tmp_path to create a temporary file更新进行测试。
src.py
def add_hosts_entry():
ip_addr = "1.2.3.4"
HOST_FILE_PATH = "/etc/hosts"
reg_svc_name = "SVC_NAME"
try:
with open(HOST_FILE_PATH, 'r+') as fp:
lines = fp.readlines()
fp.seek(0)
fp.truncate()
for line in lines:
if not reg_svc_name in line:
fp.write(line)
fp.write(f"{ip_addr}\t{reg_svc_name}\n")
except FileNotFoundError as ex:
print(f"Failed to read file. Details: {repr(ex)}")
else:
print(f"Successfully made entry in /etc/hosts file:\n{ip_addr}\t{reg_svc_name}")
test_src.py
import pytest
from src import add_hosts_entry
@pytest.fixture
def etc_hosts_content_raw():
return "some text\nhere\nSVC_NAME\nand the last!\n"
@pytest.fixture
def etc_hosts_content_updated():
return "some text\nhere\nand the last!\n1.2.3.4\tSVC_NAME\n"
@pytest.fixture
def etc_hosts_file(tmp_path, etc_hosts_content_raw):
file = tmp_path / "dummy_etc_hosts"
file.write_text(etc_hosts_content_raw)
return file
@pytest.fixture
def mocker_etc_hosts(mocker, etc_hosts_file):
real_open = open
def _mock_open(file, *args, **kwargs):
print(f"Intercepted. Would open {etc_hosts_file} instead of {file}")
return real_open(etc_hosts_file, *args, **kwargs)
mocker.patch("builtins.open", side_effect=_mock_open)
def test_add_hosts_entry(
mocker_etc_hosts, etc_hosts_file, etc_hosts_content_raw, etc_hosts_content_updated
):
assert etc_hosts_file.read_text() == etc_hosts_content_raw
add_hosts_entry()
assert etc_hosts_file.read_text() == etc_hosts_content_updated
输出
$ pytest -q -rP
. [100%]
============================================== PASSES ===============================================
_______________________________________ test_add_hosts_entry ________________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Intercepted. Would open /tmp/pytest-of-nponcian/pytest-13/test_add_hosts_entry0/dummy_etc_hosts instead of /etc/hosts
Successfully made entry in /etc/hosts file:
1.2.3.4 SVC_NAME
1 passed in 0.05s
如果您有兴趣,也可以显示临时虚拟文件以查看处理结果:
$ cat /tmp/pytest-of-nponcian/pytest-13/test_add_hosts_entry0/dummy_etc_hosts
some text
here
and the last!
1.2.3.4 SVC_NAME
解决方案 2
模拟open
以及.write
操作。模拟后,通过 call_args_list 查看对模拟 .write
的所有调用。不推荐这样做,因为感觉我们正在编写一个更改检测器测试,它与源代码的逐行实现方式紧密耦合,而不是检查行为。
我有一个函数可以做一些文件操作和 在 /etc/hosts 文件中为该 IP 创建条目以进行 DNS 解析
def add_hosts_entry():
ip_addr = "1.2.3.4"
HOST_FILE_PATH = "/etc/hosts"
reg_svc_name = "SVC_NAME"
try:
with open(HOST_FILE_PATH, 'r+') as fp:
lines = fp.readlines()
fp.seek(0)
fp.truncate()
for line in lines:
if not reg_svc_name in line:
fp.write(line)
fp.write(f"{ip_addr}\t{reg_svc_name}\n")
except FileNotFoundError as ex:
LOGGER.error(f"Failed to read file. Details: {repr(ex)}")
sys.exit(1)
LOGGER.info(
f"Successfully made entry in /etc/hosts file:\n{ip_addr}\t{reg_svc_name}"
)
- 我想测试一下我在文件中确实有一个IP条目 制作。
- 并且只有 1 个 IP 地址映射到
reg_svc_name
我找到了如何模拟 open()
。
到目前为止我有这个但不确定如何检查以上两种情况:
@pytest.fixture
def mocker_etc_hosts(mocker):
mocked_etc_hosts_data = mocker.mock_open(read_data=etc_hosts_sample_data)
mocker.patch("builtins.open", mocked_etc_hosts_data)
def test_add_hosts_entry(mocker_etc_hosts):
with caplog.at_level(logging.INFO):
registry.add_hosts_entry()
# how to assert??
解决方案 1
不要模拟 open
功能,因为我们希望它实际更新我们可以检查的文件。相反,拦截它并打开一个测试文件而不是源代码中使用的实际文件。在这里,我们将使用tmp_path to create a temporary file更新进行测试。
src.py
def add_hosts_entry():
ip_addr = "1.2.3.4"
HOST_FILE_PATH = "/etc/hosts"
reg_svc_name = "SVC_NAME"
try:
with open(HOST_FILE_PATH, 'r+') as fp:
lines = fp.readlines()
fp.seek(0)
fp.truncate()
for line in lines:
if not reg_svc_name in line:
fp.write(line)
fp.write(f"{ip_addr}\t{reg_svc_name}\n")
except FileNotFoundError as ex:
print(f"Failed to read file. Details: {repr(ex)}")
else:
print(f"Successfully made entry in /etc/hosts file:\n{ip_addr}\t{reg_svc_name}")
test_src.py
import pytest
from src import add_hosts_entry
@pytest.fixture
def etc_hosts_content_raw():
return "some text\nhere\nSVC_NAME\nand the last!\n"
@pytest.fixture
def etc_hosts_content_updated():
return "some text\nhere\nand the last!\n1.2.3.4\tSVC_NAME\n"
@pytest.fixture
def etc_hosts_file(tmp_path, etc_hosts_content_raw):
file = tmp_path / "dummy_etc_hosts"
file.write_text(etc_hosts_content_raw)
return file
@pytest.fixture
def mocker_etc_hosts(mocker, etc_hosts_file):
real_open = open
def _mock_open(file, *args, **kwargs):
print(f"Intercepted. Would open {etc_hosts_file} instead of {file}")
return real_open(etc_hosts_file, *args, **kwargs)
mocker.patch("builtins.open", side_effect=_mock_open)
def test_add_hosts_entry(
mocker_etc_hosts, etc_hosts_file, etc_hosts_content_raw, etc_hosts_content_updated
):
assert etc_hosts_file.read_text() == etc_hosts_content_raw
add_hosts_entry()
assert etc_hosts_file.read_text() == etc_hosts_content_updated
输出
$ pytest -q -rP
. [100%]
============================================== PASSES ===============================================
_______________________________________ test_add_hosts_entry ________________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Intercepted. Would open /tmp/pytest-of-nponcian/pytest-13/test_add_hosts_entry0/dummy_etc_hosts instead of /etc/hosts
Successfully made entry in /etc/hosts file:
1.2.3.4 SVC_NAME
1 passed in 0.05s
如果您有兴趣,也可以显示临时虚拟文件以查看处理结果:
$ cat /tmp/pytest-of-nponcian/pytest-13/test_add_hosts_entry0/dummy_etc_hosts
some text
here
and the last!
1.2.3.4 SVC_NAME
解决方案 2
模拟open
以及.write
操作。模拟后,通过 call_args_list 查看对模拟 .write
的所有调用。不推荐这样做,因为感觉我们正在编写一个更改检测器测试,它与源代码的逐行实现方式紧密耦合,而不是检查行为。