鉴于这些文件:
# bar.py
barvar = []
def barfun():
barvar.append(1)
# foo.py
import bar
foovar = []
def foofun():
foovar.append(1)
if __name__ == '__main__':
foofun()
bar.barfun()
foovar.append(2)
bar.barvar.append(2)
print(f'{foovar =}')
print(f'{bar.barvar=}')
# test_foo.py
import sys
import os
import pytest
import runpy
sys.path.insert(0,os.getcwd()) # so that "import bar" in foo.py works
@pytest.mark.parametrize('execution_number', range(5))
def test1(execution_number):
print(f'\n{execution_number=}\n')
sys.argv=[os.path.join(os.getcwd(),'foo.py')]
runpy.run_path('foo.py',run_name="__main__")
如果我现在运行
pytest test_foo.py -s
我会得到:
========================================================================
platform win32 -- Python 3.10.8, pytest-7.2.0, pluggy-1.0.0
rootdir: C:\Temp
plugins: anyio-3.6.2
collected 5 items
test_foo.py
execution_number=0
foovar =[1, 2]
bar.barvar=[1, 2]
.
execution_number=1
foovar =[1, 2]
bar.barvar=[1, 2, 1, 2]
.
execution_number=2
foovar =[1, 2]
bar.barvar=[1, 2, 1, 2, 1, 2]
.
execution_number=3
foovar =[1, 2]
bar.barvar=[1, 2, 1, 2, 1, 2, 1, 2]
.
execution_number=4
foovar =[1, 2]
bar.barvar=[1, 2, 1, 2, 1, 2, 1, 2, 1, 2]
.
========================================================================
所以
barvar
正在记住它之前的内容。这显然不利于测试。
还在用
runpy
就可以预防吗?
可以理解,python docs 警告
runpy
副作用:
请注意,这不是沙盒模块 - 所有代码都在当前进程中执行,任何副作用(例如其他模块的缓存导入)将在函数返回后保留。
如果这很棘手或太复杂而无法可靠地完成,是否有其他选择? 我正在寻找测试接受参数并生成内容(通常是文件)的脚本的便利性。我典型的
pytest
测试脚本通过 sys.argv
设置参数,然后通过 runpy
运行目标脚本(具有大量导入的非常大的程序),然后验证生成的文件(例如,与回归测试的基线进行比较)。一次测试运行中有许多调用;因此需要一个干净的石板。
subprocess.run(['python.exe', 'script.py', *arglist])
是我能想到的另一种选择。
谢谢。
简单实用的解决方案,在测试设置中驱逐“缓存”栏模块(如果有):
@pytest.fixture(autouse=True)
def evict_bar():
sys.modules.pop("bar", None)
如果您无法重构您的代码,您不想从
sys.modules
中删除模块(两种解决方案都可以)。您可以只修补 barvar
变量设置初始状态,以便为每个测试执行一个空列表。
from unittest import mock
@pytest.mark.parametrize('execution_number', range(5))
def test1(execution_number):
with mock.patch('bar.barvar', new_callable=list):
print(f'\n{execution_number=}\n')
sys.argv=[os.path.join(os.getcwd(),'foo.py')]
runpy.run_path('foo.py',run_name="__main__", init_globals={'bar.barvar': []})