【问题标题】:nose-doctest module fixture before module is imported导入模块之前的nose-doctest模块夹具
【发布时间】:2014-10-18 14:04:47
【问题描述】:

我使用鼻子进行测试收集,我也想使用它的 doctest 插件。我有一个需要固定装置才能导入的模块。因此,我不能使用鼻子的模块夹具,因为它们是从被测模块加载的。有没有办法在模块之外为 nose-doctest 指定模块夹具?

对于某些用例,一个选项是检测是否在 doctest 下运行并在模块的开头应用夹具。我也很想听听这个用例的答案。

但是,在某些情况下这不起作用:当导入由于SyntaxError 而失败时,不会运行任何模块代码。就我而言,我主要开发与 python 2 和 python 3 兼容的代码(没有2to3)。但是,在 python 2 下运行时,有一些特定于 python 3 的模块根本不应该被鼻子检查。我最好的选择是什么?

编辑:MWE(针对SyntaxError 情况)

我有一个包含许多小模块的包,其中一些使用 python 3 语法。 这是包结构:

~/pckg/
  __init__.py
  py3only.py
  ... (other modules)
  tests/
    test_py3only.py

有些测试写成unittest.TestCase,但我也想要测试文档字符串中的代码示例。 ~/pckg/__init__.py 为空。

~/pckg/py3only.py:

def fancy_py3_func(a:"A function argument annotation (python 3 only syntax)"):
    """ A function using fancy syntax doubling it's input.

    >>> fancy_py3_func(4)
    8
    """
    return a*2

~/pckg/tests/test_py3only.py:

import sys, unittest

def setup_module():
    if sys.version_info[0] < 3:
        raise unittest.SkipTest("py3only unavailable on python "+sys.version)

class TestFancyFunc(unittest.TestCase):
    def test_bruteforce(self):
        from pckg.py3only import fancy_py3_func
        for k in range(10):
            self.assertEqual(fancy_py3_func(k),2*k)

在 python 3 上测试,一切都经过测试并通过(从封闭文件夹运行,例如~):

~ nosetests3 -v --with-doctest pckg
Doctest: pckg.py3only.fancy_py3_func ... ok
test_bruteforce (test_py3only.TestFancyFunc) ... ok

在 python 2 上,~/pckg/tests/test_py2only.py 的模块夹具正确地检测到了这种情况并跳过了测试。然而,我们从~/pckg/py3only.py 得到一个SyntaxError

~ nosetests -v --with-doctest pckg 
Failure: SyntaxError (invalid syntax (py3only.py, line 1)) ... ERROR
SKIP: py3only unavailable on python 2.7.6 (default, Mar 22 2014, 22:59:56)

一个类似于~/pckg/tests/test_py3only.py:setup_module() 的函数可以解决这个问题,如果我能让nose 在它的doctest 插件甚至尝试导入该模块之前运行该代码。

看来我最好的选择是编写一个适当的顶级测试脚本来处理测试的集合......

【问题讨论】:

  • 如果没有 X 或 Y 气味不好,就无法导入模块,这意味着,例如,pydocpylint 都不能用于您的代码。在导入时更改模块,因为它正在测试中,这会破坏整个测试点,不是吗?
  • 我基本上有两种情况:缺少 optional 外部依赖项,以及使用新 python 3 语法的模块,在 python 2 下测试。在这两种情况下,测试基本上是徒劳的,模块不可用。我想抑制测试失败,并可能用消息替换它们,因为缺少依赖项,库的某些部分不可用。
  • nosetests -v --with-doctest pckg 你让 Python 2 看到 Python 3 代码,因为你从顶层目录运行 --with-doctest。从tests 文件夹运行单元测试和文档测试。为了使 doctest 成功将其提取为纯文本文件,就像我所做的那样,您可以有条件地导入。
  • > 类似于 ~/pckg/tests/test_py3only.py:setup_module() 的函数可以解决这个问题。 setup_ 函数用于单元测试。
  • 我不想将文档测试提取到文本文件中:它们构成文档字符串的一部分。我想测试它们以确保它们是正确的。 setup_ 函数是所谓的fixtures 的一部分,我希望有一种方法可以为文档字符串嵌入的文档测试指定它们。我想那是徒劳的。

标签: python testing nose doctest


【解决方案1】:

可以使用nose-excludenose 插件排除特定的测试文件、目录、类或方法。它有--exclude-* 选项。

要处理缺失的模块,您必须使用mock 修补sys.modules

F.e,mycalc 模块中有一个 Calc 类,但我无法访问它,因为它丢失了。还有两个模块,mysuper_calcmysuper_calc3,后者是 Python 3 特有的。这两个模块导入 mycalcmysuper_calc3 不应该在 Python 2 下进行测试。如何从模块中对它们进行 doctest,即在纯文本文件中?我认为这是OP的情况。

calc/mysuper_calc3.py

from sys import version_info
if version_info[0] != 3:
    raise Exception('Python 3 required')
from mycalc import Calc
class SuperCalc(Calc):
    '''This class implements an enhanced calculator
    '''
    def __init__(self):
        Calc.__init__(self)

    def add(self, n, m):
        return Calc.add(self, n, m)

calc/mysuper_calc.py

from mycalc import Calc

class SuperCalc(Calc):
    '''This class implements an enhanced calculator
    '''
    def __init__(self):
        Calc.__init__(self)

    def add(self, n, m):
        return Calc.add(self, n, m)

现在模拟出mycalc

>>> from mock import Mock, patch
>>> mock = Mock(name='mycalc')

模块mycalc 有类Calc,它有方法add。我用2+3 测试SuperCalc 实例add 方法。

>>> mock.Calc.add.return_value = 5  

现在补丁sys.modulesmysuper_calc3 可以在with 块内有条件地导入。

>>> with patch.dict('sys.modules',{'mycalc': mock}):
...     from mysuper_calc import SuperCalc
...     if version_info[0] == 3:
...         from mysuper_calc3 import SuperCalc

calc/doctest/mysuper_calc_doctest.txt

>>> from sys import version_info
>>> from mock import Mock, patch
>>> mock = Mock(name='mycalc')
>>> mock.Calc.add.return_value = 5

>>> with patch.dict('sys.modules',{'mycalc': mock}):
...     from mysuper_calc import SuperCalc
...     if version_info[0] == 3:
...         from mysuper_calc3 import SuperCalc
>>> c = SuperCalc()
>>> c.add(2,3)
5

文件mysuper_calc_doctest.txt 必须单独在其自己的目录中,否则nosetests 在非测试模块中搜索doctest

PYTHONPATH=.. nosetests --with-doctest --doctest-extension=txt --verbosity=3

Doctest: mysuper_calc_doctest.txt ...好的


在 0.038 秒内运行 1 次测试

好的

nosetests 周围的包装器,用于检测 Python 3,它将没有语法错误的 .py 文件传递​​给 nosetests

mynostests.py

import sys
from subprocess import Popen, PIPE
from glob import glob

f_list = []

py_files = glob('*py')
try:
    py_files.remove(sys.argv[0])
except ValueError:
    pass

for py_file in py_files:
    try:
        exec open(py_file)
    except SyntaxError:
        continue
    else:
        f_list.append(py_file)

proc = Popen(['nosetests'] + sys.argv[1:] + f_list,stdout=PIPE, stderr=PIPE)
print('%s\n%s' % proc.communicate())
sys.exit(proc.returncode)

【讨论】:

  • 当然,使用模拟模块修补sys.modules 是我的夹具可以执行的一项可能操作,以使它们可导入。问题是,我在哪里放置这个夹具代码,以便nosenose-doctest 尝试导入模块之前运行它?
  • 另外,虽然nose-exclude 允许我从测试中排除使用python 3 语法的文件,但它只允许从node.cfg 文件或命令行静态地这样做。我宁愿让夹具决定跳过,因为我只想在测试 python 2 时跳过,而不是在测试 python 3 时跳过,并且没有任何手动交互。
  • 'fixture' 在您的上下文中不清楚。向我们展示一些示例代码,描述您的两种情况,nose 无法导入和 'fixutre' 决定跳过。
  • 感谢非常详细的编辑。就我而言,文档测试嵌入在模块内的文档字符串中。我依靠nose-doctest 来收集它们。因此,当它们运行时,进行模拟修补为时已晚,因为模块已经导入。但是,在您的情况下,doctest 似乎不会检查您的模块中的文档字符串,否则在导入 mysuper_calc3 时会出现异常。我将发布一个最小的工作示例(当然应该从一开始就有)。
  • 您可以直接在缺少导入的模块中进行修补。
【解决方案2】:

鼻子有以下选项:

  --doctest-fixtures=SUFFIX
                    Find fixtures for a doctest file in module with this
                    name appended to the base name of the doctest file

也许您可以将固定装置隔离到一个单独的文件中?

【讨论】:

  • 听起来很有趣,重要的问题是夹具是否会在模块导入之前运行。我试试看。
  • 不幸的是,--doctest-fixtures 仅适用于 非模块 doctests。因此,当 doctest 在模块中搜索测试时,这无法帮助我在导入期间隐藏SyntaxErrors。无论如何,谢谢您的回答!
猜你喜欢
  • 2011-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-07
  • 2023-02-22
相关资源
最近更新 更多