【问题标题】:How to correct test for unicode strings with Django and Python 2如何使用 Django 和 Python 2 更正对 unicode 字符串的测试
【发布时间】:2016-06-12 10:12:25
【问题描述】:

我需要测试我的 Django 模型的表示是否与 Unicode 一起使用,因为用户可能会在其中插入 ü 或 ¼ 之类的字符。为此,我有这个 Django tests.py

# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils import timezone

from .models import *
from .views import *

class CategoryTestCase(TestCase):
    """ Test to check whether category name is printed correctly.
        If there is a parent, it should be also printed seperated by a : """

    def setUp(self):
        self.cat1 = Category.objects.create(name=u'Category 1')
        self.cat2 = Category.objects.create(name=u'Category ü', parent=self.cat1)
        self.cat3 = Category.objects.create(name=u'Category 3', parent=self.cat2)

    def test_category_name(self):
        cat_result1 = u'Category 1'
        cat_result2 = u'Category 1' + settings.PARENT_DELIMITER + u'Category ü'
        cat_result3 = u'Category 1' + settings.PARENT_DELIMITER + u'Category ü' + settings.PARENT_DELIMITER + u'Category 3'
        self.assertEqual(self.cat1.__str__(), cat_result1)
        self.assertEqual(self.cat2.__str__(), cat_result2)
        self.assertEqual(self.cat3.__str__(), cat_result3)

这是为了测试这个小模型:

#...
from django.utils.encoding import python_2_unicode_compatible
#....
@python_2_unicode_compatible
class Category(models.Model):
    """ Representing a category a part might contains to.
    E.g. resistor """

    name = models.CharField(
        max_length=50,
        help_text=_("Name of the category.")
    )
    parent = models.ForeignKey(
        "self",
        null=True,
        blank=True,
        help_text=_("If having a subcateogry, the parent.")
    )
    description = models.TextField(
        _("Description"),
        blank=True,
        null=True,
        help_text=_("A chance to summarize usage of category.")
    )

    def __str__(self):
        if self.parent is None:
            return ('{}'.format(self.name))
        else:
            return ('%s%s%s' % (
                self.parent.__str__(),
                settings.PARENT_DELIMITER,
                self.name)
            )

    def get_parents(self):
        """ Returns a list with parants of that StoragePare incl itself"""
        result = []
        next = self
        while True:
            if next.id in result:
                raise(CircleDetectedException(
                    _('There seems to be a circle inside ancestors of %s.' % self.id)))
            else:
                result.append(next.id)
                if next.parent is not None:
                    next = next.parent
                else:
                    break
        return result

    def clean(self):
        pass

(剥去一点)

当通过 Python 3 运行此代码并进行测试或将 Python2/3 作为应用程序执行时,它正在工作。只有 Python2 的测试失败了,所以我认为我的想法有问题如何测试它。根据错误消息,似乎 Unicode 字符串在某处未正确编码和解码。

======================================================================
FAIL: test_category_name (partsmanagement.tests.CategoryTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/frlan/quellen/partuniverse/partuniverse/partsmanagement/tests.py", line 31, in test_category_name
    self.assertEqual(self.cat2.__str__(), cat_result2)
AssertionError: 'Category 1->Category \xc3\xbc' != u'Category 1->Category \xfc'

所以我的问题是:如何使用 Django 进行正确的 Unicode 表示测试。

【问题讨论】:

  • 如果你使用 python 2,你的模型应该有一个 __unicode__ 方法而不是 __str__,并且该方法应该返回一个 unicode 对象 (docs)。您可以在 django 代码中使用 from __future__ import unicode_literals 以使其在 python 2 和 3 之间交叉兼容。(docs)
  • 装饰器假装在模型端这样做。事实上,它似乎在应用程序运行时工作。但是在使用 Python 2 的测试中并检查 __str__() 并没有像 __unicode__() 一样正常工作。
  • 'Category 1->Category \xc3\xbc' 是一个 utf-8 编码的 python 2 字符串。为什么不到处使用 unicode 对象呢?我在之前的评论中链接到的文档中对此进行了解释。如果你想编写同时适用于 python 2 和 3 的代码,你应该阅读整个页面。
  • 可能是一个转储,但尝试应用文档迫使我在 Python 2 上创建具有相同内容的 __str__() 和 __unicode__(),即使文档说只是创建 __unciode__()。与上述相同的错误。当我想测试真正的 UTF 时,使用 Bytestring 编码的 UTF 也感觉不对。
  • 只要将 utf-8 编码的字符串与真正的 unicode 进行比较,您的测试就不会通过。你可以解码。 '\xc3\xbc'.decode('utf-8') == u'\xfc'

标签: python django testing unicode


【解决方案1】:

tl; dr ) 在 assertEqual 的两边使用相同的类型

无需添加u'', unicode, foo.__str__() 等符号即可获得适用于 Python 3 和 2 的最佳代码。应该编写更少的代码,更多地考虑 Python 2/3 中预期的类型。

A 部分)修复测试

如果您在右侧使用相同的显式类型u'...',则一个简短(丑陋)的解决方案是在左侧使用 text_type。由于更好的可读性,避免使用带下划线的函数。您可以通过多种方式将值转换为 text_type:

将 test 中的行替换为

self.assertEqual(u'%s' % self.cat1, cat_result1)

self.assertEqual(u'{}'.format(self.cat1), cat_result1)

from django.utils.six import text_type
self.assertEqual(text_type(self.cat1), cat_result1)

更好的解决方案是统一模块中的类型并在模块开头使用from __future__ import unicode_literals,因为您主要使用文本而不是二进制数据。你可以删除所有u',但它仍然很有用,直到两个版本都可以正常工作。

B 部分)修复 __str__ 方法

如果父类别名称和当前名称都不是 ASCII,您的代码将失败。修复:

from __future__ import unicode_literals
# ... many lines

def __str__(self):
    if self.parent is None:
        return ('{}'.format(self.name))
    else:
        return ('%s%s%s' % (
            self.parent,
            settings.PARENT_DELIMITER,
            self.name)
        )

我只删除了__str__() 调用并添加了future 指令,因为models.py 是第一个特别有用的模块。否则,您应该在此处将u' 添加到两个格式字符串中。

了解python_2_unicode_compatible 装饰器的作用很有用。 __str__ 方法的结果应该是 text_type(Python 2 中的 unicode),但是如果你直接在 Python 2 中调用它,你会得到 bytes 类型。格式化运算符选择匹配方法,但任何显式方法在 Python 3 或 2 中均无效。不能组合不同类型的非 ascii 值。

【讨论】:

  • 你的意思是from __future__ import unicode_literals而不是absolute_import
  • @AlexL 修正了错字。谢谢
【解决方案2】:

您是否尝试过使用six.text_type

six.text_type

用于表示 (Unicode) 文本数据的类型。这是 Python 2 中的 unicode() 和 Python 3 中的 str

编辑:您不必安装six,因为django.utils.six 中提供了所需的方法 - 感谢@hynekcer 指出

Six 提供了简单的实用程序来弥补 Python 2 和 Python 3 之间的差异。它旨在支持无需修改即可在 Python 2 和 3 上运行的代码库。 6 只包含一个 Python 文件,所以复制到项目中很轻松。~

【讨论】:

  • 1+,但django.utils.six 是 Django 通常有用的六个中的一个简单子集
【解决方案3】:

根据错误消息测试尝试比较strunicode 对象。这一般都不好。

AssertionError: 'Category 1->Category \xc3\xbc' != u'Category 1->Category \xfc'

尝试:

  • 比较 unicode 对象:self.assertEqual(self.cat1, cat_result1)
  • 始终使用 unicode 语言环境,即使您目前只使用 latin1-symbols

【讨论】:

  • 问题是type(self.cat1)<type 'str'> 相当于bytes,而不是unicode。
  • 抱歉,在这种情况下不确定。我搬到了 Python3。那里修复了一些 unicode 问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-22
  • 2017-03-15
  • 2012-03-15
  • 1970-01-01
  • 1970-01-01
  • 2017-06-28
  • 1970-01-01
相关资源
最近更新 更多