[英]How do I run tests against a Django data migration?
使用文档中的以下示例:
def combine_names(apps, schema_editor):
Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
person.name = "%s %s" % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]
我将如何针对此迁移创建和运行测试,以确认数据已正确迁移?
我正在做一些谷歌来解决同样的问题,并找到了一篇文章,它为我钉在钉子上,似乎没有现有答案那么笨拙。 所以,把它放在这里以防它帮助其他人。
提出了 Django 的TestCase
的以下子类:
from django.apps import apps
from django.test import TestCase
from django.db.migrations.executor import MigrationExecutor
from django.db import connection
class TestMigrations(TestCase):
@property
def app(self):
return apps.get_containing_app_config(type(self).__module__).name
migrate_from = None
migrate_to = None
def setUp(self):
assert self.migrate_from and self.migrate_to, \
"TestCase '{}' must define migrate_from and migrate_to properties".format(type(self).__name__)
self.migrate_from = [(self.app, self.migrate_from)]
self.migrate_to = [(self.app, self.migrate_to)]
executor = MigrationExecutor(connection)
old_apps = executor.loader.project_state(self.migrate_from).apps
# Reverse to the original migration
executor.migrate(self.migrate_from)
self.setUpBeforeMigration(old_apps)
# Run the migration to test
executor = MigrationExecutor(connection)
executor.loader.build_graph() # reload.
executor.migrate(self.migrate_to)
self.apps = executor.loader.project_state(self.migrate_to).apps
def setUpBeforeMigration(self, apps):
pass
他们提出的一个示例用例是:
class TagsTestCase(TestMigrations):
migrate_from = '0009_previous_migration'
migrate_to = '0010_migration_being_tested'
def setUpBeforeMigration(self, apps):
BlogPost = apps.get_model('blog', 'Post')
self.post_id = BlogPost.objects.create(
title = "A test post with tags",
body = "",
tags = "tag1 tag2",
).id
def test_tags_migrated(self):
BlogPost = self.apps.get_model('blog', 'Post')
post = BlogPost.objects.get(id=self.post_id)
self.assertEqual(post.tags.count(), 2)
self.assertEqual(post.tags.all()[0].name, "tag1")
self.assertEqual(post.tags.all()[1].name, "tag2")
您可以使用django-test-migrations
包。 它适用于测试:数据迁移、模式迁移和迁移顺序。
这是它的工作原理:
from django_test_migrations.migrator import Migrator
# You can specify any database alias you need:
migrator = Migrator(database='default')
old_state = migrator.before(('main_app', '0002_someitem_is_clean'))
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')
# One instance will be `clean`, the other won't be:
SomeItem.objects.create(string_field='a')
SomeItem.objects.create(string_field='a b')
assert SomeItem.objects.count() == 2
assert SomeItem.objects.filter(is_clean=True).count() == 2
new_state = migrator.after(('main_app', '0003_auto_20191119_2125'))
SomeItem = new_state.apps.get_model('main_app', 'SomeItem')
assert SomeItem.objects.count() == 2
# One instance is clean, the other is not:
assert SomeItem.objects.filter(is_clean=True).count() == 1
assert SomeItem.objects.filter(is_clean=False).count() == 1
我们还为pytest
提供了本地集成:
@pytest.mark.django_db
def test_main_migration0002(migrator):
"""Ensures that the second migration works."""
old_state = migrator.before(('main_app', '0002_someitem_is_clean'))
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')
...
和unittest
:
from django_test_migrations.contrib.unittest_case import MigratorTestCase
class TestDirectMigration(MigratorTestCase):
"""This class is used to test direct migrations."""
migrate_from = ('main_app', '0002_someitem_is_clean')
migrate_to = ('main_app', '0003_auto_20191119_2125')
def prepare(self):
"""Prepare some data before the migration."""
SomeItem = self.old_state.apps.get_model('main_app', 'SomeItem')
SomeItem.objects.create(string_field='a')
SomeItem.objects.create(string_field='a b')
def test_migration_main0003(self):
"""Run the test itself."""
SomeItem = self.new_state.apps.get_model('main_app', 'SomeItem')
assert SomeItem.objects.count() == 2
assert SomeItem.objects.filter(is_clean=True).count() == 1
编辑:
这些其他答案更有意义:
原来的:
在实际应用它们之前,通过一些基本的单元测试运行您的数据迁移函数(例如 OP 示例中的combine_names
),对我来说也很有意义。
乍一看,这应该不会比普通的 Django 单元测试困难得多:迁移是 Python 模块,而migrations/
文件夹是一个包,因此可以从中导入内容。 但是,这需要一些时间才能发挥作用。
第一个困难是由于默认迁移文件名以数字开头。 例如,假设来自 OP(即 Django 的)数据迁移示例的代码位于0002_my_data_migration.py
,那么很容易使用
from yourappname.migrations.0002_my_data_migration import combine_names
但这会引发SyntaxError
因为模块名称以数字 ( 0
) 开头。
至少有两种方法可以使这项工作:
重命名迁移文件,使其不以数字开头。 根据文档,这应该完全没问题:“Django 只关心每个迁移都有不同的名称。” 然后你可以像上面一样使用import
。
如果您想坚持使用默认编号的迁移文件名,您可以使用 Python 的import_module
(请参阅文档和这个SO 问题)。
第二个困难来自这样一个事实,即您的数据迁移函数被设计为传递到RunPython
( docs ),因此它们默认需要两个输入参数: apps
和schema_editor
。 要查看这些来自哪里,您可以检查源。
现在,我不知道这个工程的每一种情况下(请,任何人,评论,如果你能澄清),但对于我们而言,这是足够的导入apps
从django.apps并获得schema_editor
从活动数据库connection
( Django的.db.connection )。
以下是一个精简的示例,展示了如何为 OP 示例实现这一点,假设迁移文件名为0002_my_data_migration.py
:
from importlib import import_module
from django.test import TestCase
from django.apps import apps
from django.db import connection
from yourappname.models import Person
# Our filename starts with a number, so we use import_module
data_migration = import_module('yourappname.migrations.0002_my_data_migration')
class DataMigrationTests(TestCase):
def __init__(self, *args, **kwargs):
super(DataMigrationTests, self).__init__(*args, **kwargs)
# Some test values
self.first_name = 'John'
self.last_name = 'Doe'
def test_combine_names(self):
# Create a dummy Person
Person.objects.create(first_name=self.first_name,
last_name=self.last_name,
name=None)
# Run the data migration function
data_migration.combine_names(apps, connection.schema_editor())
# Test the result
person = Person.objects.get(id=1)
self.assertEqual('{} {}'.format(self.first_name, self.last_name), person.name)
您可以在先前的迁移中添加一个粗略的 if 语句来测试测试套件是否正在运行,如果是,则添加初始数据——这样您就可以编写一个测试来检查对象是否处于您想要的最终状态in. 只需确保您的条件与生产兼容,这是一个适用于python manage.py test
的示例:
import sys
if 'test in sys.argv:
# do steps to update your operations
对于更“完整”的解决方案,这篇较旧的博客文章有一些很好的信息和更多最新的灵感评论:
https://micknelson.wordpress.com/2013/03/01/testing-django-migrations/#comments
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.