在Django中,为什么我需要将站点名称添加到tests.py中的导入?

时间:2011-09-20 09:47:44

标签: python django django-models django-testing

我在mysite/vncbrowser/tests.py中放了一些单元测试,我可以用以下方法运行:

cd mysite
python manage.py test vncbrowser

tests.py中,我使用以下内容导入模型类:

from vncbrowser.models import Project, Stack, Integer3D, Double3D

...并测试将Integer3D插入到自定义字段类型中,测试类似于:

class InsertionTest(TestCase):

    def test_stack_insertion(self):
        s = Stack()
        s.title = "Example Stack"
        s.image_base = "http://foo/bar/"
        s.dimension = Integer3D(x=2048, y=1536, z=460)
        s.resolution = Double3D(x=5.0001, y = 5.0002, z=9.0003)
        s.save()
        self.assertEqual(s.id, 1)

但是,当我使用python manage.py test vncbrowser运行测试时,我发现isinstance(value, Integer3D)来源中的models.py检查失败了。似乎在models.py文件中,对Integer3D(在该文件中先前定义)的裸引用具有全名vncbrowser.models.Integer3D,而从测试传入的对象具有全名mysite.vncbrowser.models.Integer3D

来自models.py的相关代码包含一些调试语句:

class Integer3D(object):
    [... elided ...]

class Integer3DField(models.Field):
    def to_python(self, value):
        a = Integer3D()
        print >> sys.stderr, "value is %s, of type %s" % (value, type(value))
        print >> sys.stderr, "but a new Integer3D instance is", type(a)
        if isinstance(value, Integer3D):
            print >> sys.stderr, "isinstance check worked"
            return value
        print >> sys.stderr, "isinstance check failed"

...产生此输出(为清晰起见添加了一些换行符和空格):

value is <vncbrowser.models.Integer3D object at 0x22bbf90>, of type
      <class 'vncbrowser.models.Integer3D'>

but a new Integer3D instance is
      <class 'mysite.vncbrowser.models.Integer3D'>

isinstance check failed

我可以通过将tests.py中的导入更改为:

来进行此测试
 from mysite.vncbrowser.models import Project, Stack, Integer3D, Double3D

...但我不明白为什么mysite文件中需要tests.py限定。在我的django源中似乎没有其他要求。我确定我错过了一些明显的东西,但也许有人可以解释一下?

(我甚至不确定为什么from mysite....导入工作正常,因为如果我在该声明之前打印sys.path,它包含路径/home/mark/foo/mysite/,但不是/home/mark/foo/。)

当我运行/home/mark/foo/mysite/时,我当前的工作目录为python manage.py test vncbrowser


根据要求,我的项目布局如下:

 ── mysite
    ├── custom_postgresql_psycopg2
    │   ├── base.py
    │   └── __init__.py
    ├── __init__.py
    ├── manage.py
    ├── settings.py
    ├── urls.py
    └── vncbrowser
        ├── __init__.py
        ├── models.py
        ├── tables.sql
        ├── tests.py
        └── views.py

上面列出的所有__init__.py个文件都是空的。我正在使用Python 2.6.5和Django 1.3。我在virtualenv中使用Python,如果我在tests.py的开头打印"\n".join(sys.path),我得到:

/home/mark/foo/mysite
/home/mark/foo/env/lib/python2.6/site-packages/distribute-0.6.10-py2.6.egg
/home/mark/foo/env/lib/python2.6
/home/mark/foo/env/lib/python2.6/plat-linux2
/home/mark/foo/env/lib/python2.6/lib-tk
/home/mark/foo/env/lib/python2.6/lib-old
/home/mark/foo/env/lib/python2.6/lib-dynload
/usr/lib/python2.6
/usr/lib64/python2.6
/usr/lib/python2.6/plat-linux2
/usr/lib/python2.6/lib-tk
/usr/lib64/python2.6/lib-tk
/home/mark/foo/env/lib/python2.6/site-packages

更新:按照lbp's answer的建议,我尝试在tests.py的顶部添加以下内容:

import vncbrowser as vnc_one
import mysite.vncbrowser as vnc_two

print "vnc_one:", vnc_one.__file__
print "vnc_two:", vnc_two.__file__

...产生了输出:

vnc_one: /home/mark/foo/mysite/vncbrowser/__init__.pyc
vnc_two: /home/mark/foo/mysite/../mysite/vncbrowser/__init__.pyc

2 个答案:

答案 0 :(得分:2)

您不必了解所有PYTHONPATH,以便了解您真正想知道的一件事:其他 vncbrowser的来源。您可以执行以下操作,而不是打印出python路径:

import vncbrowser as vnc_one
import mysite.vncbrowser as vnc_two

print vnc_one.__file__
print vnc_two.__file__

在那里,您将在文件系统上看到两个不同的路径。然后你就可以开始搞清楚原因了。

这只是一个疯狂的猜测,但我认为vnc_one安装在python路径的某处,而vnc_two位于源代码中。(编辑:错误猜测)

此外,随机评论:

此外,您可以使用

使tests.py中的import语句更简单
from models import ...

而不是

from XXX.models import ...

答案 1 :(得分:0)

lbp's answer并且评论帮助我理解并解决了问题,所以这就是我接受的答案,但我认为可能值得在另一个答案中解释我的困惑来源,因为这个可以帮助别人。

当您运行manage.py时,它会在运行测试之前将站点目录(在我的示例中为/home/mark/foo/mysite)添加到sys.path。那么,如果您使用导入行suggested in the Django documentation(在我的情况下为tests.py)从from vncbrowser.models import Integer3D导入模型,然后创建Integer3D的实例,将为vncbrowser.models.Integer3D类型,因为假定包层次结构的根在sys.path中的每个条目处开始。

这个包的名称实际上是不正确的,因为应用程序目录嵌套在站点目录中(如django-admin.py和Django教程所示),这两个都是Python包。这意味着该类的真实姓名以及models.py中有效的真实姓名是完全限定的mysite.vncbrowser.models.Integer3D

通常情况下,这不会产生问题,因为如果你正在编写Python,使用isinstance should be rare,并且鸭子打字意味着类的全名有任何区别特定对象应该是无关紧要的。但是,在编写自定义字段类型时,您必须区分是使用字符串还是对象调用to_python方法,并the documentation建议使用isinstance

为了避免将来出现这种混淆,正如评论中提到的lbp,我现在将我的应用程序放在一个单独的(非包)目录中,以避免意外使应用程序依赖于站点的名称 - 这很容易发生在站点包中嵌套应用程序包的奇怪的默认建议层次结构。