簡體   English   中英

如何針對 Django 數據遷移運行測試?

[英]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 ) 開頭。

至少有兩種方法可以使這項工作:

  1. 重命名遷移文件,使其不以數字開頭。 根據文檔,這應該完全沒問題:“Django 只關心每個遷移都有不同的名稱。” 然后你可以像上面一樣使用import

  2. 如果您想堅持使用默認編號的遷移文件名,您可以使用 Python 的import_module (請參閱文檔這個SO 問題)。

第二個困難來自這樣一個事實,即您的數據遷移函數被設計為傳遞到RunPython ( docs ),因此它們默認需要兩個輸入參數: appsschema_editor 要查看這些來自哪里,您可以檢查

現在,我不知道這個工程的每一種情況下(請,任何人,評論,如果你能澄清),但對於我們而言,這是足夠的導入appsdjango.apps並獲得schema_editor從活動數據庫connectionDjango的.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM