Python 3.x 在py.test测试中重新启动python(或重新加载模块)

Python 3.x 在py.test测试中重新启动python(或重新加载模块),python-3.x,pytest,Python 3.x,Pytest,我有一个(python3)包,它的行为完全不同,这取决于它的init()ed(也许不是最好的设计,但重写不是一个选项)。模块只能被init()ed一次,第二次会出现错误。我想用py.test测试这个包(两种行为) 注意:软件包的性质使这两种行为相互排斥,没有可能的理由在单一程序中同时使用这两种行为 我的测试目录中有几个test_xxx.py模块。每个模块将以需要的方式初始化包(使用夹具)。由于py.test只启动一次python解释器,因此在一次py.test运行中运行所有测试模块都会失败 我不

我有一个(python3)包,它的行为完全不同,这取决于它的
init()
ed(也许不是最好的设计,但重写不是一个选项)。模块只能被
init()
ed一次,第二次会出现错误。我想用py.test测试这个包(两种行为)

注意:软件包的性质使这两种行为相互排斥,没有可能的理由在单一程序中同时使用这两种行为

我的测试目录中有几个
test_xxx.py
模块。每个模块将以需要的方式初始化包(使用夹具)。由于
py.test
只启动一次python解释器,因此在一次py.test运行中运行所有测试模块都会失败

我不想对包进行Monkey修补以允许第二次
init()
,因为存在可能导致无法解释的行为的内部缓存等

  • 是否可以告诉py.test在单独的python进程中运行每个测试模块(从而不受另一个测试模块中的inits的影响)
  • 是否有可靠地重新加载包(包括所有子依赖项等)的方法
  • 还有其他解决方案吗(我正在考虑导入并取消夹具中的包,但这似乎太过分了)

要重新加载模块,请尝试使用库
importlib中的
reload()

例如:

from importlib import reload
import some_lib
#do something
reload(some_lib) 
此外,在新流程中启动每个测试是可行的,但多处理代码调试起来有点痛苦

范例

import some_test
from multiprocessing import Manager, Process

#create new return value holder, in this case a list
manager = Manager()
return_value = manager.list()
#create new process
process =  Process(target=some_test.some_function, args=(arg, return_value))
#execute process
process.start()
#finish and return process
process.join()
#you can now use your return value as if it were a normal list, 
#as long as it was assigned in your subprocess

我也有同样的问题,并找到了三种解决方案:

  • 重新加载(一些库)
  • patch SUT,由于导入的方法是SUT中的键和值,因此可以对 苏特。例如,如果在m1中使用m2的f2,则可以修补m1.f2而不是m2.f2
  • 导入模块,并使用module.function

  • 我曾经遇到过类似的问题,但设计很糟糕

    @pytest.fixture()
    def module_type1():
        mod = importlib.import_module('example')
        mod._init(10)
        yield mod
        del sys.modules['example']
    
    @pytest.fixture()
    def module_type2():
        mod = importlib.import_module('example')
        mod._init(20)
        yield mod
        del sys.modules['example']
    
    
    def test1(module_type1)
        pass
    
    def test2(module_type2)
        pass
    
    示例/init.py有如下内容

    def _init(val):
        if 'sample' in globals():
            logger.info(f'example already imported, val{sample}' )
        else:
            globals()['sample'] = val
            logger.info(f'importing example with val : {val}')
    
    输出:

    importing example with val : 10
    importing example with val : 20
    

    不知道您的包有多复杂,但如果它只是全局变量,那么这可能会有所帮助。

    删除所有模块导入以及同时导入模块的测试导入:

    导入系统 对于输入列表(sys.modules.keys()): 如果key.startswith(“您的包名”)或key.startswith(“测试”): 删除系统模块[键]

    您可以通过在
    conftest.py
    文件上使用
    @pytest.fixture
    装饰器配置一个fixture来使用它作为一个fixture。

    我没有使用它的经验,但是您可以使用/调整这个插件来启动几个子进程,并以这种方式执行操作:如果可以在一个文件(`pytext text text/text_xyz.py)上运行pytest,然后,您可以编写一个程序来查找test_xyz文件,并使用子流程模块在单独的流程中运行每个文件。这就是Simeon引用的插件应该做的。谢谢。查看
    pytest xdist
    ,不幸的是,它没有为每个模块启动新的子进程。实际上,现在我有两个目录中的测试模块:一个使用一种初始化方式,另一个使用另一种初始化方式。然后,我对每个目录运行一次pytest。然而不幸的是,我需要一个额外的脚本来确保各个运行的返回值和测试报告得到组合。@Claude具体来说,xdist
    --boxed
    选项是否不符合您的要求?@pfctdayelise:谢谢!它确实可以工作,我应该测试这个选项(尽管文档中只提到它是防止SEGFULTS的保护,但我应该对它是如何工作的考虑得更久一些)。缺点是它会在每次测试中重新启动python,而不是在每个模块中重新启动一次。也许我应该更深入地研究pytest xdist,看看是否可以将它修改到我需要的位置。我使用的是python3,所以
    imp
    应该是
    importlib
    。以这种方式重新加载模块(不幸的是)不会清除该模块的全局字典,因此没有帮助。恐怕它也不会重新加载所有依赖的模块。您在一个新流程中运行每个测试看起来很有趣,但我不知道这如何与
    py.test
    相结合。我还需要每个模块在一个单独的测试中运行,而不是每个函数。@Claude,假设您正在运行一个测试,并且您意识到,您必须对测试代码进行一些更改,否则测试将无法工作
    importlib
    将重新加载测试函数调用的其他模块,但是当您在调试器中单步执行测试函数时,是否有方法重新加载测试函数本身?我不这么认为。此外,我不知道这将如何工作,它如何知道代码指针将在哪里。