当使用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 hash
和man bash
。
我使用zsh
而不是bash
, hash -d nosetests
给了我一个错误消息。然而,在我做了hash -r
之后,问题就消失了。