Как запустить тестовую базу данных Django только в памяти?


125

Мои модульные тесты Django выполняются долго, поэтому я ищу способы ускорить это. Я подумываю об установке SSD , но знаю, что у него есть и недостатки. Конечно, со своим кодом я могу кое-что сделать, но я ищу структурное исправление. Даже запуск одного теста выполняется медленно, поскольку базу данных необходимо каждый раз перестраивать / переносить на юг. Итак, вот моя идея ...

Поскольку я знаю, что тестовая база данных всегда будет довольно маленькой, почему я не могу просто настроить систему, чтобы всегда хранить всю тестовую базу данных в ОЗУ? Ни в коем случае не трогайте диск. Как мне настроить это в Django? Я бы предпочел продолжать использовать MySQL, поскольку это то, что я использую в производстве, но если SQLite  3 или что-то еще упрощает это, я бы пошел по этому пути.

Есть ли у SQLite или MySQL возможность работать полностью в памяти? Должна быть возможность настроить RAM-диск, а затем настроить тестовую базу данных для хранения своих данных там, но я не уверен, как указать Django / MySQL использовать другой каталог данных для определенной базы данных, тем более, что он продолжает стираться и воссоздавал каждый прогон. (Я использую Mac FWIW.)

Ответы:


164

Если вы установите ядро ​​базы данных на sqlite3 при запуске тестов, Django будет использовать базу данных в памяти .

Я использую такой код в моем, settings.pyчтобы настроить двигатель на sqlite при запуске моих тестов:

if 'test' in sys.argv:
    DATABASE_ENGINE = 'sqlite3'

Или в Django 1.2:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'sqlite3'}

И, наконец, в Django 1.3 и 1.4:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

(Полный путь к бэкэнду не является строго обязательным для Django 1.3, но обеспечивает прямую совместимость настроек.)

Вы также можете добавить следующую строку, если у вас возникли проблемы с миграцией на юг:

    SOUTH_TESTS_MIGRATE = False

9
Да, точно. Я должен был вставить это в свой ответ! Объедините это с SOUTH_TESTS_MIGRATE = False, и ваши тесты должны быть намного быстрее.
— Etienne

7
это является удивительным. в новых настройках django используйте эту строку: 'ENGINE': 'sqlite3' if 'test' в sys.argv else 'django.db.backends.mysql',
— mjallday

3
@Tomasz Zielinski - Хм, это зависит от того, что вы тестируете. Но я полностью согласен с тем, что в конце и время от времени вам нужно запускать тесты с вашей реальной базой данных (Postgres, MySQL, Oracle ...). Но выполнение ваших тестов в памяти с помощью sqlite может сэкономить вам много времени.
— Этьен

3
Я переворачиваю -1 на +1: как я вижу сейчас, гораздо быстрее использовать sqlite для быстрого запуска и переключаться на MySQL, например, для заключительных ежедневных тестов. (Обратите внимание, что мне пришлось сделать фиктивное редактирование, чтобы разблокировать голосование)
— Tomasz Zieliński

12
Осторожно с этим "test" in sys.argv; он может срабатывать, когда вы этого не хотите, например manage.py collectstatic -i test. sys.argv[1] == "test"это более точное условие, которое не должно иметь такой проблемы.
— keturn

83

Я обычно создаю отдельный файл настроек для тестов и использую его в тестовой команде, например

python manage.py test --settings=mysite.test_settings myapp

У этого есть два преимущества:

  1. Вам не нужно проверять какое- testлибо волшебное слово в sys.argv, test_settings.pyможно просто

    from settings import *
    
    # make tests faster
    SOUTH_TESTS_MIGRATE = False
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}
    

    Или вы можете дополнительно настроить его под свои нужды, четко отделив тестовые настройки от производственных.

  2. Еще одно преимущество заключается в том, что вы можете запускать тест с движком производственной базы данных вместо sqlite3, избегая тонких ошибок, поэтому при разработке используйте

    python manage.py test --settings=mysite.test_settings myapp

    и перед фиксацией кода запустить один раз

    python manage.py test myapp

    просто чтобы убедиться, что все тесты действительно проходят.


2
Мне нравится такой подход. У меня есть куча разных файлов настроек, и я использую их для разных серверных сред, но я не думал об использовании этого метода для выбора другой тестовой базы данных. Спасибо за идею.
— Alexis Bellido

Привет, Anurag, я пробовал это, но другие мои базы данных, упомянутые в настройках, также выполняются. Я не могу понять точную причину.
— Bhupesh Pant

Хороший ответ. Интересно, как указать файл настроек при запуске тестов через покрытие.
— Wtower

Это хороший подход, но не СУХОЙ. Django уже знает, что вы запускаете тесты. Если бы вы могли каким-то образом «привязаться» к этим знаниям, вы бы были настроены. К сожалению, я считаю, что это требует расширения команды управления. Вероятно, было бы разумно сделать этот общий элемент в ядре фреймворка, например, установив параметр MANAGEMENT_COMMAND на текущую команду всякий раз, когда вызывается manage.py, или что-то в этом роде.
— DylanYoung

2
@DylanYoung, вы можете сделать его сухим, включив основные настройки в test_settings и просто переопределив то, что вы хотите для тестирования.
— Anurag Uniyal

22

MySQL поддерживает механизм хранения под названием «MEMORY», который вы можете настроить в своей базе данных config ( settings.py) как таковой:

    'USER': 'root',                      # Not used with sqlite3.
    'PASSWORD': '',                  # Not used with sqlite3.
    'OPTIONS': {
        "init_command": "SET storage_engine=MEMORY",
    }

Обратите внимание, что механизм хранения MEMORY не поддерживает столбцы blob / text, поэтому, если вы используете его, django.db.models.TextFieldэто не сработает для вас.


5
+1 за упоминание об отсутствии поддержки столбцов blob / text. Он также, похоже, не поддерживает транзакции ( dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html ).
— Туукка Мустонен

Если вам действительно нужны тесты в памяти, вам, вероятно, лучше использовать sqlite, который хотя бы поддерживает транзакции.
— atomic77

15

Я не могу ответить на ваш главный вопрос, но есть несколько вещей, которые вы можете сделать, чтобы ускорить процесс.

Во-первых, убедитесь, что ваша база данных MySQL настроена для использования InnoDB. Затем он может использовать транзакции для отката состояния базы данных перед каждым тестом, что, по моему опыту, привело к значительному ускорению. Вы можете передать команду инициализации базы данных в файле settings.py (синтаксис Django 1.2):

DATABASES = {
    'default': {
            'ENGINE':'django.db.backends.mysql',
            'HOST':'localhost',
            'NAME':'mydb',
            'USER':'whoever',
            'PASSWORD':'whatever',
            'OPTIONS':{"init_command": "SET storage_engine=INNODB" } 
        }
    }

Во-вторых, вам не нужно каждый раз запускать миграции на юг. Задайте его SOUTH_TESTS_MIGRATE = Falseв файле settings.py, и база данных будет создана с помощью обычного syncdb, что будет намного быстрее, чем выполнение всех предыдущих миграций.


Отличный совет! Это сократило мои тесты с 369 tests in 498.704sдо 369 tests in 41.334s . Это более чем в 10 раз быстрее!
— Габи Пуркару

Есть ли в settings.py эквивалентный переключатель для миграции в Django 1.7+?
— Эдвард Ньюэлл

@EdwardNewell Не совсем так. Но вы можете использовать его --keepдля сохранения базы данных и не требовать повторного применения полного набора миграций при каждом тестовом запуске. Новые миграции по-прежнему будут выполняться. Если вы часто переключаетесь между ветвями, легко попасть в несогласованное состояние (вы можете отменить новые миграции перед переключением, изменив базу данных на тестовую и запустив ее migrate, но это немного неудобно).
— DylanYoung

10

Вы можете сделать двойную настройку:

  • использовать транзакционные таблицы: начальное состояние фикстур будет установлено с помощью отката базы данных после каждого TestCase.
  • поместите каталог данных вашей базы данных на ramdisk: вы выиграете много в том, что касается создания базы данных, а также выполнение теста будет быстрее.

Я использую обе уловки и вполне доволен.

Как настроить его для MySQL в Ubuntu:

$ sudo service mysql stop
$ sudo cp -pRL /var/lib/mysql /dev/shm/mysql

$ vim /etc/mysql/my.cnf
# datadir = /dev/shm/mysql
$ sudo service mysql start

Осторожно, это просто для тестирования, после перезагрузки ваша база данных из памяти теряется!


Спасибо! работает для меня. Я не могу использовать sqlite, потому что использую функции, специфичные для mysql (полнотекстовые индексы). Для пользователей ubuntu вам нужно будет отредактировать конфигурацию apparmor, чтобы разрешить доступ mysqld к / dev / shm / mysql
— Иван Вирабян

Поздравления с Иваном и Потром. На данный момент отключил профиль mysql в AppArmor, но нашел руководство по настройке соответствующего локального профиля: blogs.oracle.com/jsmyth/entry/apparmor_and_mysql
— trojjer

Хм. Я пробовал настроить локальный профиль, чтобы предоставить mysqld доступ к пути / dev / shm / mysql и его содержимому, но служба может запускаться только в режиме «жаловаться» (команда aa-жалоба), а не «принудительно», для некоторых причина ... Вопрос к другому форуму! Что я не могу понять, так это то, что вообще нет никаких «жалоб», когда он работает, подразумевая, что mysqld не нарушает профиль ...
— Trojjer

4

Другой подход: другой экземпляр MySQL работает в tempfs, который использует RAM Disk. Инструкции в этом сообщении в блоге: Ускорение MySQL для тестирования в Django .

Преимущества:

  • Вы используете ту же базу данных, что и ваш производственный сервер.
  • нет необходимости изменять конфигурацию mysql по умолчанию

2

Расширяя ответ Anurag, я упростил процесс, создав те же настройки test_settings и добавив следующее в manage.py

if len(sys.argv) > 1 and sys.argv[1] == "test":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.test_settings")
else:
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

кажется более чистым, поскольку sys уже импортирован, а manage.py используется только через командную строку, поэтому не нужно загромождать настройки


2
Осторожно с этим "test" in sys.argv; он может срабатывать, когда вы этого не хотите, например manage.py collectstatic -i test. sys.argv[1] == "test"это более точное условие, которое не должно иметь такой проблемы.
— keturn

2
@keturn, таким образом, он генерирует исключение при работе ./manage.pyбез аргументов (например, чтобы увидеть, какие плагины доступны, как и --help)
— Энтони Хэтчкинс

1
@AntonyHatchkins Это тривиально решить:len(sys.argv) > 1 and sys.argv[1] == "test"
— DylanYoung

1
@DylanYoung Да, это именно то, что я хотел, чтобы Элвин добавил к своему решению, но он не особо заинтересован в его улучшении. В любом случае это больше похоже на быстрый взлом, чем на законное решение.
— Энтони Хэтчкинс

1
давно не смотрел на этот ответ, я обновил фрагмент, чтобы отразить улучшение @ DylanYoung
— Элвин

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.