当使用nosetests来运行我无法在鼻子外面重现的测试套件时,我遇到了一个神秘的导入错误。此外,当我跳过测试的子集时,导入错误消失。
执行摘要:我在Nose中遇到导入错误a)仅在排除带有某个属性的测试时出现,并且b)无法在交互式python会话中重现,即使我确保sys.path对于两者都是相同的。
详细信息:
包结构如下所示:
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 /目录运行unit test,如上面的示例代码所示。
为了让这个从项目的根目录中运行nose,我将以下代码添加到每个测试包中的 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交互式会话中,我可以毫无困难地导入测试模块:
$ 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 / importer.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.path从我的交互式会话复制到pdb会话并重复上述操作,我也会看到相同的结果。任何人都可以给我任何有关可能发生的事情的见解吗?我意识到我正在同时做几件非标准的事情,这可能会导致奇怪的互动。我对如何简化我的架构的建议感兴趣,因为我会得到这个bug的解释。
答案 0 :(得分:9)
以下是如何追踪错误的背景。
nosetests --debug=nose,nose.importer --debug-log=nose_debug <your usual args>
然后,检查nose_debug
文件。搜索您的错误消息“No module named foo_test
”。然后查看前面几行,看看nose正在查看哪些文件/目录。
在我的情况下,鼻子试图运行一些我导入我的代码库的代码 - 一个第三方模块,其中包含自己的测试,但我不打算包含在我的测试套件中。为解决此问题,我使用nose-exclude插件排除了此目录。
答案 1 :(得分:7)
默认情况下只是调整路径。它将在导入模块之前更改sys.path,可能允许双重代码执行并在包外部导入(如您的情况)。
要避免这种情况,请在运行鼻子前设置PYTHONPATH
并使用nose --no-path-adjustment
。请参阅:http://nose.readthedocs.org/en/latest/usage.html#cmdoption--no-path-adjustment
如果您无法添加命令行参数,则可以在NOSE_NOPATH=y
中使用env var(.noserc
)或此参数:
[nosetests]
no-path-adjustment=1
答案 2 :(得分:1)
我遇到了这个问题,并追溯到1)忘记激活我正在使用的virtualenv,以及2)我的shell zsh,显然已经缓存了错误实例的路径我机器上的nosetests
可执行文件。
一旦我激活了virtualenv,然后给出了shell命令hash -r
,这个错误就停止了。对不起,我没有确定其中只有一个是否足够。
我发现raffienficiaud的回复是鼻子问题“nosetest does not honour virtual environments”,有帮助:
对于记录,缓存命令是
bash
问题。在那里面 案例,which nosetests
点(确定性地)指向右侧 可执行文件,而bash
缓存系统安装了一个。使用hash -r
清除缓存(请参阅http://unix.stackexchange.com/questions/5609/how-do-i-clear-bashs-cache-of-paths-to-executables)
Unix.SE的答案是Tobu和Zigg提出的问题“How do I clear Bash's cache of paths to executables?”。
bash
会缓存命令的完整路径。你可以验证一下 使用type
命令对您尝试执行的命令进行哈希处理:
$ type svnsync svnsync is hashed (/usr/local/bin/svnsync)
清除整个缓存:
$ hash -r
或只是一个条目:
$ hash -d svnsync
有关其他信息,请参阅
help hash
和man bash
。
我使用zsh
而非bash
,hash -d nosetests
给了我一条错误消息。然而,在我hash -r
之后问题就消失了。