如何测试 Django 模型字段

How to test Django model fields

我想对 Django 模型字段进行单元测试:

  class Animal(models.Model):
    name = models.CharField(unique=True, max_length=30, blank=False, null=False)
    class Meta:
        managed = True
        db_table = 'animal'
        ordering = ['name']

我的测试设置如下所示:

class TestAnimalModel(TestCase):
    def setUp(self):
        self.animal = Animal.objects.create(name = 'TestAnimal')
        self.animal2 = Animal.objects.create(name = 123)
        self.animal3 = Animal.objects.create(name = 'TestAnimalWithTooManyCharacters')

animal2 和 animal3 被创建,尽管它们不应该被创建,因为 animal2.name 是 int 而 animal3.name 有超过 30 个字符。
那么如何测试名称字段是否具有正确的值?
尽管对象的值与模型不匹配,但创建对象是否正常?

they shouldn't because animal2.name is int

A CharField 将对作为值传递的内容调用 str(…)。这有时会产生意想不到的副作用。但是 Django 字段被设计为接受各种值。例如对于 DateField,它也接受格式为 YYYY-MM-DD 的日期。我们可以在 implementation of a CharField [GitHub]:

中看到
class CharField(Field):
    # …

    def to_python(self, value):
        """Return a string."""
        if value not in self.empty_values:
            value = <strong>str(</strong>value<strong>)</strong>
            if self.strip:
                value = value.strip()
        if value in self.empty_values:
            return self.empty_value
        return value

animal3.name has more than 30 characters.

如果我 运行 这个与 MySQL 我得到一个:

>>> Animal.objects.create(name='TestAnimalWithTooManyCharacters')
Traceback (most recent call last):
  File "/django/env/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/django/env/lib/python3.8/site-packages/django/db/backends/mysql/base.py", line 73, in execute
    return self.cursor.execute(query, args)
  File "/django/env/lib/python3.8/site-packages/MySQLdb/cursors.py", line 206, in execute
    res = self._query(query)
  File "/django/env/lib/python3.8/site-packages/MySQLdb/cursors.py", line 319, in _query
    db.query(q)
  File "/django/env/lib/python3.8/site-packages/MySQLdb/connections.py", line 259, in query
    _mysql.connection.query(self, query)
MySQLdb._exceptions.DataError: (1406, "Data too long for column 'name' at row 1")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/django/env/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/django/env/lib/python3.8/site-packages/django/db/models/query.py", line 453, in create
    obj.save(force_insert=True, using=self.db)
  File "/django/env/lib/python3.8/site-packages/django/db/models/base.py", line 726, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/django/env/lib/python3.8/site-packages/django/db/models/base.py", line 763, in save_base
    updated = self._save_table(
  File "/django/env/lib/python3.8/site-packages/django/db/models/base.py", line 868, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "/django/env/lib/python3.8/site-packages/django/db/models/base.py", line 906, in _do_insert
    return manager._insert(
  File "/django/env/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/django/env/lib/python3.8/site-packages/django/db/models/query.py", line 1270, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/django/env/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1410, in execute_sql
    cursor.execute(sql, params)
  File "/django/env/lib/python3.8/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File "/django/env/lib/python3.8/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/django/env/lib/python3.8/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/django/env/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/django/env/lib/python3.8/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/django/env/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/django/env/lib/python3.8/site-packages/django/db/backends/mysql/base.py", line 73, in execute
    return self.cursor.execute(query, args)
  File "/django/env/lib/python3.8/site-packages/MySQLdb/cursors.py", line 206, in execute
    res = self._query(query)
  File "/django/env/lib/python3.8/site-packages/MySQLdb/cursors.py", line 319, in _query
    db.query(q)
  File "/django/env/lib/python3.8/site-packages/MySQLdb/connections.py", line 259, in query
    _mysql.connection.query(self, query)
django.db.utils.DataError: (1406, "Data too long for column 'name' at row 1")

根据您使用的数据库,它可能(不)被接受。例如,SQLite 强制执行长度限制。事实上,作为 FAQ of SQLite says:

(9) What is the maximum size of a VARCHAR in SQLite?

SQLite does not enforce the length of a VARCHAR. You can declare a VARCHAR(10) and SQLite will be happy to store a 500-million character string there. And it will keep all 500-million characters intact. Your content is never truncated. SQLite understands the column type of "VARCHAR(N)" to be the same as "TEXT", regardless of the value of N.

所以 SQLite 不会验证它。

您可以让 Django 验证器 运行 但是 将其保存到数据库之前,例如:

animal = Animal(name='TestAnimalWithTooManyCharacters')
animal.full_clean()
animal.save()

这将使 Django 验证器 运行 并在数据无效时引发异常:

>>> animal = Animal(name='TestAnimalWithTooManyCharacters')
>>> animal.full_clean()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/django/env/lib/python3.8/site-packages/django/db/models/base.py", line 1238, in full_clean
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'name': ['Ensure this value has at most 30 characters (it has 31).']}

Django ModelForm 也会检查这一点并拒绝传递给表单的数据,以防字符串太长。