如何对 Django 数据库迁移进行单元测试?
How to unittest a django database migration?
我们已经使用 django 迁移 (django v1.7+) 更改了我们的数据库。
数据库中存在的数据不再有效。
基本上,我想通过在单元测试中构建迁移前数据库、添加一些数据、应用迁移,然后确认一切顺利进行来测试迁移。
一个怎么样:
加载单元测试时阻止新迁移
我发现 some stuff 关于覆盖 settings.MIGRATION_MODULES
但不知道如何使用它。当我检查 executor.loader.applied_migrations
时,它仍然列出所有内容。我可以阻止新迁移的唯一方法是实际删除文件;不是我可以使用的解决方案。
在单元测试数据库中创建一条记录(使用旧模型)
如果我们可以阻止迁移,那么这应该非常简单。 myModel.object.create(...)
应用迁移
既然我找到了 test_executor:我想我可能可以解决这个问题:设置一个指向迁移文件的计划并执行它?嗯,对吧?得到任何代码:-D
确认数据库中的旧数据现在与新模型匹配
同样,我希望这应该非常简单:只需获取迁移前创建的实例并确认它已以所有正确的方式更改。
所以真正的挑战就是解决如何防止单元测试应用最新的迁移脚本然后在我们准备好时应用它?
可能是我方法不对?我应该创建固定装置,并确认它们最后都很好吗?固定装置是在应用迁移之前加载还是在它们全部完成之后加载?
通过使用 MigrationExecutor
并使用 .migrate
选择特定的迁移,我已经能够,也许?,将其回滚到特定状态,然后一个一个地向前滚动.但这引起了怀疑;由于缺少实际的 ALTER TABLE 指令,目前正在追查 sqlite fudging。陪审团还没出来。
我们在 settings_test.py
中使用了以下代码来忽略测试迁移:
MIGRATION_MODULES = dict(
(app.split('.')[-1], '.'.join([app, 'nonexistent_django_migrations_module']))
for app in INSTALLED_APPS
)
这里的想法是 none 的应用程序有一个 nonexistent_django_migrations_module
文件夹,因此 django 将根本找不到任何迁移。
我无法阻止单元测试从 开始 使用当前的数据库架构,但我确实发现很容易恢复到迁移历史中的早期点:
其中“0014_nulls_permitted”是迁移目录中的一个文件...
from django.db.migrations.executor import MigrationExecutor
executor.migrate([("workflow_engine", "0014_nulls_permitted")])
executor.loader.build_graph()
注意: 运行 executor.migrate
调用之间的 executor.loader.build_graph
似乎是完成的一个非常重要的部分迁移并使事情表现得像人们预期的那样
当前适用于数据库的迁移可以通过以下方式检查:
print [x[1] for x in sorted(executor.loader.applied_migrations)]
[u'0001_initial', u'0002_fix_foreignkeys', ... u'0014_nulls_permitted']
我通过 ORM 创建了一个模型实例,然后通过 运行 一些 SQL 直接确保数据库处于旧状态:
job = Job.objects.create(....)
from django.db import connection
cursor = connection.cursor()
cursor.execute('UPDATE workflow_engine_job SET next_job_state=NULL')
太棒了。现在我知道我有一个旧状态的数据库,可以测试向前迁移。所以 0016_nulls_banished 是一个迁移文件:
executor.migrate([("workflow_engine", "0016_nulls_banished")])
executor.loader.build_graph()
迁移 0015 通过数据库将所有 NULL 字段转换为默认值。迁移 0016 改变了架构。您可以散布一些打印语句以确认事情正在按照您认为应该发生的方式发生。
现在测试可以确认迁移成功了。在这种情况下,通过确保数据库中没有遗留空值。
jobs = Job.objects.all()
self.assertTrue(all([j.next_job_state is not None for j in jobs]))
我们已经使用 django 迁移 (django v1.7+) 更改了我们的数据库。 数据库中存在的数据不再有效。
基本上,我想通过在单元测试中构建迁移前数据库、添加一些数据、应用迁移,然后确认一切顺利进行来测试迁移。
一个怎么样:
加载单元测试时阻止新迁移
我发现 some stuff 关于覆盖
settings.MIGRATION_MODULES
但不知道如何使用它。当我检查executor.loader.applied_migrations
时,它仍然列出所有内容。我可以阻止新迁移的唯一方法是实际删除文件;不是我可以使用的解决方案。在单元测试数据库中创建一条记录(使用旧模型)
如果我们可以阻止迁移,那么这应该非常简单。
myModel.object.create(...)
应用迁移
既然我找到了 test_executor:我想我可能可以解决这个问题:设置一个指向迁移文件的计划并执行它?嗯,对吧?得到任何代码:-D
确认数据库中的旧数据现在与新模型匹配
同样,我希望这应该非常简单:只需获取迁移前创建的实例并确认它已以所有正确的方式更改。
所以真正的挑战就是解决如何防止单元测试应用最新的迁移脚本然后在我们准备好时应用它?
可能是我方法不对?我应该创建固定装置,并确认它们最后都很好吗?固定装置是在应用迁移之前加载还是在它们全部完成之后加载?
通过使用 MigrationExecutor
并使用 .migrate
选择特定的迁移,我已经能够,也许?,将其回滚到特定状态,然后一个一个地向前滚动.但这引起了怀疑;由于缺少实际的 ALTER TABLE 指令,目前正在追查 sqlite fudging。陪审团还没出来。
我们在 settings_test.py
中使用了以下代码来忽略测试迁移:
MIGRATION_MODULES = dict(
(app.split('.')[-1], '.'.join([app, 'nonexistent_django_migrations_module']))
for app in INSTALLED_APPS
)
这里的想法是 none 的应用程序有一个 nonexistent_django_migrations_module
文件夹,因此 django 将根本找不到任何迁移。
我无法阻止单元测试从 开始 使用当前的数据库架构,但我确实发现很容易恢复到迁移历史中的早期点:
其中“0014_nulls_permitted”是迁移目录中的一个文件...
from django.db.migrations.executor import MigrationExecutor
executor.migrate([("workflow_engine", "0014_nulls_permitted")])
executor.loader.build_graph()
注意: 运行 executor.migrate
调用之间的 executor.loader.build_graph
似乎是完成的一个非常重要的部分迁移并使事情表现得像人们预期的那样
当前适用于数据库的迁移可以通过以下方式检查:
print [x[1] for x in sorted(executor.loader.applied_migrations)]
[u'0001_initial', u'0002_fix_foreignkeys', ... u'0014_nulls_permitted']
我通过 ORM 创建了一个模型实例,然后通过 运行 一些 SQL 直接确保数据库处于旧状态:
job = Job.objects.create(....)
from django.db import connection
cursor = connection.cursor()
cursor.execute('UPDATE workflow_engine_job SET next_job_state=NULL')
太棒了。现在我知道我有一个旧状态的数据库,可以测试向前迁移。所以 0016_nulls_banished 是一个迁移文件:
executor.migrate([("workflow_engine", "0016_nulls_banished")])
executor.loader.build_graph()
迁移 0015 通过数据库将所有 NULL 字段转换为默认值。迁移 0016 改变了架构。您可以散布一些打印语句以确认事情正在按照您认为应该发生的方式发生。
现在测试可以确认迁移成功了。在这种情况下,通过确保数据库中没有遗留空值。
jobs = Job.objects.all()
self.assertTrue(all([j.next_job_state is not None for j in jobs]))