这是我所面临的问题的一个简化版本:假设我有一个函数,它接受一个目录的路径,然后删除它的所有内容,除了(可选地)指定的"keep文件"
import os
KEEP_FILE_CONSTANT = '.gitkeep'
def clear_directory(directory: str, retain: bool = True) -> bool:
try:
filelist = list(os.listdir(directory))
for f in filelist:
filename = os.path.basename(f)
if retain and filename == KEEP_FILE_CONSTANT:
continue
os.remove(os.path.join(directory, f))
return True
except OSError as e:
print(e)
return False
我正试图为这个函数编写一个单元测试,以验证os.remove
被调用。这是我目前测试它的方式:
import pytest
from unittest.mock import ANY
@pytest.mark.parametrize('directory', [
('random_directory_1'),
('random_directory_2'),
# ...
])
@patch('module.os.remove')
def test_clear_directory(delete_function, directory):
clear_directory(directory)
delete_function.assert_called()
delete_function.assert_called_with(ANY)
理想情况下,我想在测试中断言的是delete_function
是用包含directory
的参数调用的,例如,
delete_function.assert_called_with(CONTAINS(directory))
之类的。我一直在看PyHamcrest,特别是contains_string函数,但我不确定如何在这里应用它,或者如果它甚至可能。
是否有任何方法来实现这个用例的包含匹配器?
这不是对您问题的直接回答,但如果由我来编写这些测试,我会采用不同的方法:
- 创建临时目录
- 实际删除文件
- 检查只剩下预期的文件。
这样,您正在测试您想要的实际行为,而不依赖于内部实现细节(即您正在使用os.remove()
而不是Pathlib.unlink()
之类的替代方案)。
如果您不熟悉,pytest为这种测试提供了一个tmp_path
fixture。但是,填充临时目录仍然有点麻烦,特别是如果您想要测试各种嵌套的文件层次结构。我写了一个名为tmp_files
的夹具,使这更容易,虽然,我认为它可能很适合您的问题。下面是测试的样子:
import pytest
# I included tests for nested files, even though the sample function you
# provided doesn't support them, just to show how to do it.
@pytest.mark.parametrize(
'tmp_files, to_remove, to_keep', [
pytest.param(
{'file.txt': ''},
['file.txt'],
[],
id='remove-1-file',
),
pytest.param(
{'file-1.txt': '', 'file-2.txt': ''},
['file-1.txt', 'file-2.txt'],
[],
id='remove-2-files',
),
pytest.param(
{'file.txt': '', 'dir/file.txt': ''},
['file.txt', 'dir/file.txt'],
[],
id='remove-nested-files',
marks=pytest.mark.xfail,
),
pytest.param(
{'.gitkeep': ''},
[],
['.gitkeep'],
id='keep-1-file',
),
pytest.param(
{'.gitkeep': '', 'dir/.gitkeep': ''},
[],
['.gitkeep', 'dir/.gitkeep'],
id='keep-nested-files',
marks=pytest.mark.xfail,
),
],
indirect=['tmp_files'],
)
def test_clear_directory(tmp_files, to_remove, to_keep):
clear_directory(tmp_files)
for p in to_remove:
assert not os.path.exists(tmp_files / p)
for p in to_keep:
assert os.path.exists(tmp_files / p)
简要地解释一下,tmp_files
参数指定在每个临时目录中创建什么文件,并且只是将文件名映射到文件内容的字典。这里所有的文件都是简单的文本文件,但也可以创建符号链接、fifo等。indirect=['tmp_files']
参数很容易被忽略,但非常重要。它告诉pytest使用tmp_files
参数对tmp_files
fixture进行参数化。