运行鼻测试时导入错误,我无法在鼻子外重现



当使用nosetest运行一个测试套件时,我遇到了一个神秘的导入错误,我无法在鼻子之外复制。此外,当我跳过测试的一个子集时,导入错误就会消失。

执行摘要:我在Nose中得到一个导入错误,a)仅在排除具有特定属性的测试时出现,b)不能在交互式python会话中复制,即使我确保sys. s.>路径相同

细节:

包的结构是这样的:

project/
    module1/__init__.py
    module1/foo.py
    module1/test/__init__.py
    module1/test/foo_test.py
    module1/test/test_data/foo_test_data.txt
    module2/__init__.py
    module2/bar.py
    module2/test/__init__.py
    module2/test/bar_test.py
    module2/test/test_data/bar_test_data.txt

foo_test.py中的一些测试很慢,所以我创建了一个@slow装饰器,允许我使用nosetests选项跳过它们:

def slow(func):
    """Decorator sets slow attribute on a test method, so 
       nosetests can skip it in quick test mode."""
    func.slow = True
    return func
class TestFoo(unittest.TestCase):
    @slow
    def test_slow_test(self):
        load_test_data_from("test_data/")
        slow_test_operations_here

    def test_fast_test(self):
        load_test_data_from("test_data/")

当我只想运行快速单元测试时,我使用

nosetests -vv -a'!slow'

从项目的根目录。当我想要运行它们时,我删除最后一个参数。

我怀疑是细节造成了这种混乱。单元测试需要从文件中加载测试数据(我知道这不是最佳实践)。这些文件被放置在每个测试包中的一个名为"test_data"的目录中,并且单元测试代码通过一个相对路径引用它们,假设单元测试是从test/目录运行的,如上面的示例代码所示。

为了使它能够从项目的根目录运行,我在每个测试包中的init.py中添加了以下代码:
import os
import sys
orig_wd = os.getcwd()
def setUp():
    """
    test package setup:  change working directory to the root of the test package, so that 
    relative path to test data will work.
    """
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
def tearDown():
    global orig_wd
    os.chdir(orig_wd)

据我所知,nose在运行该包中的测试之前和之后执行setUp和tearDown包方法,以确保单元测试可以找到适当的test_data目录,并且在测试完成时将工作目录重置为原始值。

设置到此为止。问题是,当我运行完整的测试套件时,我只得到的导入错误。当我排除慢速测试时,相同的模块导入很好。(澄清一下,抛出导入错误的测试并不慢,因此它们在两种情况下都可以执行。)

$ nosetests
...
ERROR: Failure: ImportError (No module named foo_test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Python/2.7/site-packages/nose/loader.py", line 413, in loadTestsFromName
    addr.filename, addr.module)
  File "/Library/Python/2.7/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/Library/Python/2.7/site-packages/nose/importer.py", line 80, in importFromDir
    fh, filename, desc = find_module(part, path)
ImportError: No module named foo_test

如果我不运行慢速测试而运行测试套件,则没有错误:

$ nosetests -a'!slow'
...
test_fast_test (module1.test.foo_test.TestFoo) ... ok

在python交互式会话中,我可以毫不费力地导入test模块:

$ python
Python 2.7.1 (r271:86832, Aug  5 2011, 03:30:24) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import module1.test
>>> module1.test.__path__
['/Users/USER/project/module1/test']
>>> dir(module1.test)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'orig_wd', 'os', 'setUp', 'sys', 'tearDown']

当我在nose/import .py中设置断点时,情况看起来不同了:

> /Library/Python/2.7/site-packages/nose/importer.py(83)importFromDir()
-> raise
(Pdb) l
 78                               part, part_fqname, path)
 79                     try:
 80                         fh, filename, desc = find_module(part, path)
 81                     except ImportError, e:
 82                         import pdb; pdb.set_trace()
 83  ->                     raise
 84                     old = sys.modules.get(part_fqname)
 85                     if old is not None:
 86                         # test modules frequently have name overlap; make sure
 87                         # we get a fresh copy of anything we are trying to load
 88                         # from a new path
(Pdb) part
'foo_test'
(Pdb) path
['/Users/USER/project/module1/test']
(Pdb) import module1.test.foo_test
*** ImportError: No module named foo_test
#If I import module1.test, it works, but the __init__.py file is not being executed
(Pdb) import partition.test
(Pdb) del dir
(Pdb) dir(partition.test)
['__doc__', '__file__', '__name__', '__package__', '__path__'] #setUp and tearDown missing?
(Pdb) module1.test.__path__
['/Users/USER/project/module1/test']  #Module path is the same as before.
(Pdb) os.listdir(partition.test.__path__[0])  #All files are right where they should be...
['.svn', '__init__.py', '__init__.pyc', 'foo_test.py', 'foo_test.pyc','test_data']

即使复制sys,也会看到相同的结果。路径从我的交互式会话进入PDB会话,并重复上述操作。有谁能给我点建议吗?我意识到我同时在做一些非标准的事情,这可能会导致奇怪的互动。我对如何简化我的架构的建议和对这个bug的解释一样感兴趣。

如何跟踪错误的上下文

nosetests --debug=nose,nose.importer --debug-log=nose_debug <your usual args>

检查nose_debug文件。搜索您的错误信息"No module named foo_test"。然后查看前面的几行,看看nose正在查看哪些文件/目录。

在我的例子中,nose试图运行一些我导入到我的代码库中的代码——一个包含自己测试的第三方模块,但我不打算将其包含在我的测试套件中。为了解决这个问题,我使用了nose-exclude插件来排除这个目录。

这只是在默认情况下调整路径。它会改变sys。路径,可能允许双代码执行和包外的导入(就像你的例子)。

为了避免这种情况,在流鼻涕之前设置PYTHONPATH并使用nose --no-path-adjustment。见:http://nose.readthedocs.org/en/latest/usage.html cmdoption——no-path-adjustment

如果您不能添加命令行参数,您可以使用env var (NOSE_NOPATH=y)或.noserc:

[nosetests]
no-path-adjustment=1

我遇到了这个问题,并将其跟踪到1)忘记激活我正在使用的virtualenv,以及2)我的shell zsh,显然已将路径缓存到我机器上的nosetests可执行文件的错误实例。

一旦我激活了我的virtualenv,然后给出了shell命令hash -r,这个错误就不再发生了。对不起,我没有确定其中一个是否足够。

我发现raffienfiiciaud对nose问题"nosetest不支持虚拟环境"的回复很有帮助:

请注意,缓存命令是bash问题。在那在这种情况下,which nosetests(确定地)向右可执行文件,而bash则缓存系统安装的文件。使用hash -r清除缓存(参见http://unix.stackexchange.com/questions/5609/how-do-i-clear-bashs-cache-of-paths-to-executables)

Unix。这个问题的答案是:"我如何清除Bash的可执行文件路径缓存?",由Tobu和Zigg给出。

bash不缓存命令的完整路径。可以验证您试图执行的命令是用type命令散列的:

$ type svnsync svnsync is hashed (/usr/local/bin/svnsync)

清空整个缓存:

$ hash -r

或者只有一个条目:

$ hash -d svnsync

有关其他信息,请参阅help hashman bash

我使用zsh而不是bash, hash -d nosetests给了我一个错误消息。然而,在我做了hash -r之后,问题就消失了。

最新更新