简体   繁体   English

相互隔离 pytest 测试

[英]Isolating pytest tests from each other

I'm working on a rapidly growing Python project.我正在从事一个快速发展的 Python 项目。 Lately, our test suite started being somewhat unmanageable.最近,我们的测试套件开始变得有些难以管理。 Some tests fail when the modules they are in get executed in the wrong order, despite being seemingly well-isolated.尽管看似隔离良好,但当它们所在的模块以错误的顺序执行时,一些测试会失败。

I found some other questions about this, but they were concerned with fixtures:我发现了一些关于此的其他问题,但它们与固定装置有关:

Pytest fixtures interfering with each other Pytest 夹具相互干扰

test isolation between pytest-hypothesis runs pytest-hypothesis 运行之间的测试隔离

While we're using fixtures as well, I do not think the problem lies within them, but more likely in the fact that we use libraries where classes have internal state which gets changed by the test run, for example by mockito-python .虽然我们也在使用固定装置,但我认为问题不在于它们,但更有可能是因为我们使用的库中的类具有内部 state,它会被测试运行更改,例如mockito-python

I originally come from Java world where this does not happen unless you explicitly make your tests depend on each other or do some crazy and unusual things.我最初来自 Java 世界,除非你明确地让你的测试相互依赖或做一些疯狂和不寻常的事情,否则不会发生这种情况。 Following the same set of practices in Python led me to these problems, so I realize I might be missing some crucial rule of Python test development.遵循 Python 中的同一组实践导致我遇到了这些问题,因此我意识到我可能遗漏了 Python 测试开发的一些关键规则。

Is it possible to tell pytest to drop/revert/reinitialize the internal state of all classes between various module runs?是否可以告诉pytest删除/还原/重新初始化各种模块运行之间所有类的内部 state?

If not, which rules should we follow during test development to prevent these issues?如果不是,我们在测试开发过程中应该遵循哪些规则来防止这些问题?

Edit: One of the issues we identified was that we had some objects set up at top level in the test file, which were created by mockito-python .编辑:我们发现的问题之一是我们在测试文件的顶层设置了一些对象,这些对象是由mockito-python创建的。 When the tests were executed, the files are first all imported and then the tests are executed.执行测试时,首先将文件全部导入,然后执行测试。 What happened was that one test was calling mockito.unstub() which tears down all mocks in mockito's mock registry.发生的事情是,一个测试调用mockito.unstub() ,它拆除了 mockito 的模拟注册表中的所有模拟。 So when the test was actually executed, another test had already torn down its mocks.所以当测试真正执行时,另一个测试已经拆除了它的模拟。 This behavior is quite counter-intuitive, especially for unexperienced developers.这种行为非常违反直觉,尤其是对于没有经验的开发人员而言。

In python it can happen easily that some state is modified by mistake.在 python 中,很容易发生某些状态被错误修改的情况。 For example, when assigning a list to a variable, it is easy enough to forget to add a [:] when it is desired that a copy of the list is created.例如,当将一个列表分配给一个变量时,很容易忘记添加一个[:]当需要创建列表的副本时。

x = [0,1,2,3,4,5]
y = x    # oops, should have been x[:]
y[2] = 7 # now we modify state somewhere...
x
=> [0, 1, 7, 3, 4, 5]

One possible approach to at least more likely identify such problems could be to execute your unit-tests in random order.至少更有可能识别此类问题的一种可能方法是以随机顺序执行单元测试。 I ran an experiment building upon the idea from https://stackoverflow.com/a/4006044/5747415 :我根据https://stackoverflow.com/a/4006044/5747415的想法进行了一项实验:

import unittest
import random
def randcmp(_, x, y):
    return random.randrange(-1, 2)
unittest.TestLoader.sortTestMethodsUsing = randcmp

As a result, the execution order of tests changed between the test executions.结果,测试的执行顺序在测试执行之间发生了变化。 If by mistake your tests happen to have dependencies, you might be able to figure it out this way, because certain execution orders would lead to failures.如果错误地您的测试碰巧有依赖项,您可能可以通过这种方式解决问题,因为某些执行顺序会导致失败。 Certainly, you would start on a small scale (only executing a small amount of tests), so you have the chance to more easily find the culprit.当然,您会从小规模开始(仅执行少量测试),因此您有机会更轻松地找到罪魁祸首。

Maybe worth trying...也许值得一试...

Is it possible to tell pytest to drop/revert/reinitialize the internal state of all classes between various module runs?是否可以告诉 pytest 删除/还原/重新初始化各种模块运行之间所有类的内部 state?

You can achieve this by using importlib to both invalidate cached modules and reload imported modules (thus refreshing the code & state).您可以通过使用importlib 使缓存模块无效重新加载导入的模块(从而刷新代码和状态)来实现这一点。

This can be useful when code is required to run at the module level, for example in Flask where it's common to initialize app at the module level in main.py :这在需要代码在模块级别运行时非常有用,例如在Flask中,通常在main.py中的模块级别初始化app

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

In this case app will be created whenever you import from main import... .在这种情况下,只要您from main import...导入,就会创建app You may want to test initializing app in different ways, so in your test you could do something like你可能想以不同的方式测试初始化app ,所以在你的测试中你可以做类似的事情

def test_app():
    import main
    importlib.reload(main)  # reset module state
    # do something with main.app

This would reset the state of the main module during your test.这将在测试期间重置main模块的 state。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM