`pytest`:下载一次测试文件,并将其用于多次测试

kri*_*nab 8 python testing fixtures pytest

我正在用来pytest为我正在开发的包运行测试用例。这些测试使用我已保存为 github 资源的小图像文件。下面的代码工作得很好,但我认为pytest每次运行新测试时都会下载图像,这会花费不必要的时间和资源。我试图弄清楚如何下载一次文件,然后在测试用例之间共享它

这是一些示例代码。

# -- in conftest.py --
import sys
import pytest
import os
import shutil
import requests

@pytest.fixture(scope="function")
def small_image(tmpdir):
    url = 'https://github.com/.../sample_image_small.tif'

    r = requests.get(url)

    with open(os.path.join(str(tmpdir), 'sample_image_small.tif'), 'wb') as f:
        f.write(r.content)

    return os.path.join(str(tmpdir), 'sample_image_small.tif')
Run Code Online (Sandbox Code Playgroud)

然后这里有一些非常简单的测试用例,应该能够共享相同的图像。

# -- test_package.py --
import pytest
import os

@pytest.mark.usefixtures('small_image')


def test_ispath(small_image, compression):

    assert os.path.exists(small_image)

def test_isfile(small_image, compression):

    assert os.path.isfile(small_image)
Run Code Online (Sandbox Code Playgroud)

现在我相信pytest会尝试单独隔离每个测试,这就是导致文件重复下载的原因。我尝试设置@pytest.fixture(scope="module")代替,function但这会产生奇怪的错误:

ScopeMismatch: You tried to access the 'function' scoped fixture 'tmpdir' with a 'module' scoped request object, involved factories
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法来设置测试,这样我就不会一遍又一遍地下载文件?

hoe*_*ing 6

首先,预先说明:旧的tmpdir/tmpdir_factory固定装置对的更好替代方案是tmp_path/ tmp_path_factory,它处理pathlib对象而不是已弃用的py.path,请参阅临时目录和文件

其次,如果您想处理会话范围(或模块范围)的文件,则tmp*_factory需要使用固定装置。例子:

@pytest.fixture(scope='session')
def small_image(tmp_path_factory):
    img = tmp_path_factory.getbasetemp() / 'sample_image_small.tif'
    img.write_bytes(b'spam')
    return img
Run Code Online (Sandbox Code Playgroud)

现在每次测试运行都会sample_image_small.tif写入一次。


当然,按照 @MrBean Bremen 在他的回答中建议的使用没有任何问题tempfile,这只是做同样的事情的另一种选择,但仅使用标准pytest装置。


MrB*_*men 3

您可以使用相同的代码,只需自己处理临时文件,而不是使用固定tmpdir装置(不能在模块范围的固定装置中使用):

import os
import tempfile
import pytest
import requests

@pytest.fixture(scope="module")
def small_image():
    url = 'https://github.com/.../sample_image_small.tif'

    r = requests.get(url)
    f = tempfile.NamedTemporaryFile(delete=False):
    f.write(f.content)
    yield f.name
    os.remove(f.name)
Run Code Online (Sandbox Code Playgroud)

这将创建文件,返回文件名,并在测试完成后删除文件。

编辑: @hoefling 的答案显示了一种更标准的方法来做到这一点,我将留下这个供参考。