如何使用 python glob 获取简单功能的 DRYer 测试?
How to get DRYer tests for simple function using python glob?
我有一个功能可以在当前位置搜索文件,然后在下载文件夹中搜索,如果找不到,则会引发错误。使用 pytest 和 pytest-mock,我能够使用比测试代码大得多的代码来测试代码。有没有办法做到这一点 tighter/DRYer?
测试代码:
# cei.py
import glob
import os
def get_xls_filename() -> str:
""" Returns first xls filename in current folder or Downloads folder """
csv_filenames = glob.glob("InfoCEI*.xls")
if csv_filenames:
return csv_filenames[0]
home = os.path.expanduser("~")
csv_filenames = glob.glob(home + "/Downloads/InfoCEI*.xls")
if csv_filenames:
return csv_filenames[0]
return sys.exit(
"Error: file not found."
)
这里有三个测试场景。在当前找到,在下载中找到但没有找到。
测试代码:
# test_cei.py
from unittest.mock import Mock
import pytest
from pytest_mock import MockFixture
import cei
@pytest.fixture
def mock_glob_glob_none(mocker: MockFixture) -> Mock:
"""Fixture for mocking glob.glob."""
mock = mocker.patch("glob.glob")
mock.return_value = []
return mock
@pytest.fixture
def mock_os_path_expanduser(mocker: MockFixture) -> Mock:
"""Fixture for mocking os.path.expanduser."""
mock = mocker.patch("os.path.expanduser")
mock.return_value = "/home/user"
return mock
def test_get_xls_filename_not_found(mock_glob_glob_none, mock_os_path_expanduser) -> None:
with pytest.raises(SystemExit):
assert cei.get_xls_filename()
mock_glob_glob_none.assert_called()
mock_os_path_expanduser.assert_called_once()
@pytest.fixture
def mock_glob_glob_found(mocker: MockFixture) -> Mock:
"""Fixture for mocking glob.glob."""
mock = mocker.patch("glob.glob")
mock.return_value = ["/my/path/InfoCEI.xls"]
return mock
def test_get_xls_filename_current_folder(mock_glob_glob_found) -> None:
assert cei.get_xls_filename() == "/my/path/InfoCEI.xls"
mock_glob_glob_found.assert_called_once()
@pytest.fixture
def mock_glob_glob_found_download(mocker: MockFixture) -> Mock:
"""Fixture for mocking glob.glob."""
values = {
"InfoCEI*.xls": [],
"/home/user/Downloads/InfoCEI*.xls": ["/home/user/Downloads/InfoCEI.xls"],
}
def side_effect(arg):
return values[arg]
mock = mocker.patch("glob.glob")
mock.side_effect = side_effect
return mock
def test_get_xls_filename_download_folder(
mock_glob_glob_found_download, mock_os_path_expanduser
) -> None:
assert cei.get_xls_filename() == "/home/user/Downloads/InfoCEI.xls"
mock_os_path_expanduser.assert_called_once()
mock_glob_glob_found_download.assert_called_with(
"/home/user/Downloads/InfoCEI*.xls"
)
这显然有点主观,但我会尝试。
首先,测试比测试代码大没有错。根据测试用例的数量,这很容易发生,我不会用它作为测试质量的标准。
也就是说,您的测试通常应该测试 API / 接口,在这种情况下,它是在不同条件下返回的文件路径。测试 os.path.expanduser
是否被调用是内部实现的一部分,可能不稳定——至少在这种情况下,我认为这不是一件好事。您已经测试了最相关的用例(可能会添加在两个位置都有文件的测试),其中使用了这些内部构件。
以下是我可能会做的事情:
import os
import pytest
from cei import get_xls_filename
@pytest.fixture
def cwd(fs, monkeypatch):
fs.cwd = "/my/path"
monkeypatch.setenv("HOME", "/home/user")
def test_get_xls_filename_not_found(fs, cwd) -> None:
with pytest.raises(SystemExit):
assert get_xls_filename()
def test_get_xls_filename_current_folder(fs, cwd) -> None:
fs.create_file("/my/path/InfoCEI.xls")
assert get_xls_filename() == "InfoCEI.xls" # adapted to your implementation
def test_get_xls_filename_download_folder(fs, cwd) -> None:
path = os.path.join("/home/user", "Downloads", "InfoCEI.xls")
fs.create_file(path)
assert get_xls_filename() == path
请注意,我使用了 pyfakefs fixture fs
来模拟 fs(我是 pyfakefs 的贡献者,所以这是我习惯的,它使代码更短),但这对你来说可能有点矫枉过正。
基本上,我尝试只测试 API,将通用设置(这里是 cwd 和主路径位置)放入夹具(或在类似 xUnit 的测试的设置方法中),然后添加特定于测试的设置(测试文件的创建)到测试本身。
我有一个功能可以在当前位置搜索文件,然后在下载文件夹中搜索,如果找不到,则会引发错误。使用 pytest 和 pytest-mock,我能够使用比测试代码大得多的代码来测试代码。有没有办法做到这一点 tighter/DRYer?
测试代码:
# cei.py
import glob
import os
def get_xls_filename() -> str:
""" Returns first xls filename in current folder or Downloads folder """
csv_filenames = glob.glob("InfoCEI*.xls")
if csv_filenames:
return csv_filenames[0]
home = os.path.expanduser("~")
csv_filenames = glob.glob(home + "/Downloads/InfoCEI*.xls")
if csv_filenames:
return csv_filenames[0]
return sys.exit(
"Error: file not found."
)
这里有三个测试场景。在当前找到,在下载中找到但没有找到。 测试代码:
# test_cei.py
from unittest.mock import Mock
import pytest
from pytest_mock import MockFixture
import cei
@pytest.fixture
def mock_glob_glob_none(mocker: MockFixture) -> Mock:
"""Fixture for mocking glob.glob."""
mock = mocker.patch("glob.glob")
mock.return_value = []
return mock
@pytest.fixture
def mock_os_path_expanduser(mocker: MockFixture) -> Mock:
"""Fixture for mocking os.path.expanduser."""
mock = mocker.patch("os.path.expanduser")
mock.return_value = "/home/user"
return mock
def test_get_xls_filename_not_found(mock_glob_glob_none, mock_os_path_expanduser) -> None:
with pytest.raises(SystemExit):
assert cei.get_xls_filename()
mock_glob_glob_none.assert_called()
mock_os_path_expanduser.assert_called_once()
@pytest.fixture
def mock_glob_glob_found(mocker: MockFixture) -> Mock:
"""Fixture for mocking glob.glob."""
mock = mocker.patch("glob.glob")
mock.return_value = ["/my/path/InfoCEI.xls"]
return mock
def test_get_xls_filename_current_folder(mock_glob_glob_found) -> None:
assert cei.get_xls_filename() == "/my/path/InfoCEI.xls"
mock_glob_glob_found.assert_called_once()
@pytest.fixture
def mock_glob_glob_found_download(mocker: MockFixture) -> Mock:
"""Fixture for mocking glob.glob."""
values = {
"InfoCEI*.xls": [],
"/home/user/Downloads/InfoCEI*.xls": ["/home/user/Downloads/InfoCEI.xls"],
}
def side_effect(arg):
return values[arg]
mock = mocker.patch("glob.glob")
mock.side_effect = side_effect
return mock
def test_get_xls_filename_download_folder(
mock_glob_glob_found_download, mock_os_path_expanduser
) -> None:
assert cei.get_xls_filename() == "/home/user/Downloads/InfoCEI.xls"
mock_os_path_expanduser.assert_called_once()
mock_glob_glob_found_download.assert_called_with(
"/home/user/Downloads/InfoCEI*.xls"
)
这显然有点主观,但我会尝试。
首先,测试比测试代码大没有错。根据测试用例的数量,这很容易发生,我不会用它作为测试质量的标准。
也就是说,您的测试通常应该测试 API / 接口,在这种情况下,它是在不同条件下返回的文件路径。测试 os.path.expanduser
是否被调用是内部实现的一部分,可能不稳定——至少在这种情况下,我认为这不是一件好事。您已经测试了最相关的用例(可能会添加在两个位置都有文件的测试),其中使用了这些内部构件。
以下是我可能会做的事情:
import os
import pytest
from cei import get_xls_filename
@pytest.fixture
def cwd(fs, monkeypatch):
fs.cwd = "/my/path"
monkeypatch.setenv("HOME", "/home/user")
def test_get_xls_filename_not_found(fs, cwd) -> None:
with pytest.raises(SystemExit):
assert get_xls_filename()
def test_get_xls_filename_current_folder(fs, cwd) -> None:
fs.create_file("/my/path/InfoCEI.xls")
assert get_xls_filename() == "InfoCEI.xls" # adapted to your implementation
def test_get_xls_filename_download_folder(fs, cwd) -> None:
path = os.path.join("/home/user", "Downloads", "InfoCEI.xls")
fs.create_file(path)
assert get_xls_filename() == path
请注意,我使用了 pyfakefs fixture fs
来模拟 fs(我是 pyfakefs 的贡献者,所以这是我习惯的,它使代码更短),但这对你来说可能有点矫枉过正。
基本上,我尝试只测试 API,将通用设置(这里是 cwd 和主路径位置)放入夹具(或在类似 xUnit 的测试的设置方法中),然后添加特定于测试的设置(测试文件的创建)到测试本身。