【问题标题】:Execute some code before Pytest runs在 Pytest 运行之前执行一些代码
【发布时间】:2018-06-15 06:43:53
【问题描述】:

我正在使用 Pytest 测试我的代码,但我遇到了一个小问题,但令人恼火。

我的程序做的第一件事就是检查是否有任何设置文件可用。如果没有,则会引发错误并调用exit()。这在正常运行时运行良好,但会与 Pytest 混淆。

我想出的解决方案是通过复制模板设置文件,在测试期间简单地创建一个临时设置文件。我已经编写并成功测试了实现这一目标的代码。

我遇到的问题是我找不到真正先于其他一切触发的 Pytest 钩子。这会导致程序在 Pytest 尝试创建临时设置文件之前抛出错误。这会导致 Pytest 在执行任何测试之前失败。

有人知道在 Pytest 执行或加载任何内容之前触发函数的方法吗?最好在 Pytest 内部。

一些代码上下文:

抛出错误并退出

这个 sn-p 在 settings 模块的导入上运行。

if len(cycles) == 0:
    log.error("No setting files found. Please create a file " +
              "in the `settings` folder using the Template.py.")
    exit()

创建临时设置文件

这段代码应该是 Pytest 运行的第一件事。

def pytest_sessionstart(session):
    """ Create a test settings file """
    folderPath = os.path.dirname(os.path.abspath(__file__))
    folderPath = os.path.split(folderPath)[0] + "/settings"

    srcfile = folderPath + '/Template.py'
    dstfile = folderPath + '/test.py'

    shutil.copy(srcfile, dstfile)

删除临时设置文件

这段代码应该是 Pytest 运行的最后一件事。

def pytest_sessionfinish(session, exitstatus):
    """ Delete the test settings file """
    folderPath = os.path.dirname(os.path.abspath(__file__))
    folderPath = os.path.split(folderPath)[0] + "/settings"

    os.remove(folderPath + "/test.py")

Pytest 输出

禁用对exit() 的调用,因此您可以查看执行顺序。

Lakitna$ pytest -v -s
 No setting files found. Please create a file in the `settings` folder using the Template.py. 
TEMPORARY SETTINGS FILE CREATED
========================= test session starts ==========================
platform darwin -- Python 3.6.4, pytest-3.3.1, py-1.5.2, pluggy-0.6.0 -- /usr/local/opt/python3/bin/python3.6
cachedir: .cache
rootdir: /Users/lakitna/Documents/Github/Onaeri-tradfri/Onaeri, inifile:
collected 24 items

【问题讨论】:

  • @progmatico 遗憾的是,这些钩子不像我需要的那样工作。就我而言,它们与pytest_sessionstartpytest_sessionfinish 存在相同的问题,如上所示
  • 可能这不是 pytest 问题,而是 python 本身。如果此代码不在 sobroutines 中,则 Python 从您的所有 import 运行代码。因此,只需删除该初始化代码并显式调用它。
  • conftest.pystackoverflow.com/a/53690092/248616怎么样

标签: python pytest


【解决方案1】:

首先,pytest 尝试使所有导入尽可能相对:这意味着如果您将测试保存在 package/tests 中,那么即使您的 conftest 也会被导入为 package.test.conftest。要进行该导入,将调用package.__init__(对您来说很不幸)。

为了在之前运行一些配置,需要将测试移出包树。我最近遇到了just such a problem

pytest issue thread 所示,pytest_configure 实际上是在pytest_sessionstart 之前调用的,但两者都发生在测试收集之前,所以应该没问题。

# contents of tests/conftest
import os
from pathlib import Path
import shutil
import pytest  # if necessary
# cannot import package here (and likely not in `tests.__init__` either)


folderPath = Path.cwd()
folderPath = folderpath / 'package' / 'settings'
srcfile = folderPath / 'Template.py'
dstfile = folderPath / 'test.py'


def pytest_configure(config):
  """ Create a test settings file """
  # can use `config` to use command line options
  shutil.copy(srcfile, dstfile)


def pytest_unconfigure(config):
  """ Delete the test settings file """
  os.remove(dstfile)

如果您的文件夹结构如下:

root_folder
├── package
│   ├── settings
│   │   └── Template.py
│   ├── __init__.py
│   └── module1.py
└── tests
    ├── __init__.py      # Does not import package
    ├── conftest.py      # Does not import package
    └── test_module1.py  # Can import package

然后可以以正常方式调用pytest:pytest -flags(或pytest tests -flags)在root_folder工作时。

如果您想根据custom command line flags to pytest 以不同方式设置您的设置文件,您也可以在conftest.py 中执行此操作。然后可以在pytest_configure 中以config.getoption(...) 访问它们。


注意:此语法假定 Python 3.6+ 使用 Paths 作为 os.removeshutil.copy 的参数。

【讨论】:

    【解决方案2】:

    使用以下代码将conftest.py 添加到您的项目中,并将您的代码添加到方法pytest_configure

    def pytest_configure(config):
        pass # your code goes here
    
    # other config
    

    【讨论】:

    • 为我工作,这是我正在寻找的确切解决方案。
    • 很高兴它有帮助
    • 这个解决方案显然比作者的回答要好。这更简单,并且使用现有的conftest.py 和更少的代码行。这应该是公认的答案。
    • 非常高兴听到您的赞美!谢谢谢谢!
    【解决方案3】:

    这偏离了您的目标,但我会质疑您的策略。我怀疑您的代码对测试不友好。可用的钩子应该足以设置这样的测试先决条件。也就是说,如果您的测试需要一些文件,以某种方式首先在设置中设置该文件,通过拆卸删除它们,或者按照 pytest 文档使用固定装置。

    如果您实际上不需要该文件来运行测试,除了代码测试它的存在之外,您可以使用模拟来伪造它的存在。

    但是您在模块导入上运行的代码正在测试(实际上不是测试代码)一个先决条件并与用户交互。 而且我认为你不应该这样做。

    删除使用 import 运行的代码的执行(使其成为函数或类)。并在代码的其他初始化位置调用它。

    设置文件(使用 pytest 挂钩)。使用设置文件的测试代码。 拆解文件(使用 pytest 钩子)。

    您可以替代地完全伪造文件,方法是用假数据覆盖读取的数据,使用一些数据结构和函数来读/写, 模拟文件。

    测试测试设置文件是否存在的代码(使用模拟)。

    测试您的其他代码方法。

    不要混用。尝试编写做一件事的方法。一次测试一件事。测试您的业务代码。用户交互是另一回事。如果您还想测试 UI,请不要同时进行(我的意思是在相同的代码中)。

    【讨论】:

      【解决方案4】:

      我已经设法找到了解决方案。

      经过一些额外的研究,我发现 Pytest 预加载了所有模块。这意味着你永远不能在模块导入之前运行代码,除非你能在收集阶段之前找到一个钩子。据我所知,没有这样的钩子。我真的很想在 Pytest 中完成这项工作,但这似乎是不可能的。

      相反,我在我的测试文件夹中创建了一个__main__.py 文件,其内容如下:

      import pytest
      import os
      import shutil
      
      """
      Create a setting file for the test procedure
      """
      folderPath = os.path.dirname(os.path.abspath(__file__))
      folderPath = os.path.split(folderPath)[0] + "/settings"
      
      srcfile = folderPath + '/Template.py'
      dstfile = folderPath + '/test.py'
      
      shutil.copy(srcfile, dstfile)
      
      """
      Actually run pytest
      """
      pytest.main()
      
      """
      Remove the test settings file
      """
      os.remove(dstfile)
      

      此代码创建一个临时设置文件,启动 Pytest,然后再次删除该临时文件。

      我现在可以按如下方式运行测试程序:

      $ python3 test
      

      Pytest 标志仍然正常工作。例如,如果您想要更详细的输出,您可以执行以下操作:

      $ python3 test -v
      

      【讨论】:

      • 对于希望在 pytest 运行之前运行代码的任何其他人,这里的导入内容是 A) 您创建一个 Python 文件来运行您的测试,并且 B) 该文件包含pytest.main() 行。此行之前的任何内容都将在 pytest 运行之前运行。
      【解决方案5】:

      要在测试的实际代码之前和之后运行一些代码,您可以使用 pytest 固定装置:

      import pytest
      
      
      @pytest.fixture
      def context():
          """What to do before/after the test."""
          print("Entering !")
          yield
          print("Exiting !")
      
      
      def test_stuff(context):
          """The actual code of your test"""
          print("Running the test")
      

      这显示:

      Entering !
      Running the test
      Exiting !
      

      要使用fixture,只需传递一个与测试函数的输入同名的参数。 yield 指令就像在测试之前和之后所做的事情之间的分隔符。

      【讨论】:

      • 如果在函数中抛出错误,这将非常有用。但就我而言,它是在模块导入时抛出的。而且由于 Pytest 似乎预加载了模块,这很遗憾在这种特殊情况下不起作用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多