Django测试运行器在Ubuntu虚拟环境中失败

14 投票
2 回答
3061 浏览
提问于 2025-04-20 23:33

我在Ubuntu 14.04的Python虚拟环境中遇到了Django测试运行器的问题,搞了很久也没解决。相同的软件在MacOS上运行得很好,我记得在早期版本的Ubuntu上也没问题。

错误信息是:

ImportError: '<test>' module incorrectly imported from '<base-env>/local/lib/python2.7/site-packages/<package-dir>'. Expected '<base-env>/lib/python2.7/site-packages/<package-dir>'. Is this module globally installed?

错误的详细信息如下:

  Traceback (most recent call last):
    File "/home/annalist/anenv/bin/django-admin", line 11, in <module>
      sys.exit(execute_from_command_line())
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
      utility.execute()
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
      self.fetch_command(subcommand).run_from_argv(self.argv)
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 50, in run_from_argv
      super(Command, self).run_from_argv(argv)
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
      self.execute(*args, **options.__dict__)
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 71, in execute
      super(Command, self).execute(*args, **options)
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
      output = self.handle(*args, **options)
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/core/management/commands/test.py", line 88, in handle
      failures = test_runner.run_tests(test_labels)
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/test/runner.py", line 147, in run_tests
      suite = self.build_suite(test_labels, extra_tests)
    File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/test/runner.py", line 96, in build_suite
      tests = self.test_loader.discover(start_dir=label, **kwargs)
    File "/usr/lib/python2.7/unittest/loader.py", line 206, in discover
      tests = list(self._find_tests(start_dir, pattern))
    File "/usr/lib/python2.7/unittest/loader.py", line 287, in _find_tests
      for test in self._find_tests(full_path, pattern):
    File "/usr/lib/python2.7/unittest/loader.py", line 287, in _find_tests
      for test in self._find_tests(full_path, pattern):
    File "/usr/lib/python2.7/unittest/loader.py", line 267, in _find_tests
      raise ImportError(msg % (mod_name, module_dir, expected_dir))
  ImportError: 'test_entity' module incorrectly imported from '/home/annalist/anenv/local/lib/python2.7/site-packages/annalist_root/annalist/tests'. Expected '/home/annalist/anenv/lib/python2.7/site-packages/annalist_root/annalist/tests'. Is this module globally installed?

在开发环境中,测试用例运行得很好,而且在MacOS开发主机上从源代码包安装到新的虚拟环境中时也能正常运行。但是当我把相同的包安装到Ubuntu 14.04的新的虚拟环境中时,测试运行器就出现了上面的错误信息。

问题出现在我创建的一个管理工具中,这个工具调用了一些django-admin的功能(还有其他一些功能)。

网上搜索后发现,有人报告了虚拟环境和posix兼容性的问题,这些问题在Ubuntu的更新版本中(2013/14年)得到了相对较新的修复,方法是在虚拟环境中创建一个local目录,这个目录里面有指向其他目录的符号链接,这些目录在虚拟环境的顶层目录中也可以访问。错误信息中显示的路径对应的就是这些别名目录的路径。

我把这个发出来是想作为一个问题,这样我可以分享我的调查结果和解决方案,希望能对其他人有帮助。因此,我并不是想详细描述我具体的软件设置。

2 个回答

33

我遇到过完全一样的问题,搞了半天也没弄明白到底怎么回事。最后发现其实是个小问题:

我的文件结构大概是这样的:

my_app/
    __init__.py
    tests.py
    tests/
        __init__.py
       test_foo.py

问题出在同一个文件夹里同时有一个“tests.py”文件和一个“tests”文件夹。

我只需要删除“tests.py”这个文件,就解决了问题。

希望这对你有帮助。

5

简短回答

我在代码中的解决办法是使用 os.path.realpath 来获取安装包路径的标准化版本,然后把这个值传递给调用 django-admin 工具的命令行。在我的情况下,代码大致是这样的:

approot = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))

还有:

with ChangeCurrentDir(approot):
    subprocess_command = (
        "django-admin test --pythonpath=%s --settings=%s --top-level-directory=%s"%
            (approot, settings_module_name, approot)
        )
    status = subprocess.call(subprocess_command.split())

(其中 ChangeCurrentDir 是一个上下文处理器,它会在指定的当前工作目录下运行包含的代码)。

更多细节

经过进一步的实验,我发现可以通过在 Python 和/或 Django 库代码中巧妙地把 os.path.abspath 替换为 os.path.realpath 来“修复”这个问题。

我发现的核心问题在于:

/usr/lib/python2.7/unittest/loader.py

具体来说:

File "/usr/lib/python2.7/unittest/loader.py", line 267, in _find_tests
    raise ImportError(msg % (mod_name, module_dir, expected_dir))
ImportError: 'test_entity' module incorrectly imported from ...

loader.py 中导致这个问题的代码是:

        if realpath.lower() != fullpath_noext.lower():
            module_dir = os.path.dirname(realpath)
            mod_name = os.path.splitext(os.path.basename(full_path))[0]
            expected_dir = os.path.dirname(full_path)
            msg = ("%r module incorrectly imported from %r. Expected %r. "
                   "Is this module globally installed?")
            raise ImportError(msg % (mod_name, module_dir, expected_dir))

如果我把 if 语句替换成这个:

        if os.path.realpath(realpath).lower() != fullpath_noext.lower():

那么一切就正常了。这确认了这是一个符号链接别名的问题,因为 os.path.realpath() 会将任何符号链接解析为实际路径。但这并不是一个适合可安装软件包的解决方案,因为它涉及到修改底层的 Python 安装。所以,在深入探讨了根本问题后,我需要找一个更容易处理的方法。

接下来,我查看了安装在虚拟环境中的 Django 测试运行库。

<base-env>/local/lib/python2.7/site-packages/django/test/runner.py

特别是,关注堆栈跟踪中的这一部分:

  File "/home/annalist/anenv/local/lib/python2.7/site-packages/django/test/runner.py", line 96, in build_suite
    tests = self.test_loader.discover(start_dir=label, **kwargs)

在这段代码中,我发现问题与 label 参数有关,它的默认值是 '.'(也就是当前目录)。这里没有明显的简单解决办法,但这表明问题可能与运行 django-admin 时使用的当前目录和/或路径有关。这让我得出了上面的解决方案(可能有点过于复杂——我不确定 --pythonpath= 这个选项是否必要,但它对我有效)。

撰写回答