跳转到主要内容

测试django模式和数据迁移,包括排序

项目描述

django-test-migrations

wemake.services Build status codecov Python Version PyPI - Django Version wemake-python-styleguide

功能

  • 允许测试django模式和数据处理迁移
  • 允许测试正向和回滚迁移
  • 允许测试迁移顺序
  • 允许测试迁移名称
  • 允许测试数据库配置
  • 完全使用注解类型化,并使用mypy检查,与PEP561兼容
  • 易于开始:拥有大量文档、测试和教程

阅读宣布文章。查看实际使用示例

安装

pip install django-test-migrations

我们支持几个django版本

  • 3.2
  • 4.1
  • 4.2
  • 5.0

其他版本可能也可以正常工作,但它们不受官方支持。

测试Django迁移

django领域,测试迁移并不常见。但有时这是完全必要的。何时?

当我们进行复杂的模式或数据更改,并确保现有数据不会被损坏时。我们可能还希望确保所有迁移都可以安全回滚。最后,我们希望确保迁移顺序正确,并且具有正确的依赖关系。

测试正向迁移

为了测试所有迁移,我们有一个Migrator类。

它有三个方法可以操作

  • .apply_initial_migration()接受应用和迁移名称,在实际迁移发生之前生成一个状态。它通过应用所有传递的参数及之前的迁移来创建before state

  • .apply_tested_migration()接受应用和迁移名称来执行实际迁移

  • .reset()在测试完成后清理一切

所以,这里有一个例子

from django_test_migrations.migrator import Migrator

migrator = Migrator(database='default')

# Initial migration, currently our model has only a single string field:
# Note:
# We are testing migration `0002_someitem_is_clean`, so we are specifying
# the name of the previous migration (`0001_initial`) in the
# .apply_initial_migration() method in order to prepare a state of the database
# before applying the migration we are going to test.
#
old_state = migrator.apply_initial_migration(('main_app', '0001_initial'))
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')

# Let's create a model with just a single field specified:
SomeItem.objects.create(string_field='a')
assert len(SomeItem._meta.get_fields()) == 2  # id + string_field

# Now this migration will add `is_clean` field to the model:
new_state = migrator.apply_tested_migration(
    ('main_app', '0002_someitem_is_clean'),
)
SomeItem = new_state.apps.get_model('main_app', 'SomeItem')

# We can now test how our migration worked, new field is there:
assert SomeItem.objects.filter(is_clean=True).count() == 0
assert len(SomeItem._meta.get_fields()) == 3  # id + string_field + is_clean

# Cleanup:
migrator.reset()

这是一个正向迁移的例子。

逆向迁移

事实上,你也可以测试逆向迁移。除了传递的迁移名称和你的逻辑外,没有什么真正改变。

migrator = Migrator()

# Currently our model has two field, but we need a rollback:
old_state = migrator.apply_initial_migration(
    ('main_app', '0002_someitem_is_clean'),
)
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')

# Create some data to illustrate your cases:
# ...

# Now this migration will drop `is_clean` field:
new_state = migrator.apply_tested_migration(('main_app', '0001_initial'))

# Assert the results:
# ...

# Cleanup:
migrator.reset()

测试迁移顺序

有时我们还想确保我们的迁移顺序正确,并且所有我们的dependencies = [...]都是正确的。

为了实现这一点,我们有一个plan.py模块。

这是它的用法

from django_test_migrations.plan import all_migrations, nodes_to_tuples

main_migrations = all_migrations('default', ['main_app', 'other_app'])
assert nodes_to_tuples(main_migrations) == [
    ('main_app', '0001_initial'),
    ('main_app', '0002_someitem_is_clean'),
    ('other_app', '0001_initial'),
    ('main_app', '0003_update_is_clean'),
    ('main_app', '0004_auto_20191119_2125'),
    ('other_app', '0002_auto_20191120_2230'),
]

这样你就可以确保迁移和相互依赖的应用将按正确的顺序执行。

factory_boy集成

如果你使用工厂创建模型,你可以用factory的方法替换它们各自的.build().create()调用,并将模型名称和工厂类作为参数传递

import factory

old_state = migrator.apply_initial_migration(
    ('main_app', '0002_someitem_is_clean'),
)
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')

# instead of
# item = SomeItemFactory.create()
# use this:
factory.create(SomeItem, FACTORY_CLASS=SomeItemFactory)

# ...

测试框架集成 🐍

我们支持几个测试框架作为一等公民。毕竟,这是一个测试工具!

请注意,在测试开始时,Django post_migrate信号的接收者列表被清除,之后恢复。如果你需要测试自己的post_migrate信号,则在测试期间附加/删除它们。

pytest

我们提供的django-test-migrations附带一个pytest插件,它提供了两个方便的固定装置

  • migrator_factory为你提供了创建任何数据库的Migrator类的机会
  • migrator实例为'default'数据库

这是它的用法

import pytest

@pytest.mark.django_db
def test_pytest_plugin_initial(migrator):
    """Ensures that the initial migration works."""
    old_state = migrator.apply_initial_migration(('main_app', None))

    with pytest.raises(LookupError):
        # Model does not yet exist:
        old_state.apps.get_model('main_app', 'SomeItem')

    new_state = migrator.apply_tested_migration(('main_app', '0001_initial'))
    # After the initial migration is done, we can use the model state:
    SomeItem = new_state.apps.get_model('main_app', 'SomeItem')
    assert SomeItem.objects.filter(string_field='').count() == 0

unittest

我们还提供与内置的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_update_is_clean')

    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

仅选择迁移测试

在CI系统中,获得即时反馈很重要。运行应用数据库迁移的测试可能会减慢测试执行速度,因此通常在并行运行较慢的迁移测试的同时运行标准的、快速的常规单元测试是一个好主意。

pytest

django_test_migrations给每个使用migrator_factorymigrator固定装置的测试添加了migration_test标记。要仅运行迁移测试,使用-m选项

pytest -m migration_test  # Runs only migration tests
pytest -m "not migration_test"  # Runs all except migration tests

unittest

django_test_migrations给每个MigratorTestCase子类添加了migration_test 标记。要仅运行迁移测试,使用--tag选项

python mange.py test --tag=migration_test  # Runs only migration tests
python mange.py test --exclude-tag=migration_test  # Runs all except migration tests

Django Checks

django_test_migrations 带有两组 Django 检查,用于

  • 自动检测迁移脚本生成的名称
  • 验证数据库设置的一部分

测试迁移名称

django 在您运行 makemigrations 时为您生成迁移名称。这些名称不好(了解更多为什么它不好!)(阅读更多关于为什么它不好!): 0004_auto_20191119_2125.py

这个迁移做了什么?它有哪些更改?

也可以在创建迁移时传递 --name 属性,但很容易忘记。

我们提供了一个自动解决方案:生成错误并针对每个名称不正确的迁移进行检查的 django 检查。

将我们的检查添加到您的 INSTALLED_APPS

INSTALLED_APPS = [
    # ...

    # Our custom check:
    'django_test_migrations.contrib.django_checks.AutoNames',
]

然后在您的 CI 中运行

python manage.py check --deploy

这样,您就可以避免迁移中的错误名称。

您有无法重命名的迁移吗?将它们添加到忽略列表

# settings.py

DTM_IGNORED_MIGRATIONS = {
    ('main_app', '0004_auto_20191119_2125'),
    ('dependency_app', '0001_auto_20201110_2100'),
}

然后我们不会抱怨它们。

或者您可以选择完全忽略整个应用程序

# settings.py

DTM_IGNORED_MIGRATIONS = {
    ('dependency_app', '*'),
    ('another_dependency_app', '*'),
}

数据库配置

将我们的检查添加到 INSTALLED_APPS

INSTALLED_APPS = [
    # ...

    # Our custom check:
    'django_test_migrations.contrib.django_checks.DatabaseConfiguration',
]

然后只需在您的 CI 中运行 check 管理命令,如上所述。

相关项目

您可能还喜欢

  • django-migration-linter - 检测 django 项目的向后不兼容迁移。
  • wemake-django-template - 针对代码质量和安全性的 bleeding edge django 模板,集成了 django-test-migrationsdjango-migration-linter

致谢

本项目基于其他优秀人士的工作

许可证

MIT。

项目详情


下载文件

为您的平台下载文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源代码分布

django_test_migrations-1.4.0.tar.gz (20.3 kB 查看散列)

上传时间

构建分布

django_test_migrations-1.4.0-py3-none-any.whl (25.2 kB 查看散列)

上传时间 Python 3

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面