Есть ли возможность писать юнит-тесты django без настройки db? Я хочу протестировать бизнес-логику, которая не требует настройки базы данных. И хотя БД настраивается быстро, мне это действительно не нужно в некоторых ситуациях.
Мне интересно, действительно ли это имеет значение. БД хранится в памяти + если у вас нет моделей, с БД ничего не происходит. Так что, если вам это не нужно, не настраивайте модели.
Торстен Энгельбрехт
3
У меня есть модели, но для тех тестов они не актуальны. И db не хранится в памяти, а создается в mysql, однако, специально для этой цели. Не то чтобы я этого хотел ... Возможно, я мог бы настроить django на использование базы данных в памяти для тестирования. Ты знаешь как это сделать?
paweloque
Ах, прошу прощения. Базы данных в памяти - это тот случай, когда вы используете базу данных SQLite. Кроме этого, я не вижу способа избежать создания тестовой базы данных. В документации об этом нет ничего + Я никогда не чувствовал необходимости избегать этого.
Вы можете создать подкласс DjangoTestSuiteRunner и переопределить методы setup_databases и teardown_databases для передачи.
Создайте новый файл настроек и установите TEST_RUNNER на новый класс, который вы только что создали. Затем, когда вы запускаете свой тест, укажите новый файл настроек с флагом --settings.
Вот что я сделал:
Создайте собственный бегун тестового костюма, подобный этому:
from django.test.simple importDjangoTestSuiteRunnerclassNoDbTestRunner(DjangoTestSuiteRunner):""" A test runner to test without database creation """def setup_databases(self,**kwargs):""" Override the database creation defined in parent class """passdef teardown_databases(self, old_config,**kwargs):""" Override the database teardown defined in parent class """pass
Создайте собственные настройки:
from mysite.settings import*# Test runner with no database creation
TEST_RUNNER ='mysite.scripts.testrunner.NoDbTestRunner'
Когда вы запускаете свои тесты, запустите его, как показано ниже, с флагом --settings, установленным для вашего нового файла настроек:
python manage.py test myapp --settings='no_db_settings'
ОБНОВЛЕНИЕ: апрель / 2018
Начиная с Django 1.8, модуль был перемещен в .django.test.simple.DjangoTestSuiteRunner'django.test.runner.DiscoverRunner'
Для получения дополнительной информации ознакомьтесь с разделом официальной документации о пользовательских средствах запуска тестов.
Эта ошибка возникает, когда у вас есть тесты, требующие транзакций с базой данных. Очевидно, что если у вас нет БД, вы не сможете запускать эти тесты. Вы должны запускать свои тесты отдельно. Если вы просто запустите свой тест с помощью python manage.py test --settings = new_settings.py, он будет запускать целый ряд других тестов из других приложений, которым может потребоваться база данных.
mohi666
5
Обратите внимание, что вам нужно расширить SimpleTestCase вместо TestCase для ваших тестовых классов. TestCase ожидает базу данных.
Бен Робертс
9
Если вы не хотите использовать новый файл настроек, вы можете указать новый TestRunner в командной строке с помощью --testrunnerпараметра.
Бран Хэндли
26
Отличный ответ !! В django 1.8 импорт из django.test.simple DjangoTestSuiteRunner был изменен на импорт из django.test.runner DiscoverRunner Надеюсь, это поможет кому-то!
Джош Браун
2
В Django 1.8 и выше можно внести небольшую поправку в приведенный выше код. Оператор импорта может быть изменен на: from django.test.runner import DiscoverRunner NoDbTestRunner теперь должен расширять класс DiscoverRunner.
Aditya Satyavada
77
Обычно тесты в приложении можно разделить на две категории.
Модульные тесты, они проверяют отдельные фрагменты кода в инсоляции и не требуют обращения к базе данных.
Тестовые примеры интеграции, которые фактически переходят в базу данных и тестируют полностью интегрированную логику.
Django поддерживает как модульные, так и интеграционные тесты.
Модульные тесты не требуют установки и удаления базы данных, и мы должны унаследовать их от SimpleTestCase .
from django.test importSimpleTestCaseclassExampleUnitTest(SimpleTestCase):def test_something_works(self):self.assertTrue(True)
Для интеграционных тестовых случаев наследование от TestCase, в свою очередь, наследуется от TransactionTestCase, и он будет настраивать и разрушать базу данных перед запуском каждого теста.
from django.test importTestCaseclassExampleIntegrationTest(TestCase):def test_something_works(self):#do something with databaseself.assertTrue(True)
Эта стратегия гарантирует, что база данных будет создана и уничтожена только для тестовых случаев, которые обращаются к базе данных, и поэтому тесты будут более эффективными.
Это может сделать выполнение тестов более эффективным, но обратите внимание, что средство запуска тестов по-прежнему создает тестовые базы данных при инициализации.
монкут
6
Настолько проще, что выбранный ответ. Спасибо вам большое!
KFunk
1
@monkut Нет ... если у вас есть только класс SimpleTestCase, средство запуска тестов ничего не запускает, см. этот проект .
Клаудио Сантос
Django все равно будет пытаться создать тестовую БД, даже если вы используете только SimpleTestCase. См. Этот вопрос .
Марко Пркач
использование SimpleTestCase точно работает для тестирования служебных методов или фрагментов и не использует и не создает тестовую базу данных. Именно то, что мне нужно!
Tyro Hunter
28
Из django.test.simple
warnings.warn("The django.test.simple module and DjangoTestSuiteRunner are deprecated; ""use django.test.runner.DiscoverRunner instead.",RemovedInDjango18Warning)
Так что переопределите DiscoverRunnerвместо DjangoTestSuiteRunner.
from django.test.runner importDiscoverRunnerclassNoDbTestRunner(DiscoverRunner):""" A test runner to test without database creation/deletion """def setup_databases(self,**kwargs):passdef teardown_databases(self, old_config,**kwargs):pass
Используйте так:
python manage.py test app --testrunner=app.filename.NoDbTestRunner
Я решил унаследовать django.test.runner.DiscoverRunnerэтот run_testsметод и внести в него несколько дополнений .
Мое первое добавление проверяет необходимость настройки базы данных и позволяет задействовать нормальную setup_databasesфункциональность, если требуется база данных. Мое второе добавление позволяет нормальному teardown_databasesзапускать, если setup_databasesметод был разрешен.
В моем коде предполагается, что любой TestCase, который наследуется от django.test.TransactionTestCase(и, следовательно, django.test.TestCase) требует настройки базы данных. Я сделал это предположение, потому что документы Django говорят:
Если вам нужны какие-либо другие более сложные и тяжелые функции, специфичные для Django, такие как ... Тестирование или использование ORM ... тогда вам следует использовать TransactionTestCase или TestCase.
from django.test importTransactionTestCasefrom django.test.runner importDiscoverRunnerclassMyDiscoverRunner(DiscoverRunner):def run_tests(self, test_labels, extra_tests=None,**kwargs):"""
Run the unit tests for all the test labels in the provided list.
Test labels should be dotted Python paths to test modules, test
classes, or test methods.
A list of 'extra' tests may also be provided; these tests
will be added to the test suite.
If any of the tests in the test suite inherit from
``django.test.TransactionTestCase``, databases will be setup.
Otherwise, databases will not be set up.
Returns the number of tests that failed.
"""self.setup_test_environment()
suite =self.build_suite(test_labels, extra_tests)# ----------------- First Addition --------------
need_databases = any(isinstance(test_case,TransactionTestCase)for test_case in suite)
old_config =Noneif need_databases:# --------------- End First Addition ------------
old_config =self.setup_databases()
result =self.run_suite(suite)# ----------------- Second Addition -------------if need_databases:# --------------- End Second Addition -----------self.teardown_databases(old_config)self.teardown_test_environment()returnself.suite_result(suite, result)
Наконец, я добавил следующую строку в файл settings.py моего проекта.
Обновлено: также см. Этот ответ для использования стороннего инструмента pytest.
@ Сезар прав. После случайного запуска./manage.py test --settings=no_db_settings без указания имени приложения моя база данных разработки была уничтожена.
Для более безопасного использования используйте то же самое NoDbTestRunner, но в сочетании со следующим mysite/no_db_settings.py:
from mysite.settings import*# Test runner with no database creation
TEST_RUNNER ='mysite.scripts.testrunner.NoDbTestRunner'# Use an alternative database as a safeguard against accidents
DATABASES['default']['NAME']='_test_mysite_db'
Вам необходимо создать базу данных, вызываемую _test_mysite_dbс помощью инструмента внешней базы данных. Затем выполните следующую команду, чтобы создать соответствующие таблицы:
Я запускал тесты с использованием pytest (с плагином pytest-django) и NoDbTestRunner, если каким-то образом вы случайно создадите объект в тестовом примере и не переопределите имя базы данных, объект будет создан в ваших локальных базах данных, которые вы настроили в настройки. Имя «NoDbTestRunner» должно быть «NoTestDbTestRunner», потому что он не будет создавать тестовую базу данных, но будет использовать вашу базу данных из настроек.
Габриэль Мудж
2
В качестве альтернативы изменению ваших настроек, чтобы сделать NoDbTestRunner «безопасным», вот модифицированная версия NoDbTestRunner, которая закрывает текущее соединение с базой данных и удаляет информацию о соединении из настроек и объекта соединения. Работает для меня, протестируйте его в своей среде, прежде чем полагаться на него :)
classNoDbTestRunner(DjangoTestSuiteRunner):""" A test runner to test without database creation """def __init__(self,*args,**kwargs):# hide/disconnect databases to prevent tests that # *do* require a database which accidentally get # run from altering your datafrom django.db import connections
from django.conf import settings
connections.databases = settings.DATABASES ={}
connections._connections['default'].close()del connections._connections['default']super(NoDbTestRunner,self).__init__(*args,**kwargs)def setup_databases(self,**kwargs):""" Override the database creation defined in parent class """passdef teardown_databases(self, old_config,**kwargs):""" Override the database teardown defined in parent class """pass
ПРИМЕЧАНИЕ. Если вы удалите соединение по умолчанию из списка подключений, вы не сможете использовать модели Django или другие функции, которые обычно используют базу данных (очевидно, что мы не взаимодействуем с базой данных, но Django проверяет различные функции, которые поддерживает БД) , Также кажется, что connections._connections больше не поддерживает __getitem__. Используйте connections._connections.defaultдля доступа к объекту.
Использование unittest.TestCase позволяет избежать затрат на запуск каждого теста в транзакции и очистку базы данных, но если ваши тесты взаимодействуют с базой данных, их поведение будет зависеть от порядка, в котором их выполняет исполнитель тестов. Это может привести к тому, что модульные тесты пройдут изолированно, но не пройдут в комплекте.
Однако, если ваш тест не использует базу данных, это предупреждение не должно вас беспокоить, и вы можете воспользоваться преимуществами отсутствия необходимости запускать каждый тестовый пример в транзакции.
Похоже, что это все еще создает и уничтожает базу данных, с той лишь разницей, что она не запускает тест в транзакции и не сбрасывает базу данных.
Cam Rail
0
Вышеупомянутые решения тоже подходят. Но следующее решение также сократит время создания базы данных, если будет большее количество миграций. Во время модульного тестирования запуск syncdb вместо запуска всех южных миграций будет намного быстрее.
SOUTH_TESTS_MIGRATE = False # Чтобы отключить миграции и вместо этого использовать syncdb
Мой веб-хостинг позволяет создавать и удалять базы данных только из своего веб-интерфейса, поэтому при попытке запуска я получал сообщение об ошибке «Получена ошибка при создании тестовой базы данных: в разрешении отказано». python manage.py test .
Я надеялся использовать параметр --keepdb для django-admin.py, но, похоже, он больше не поддерживается с Django 1.7.
В итоге я изменил код Django в ... / django / db / backends / creation.py, в частности, функции _create_test_db и _destroy_test_db.
Потому что _create_test_dbя закомментировал cursor.execute("CREATE DATABASE ...строку и заменил ее passтакtry блок не был пустым.
Потому что _destroy_test_dbя просто закомментировал cursor.execute("DROP DATABASE- мне не нужно было ничего заменять, потому что в блоке уже была другая команда (time.sleep(1) ) .
После этого мои тесты прошли нормально, хотя я настроил тестовую версию моей обычной базы данных отдельно.
Это, конечно, не лучшее решение, потому что оно сломается при обновлении Django, но у меня была локальная копия Django из-за использования virtualenv, поэтому, по крайней мере, я могу контролировать, когда / если я обновлюсь до более новой версии.
Другое решение, о котором не упоминалось: это было легко реализовать, потому что у меня уже есть несколько файлов настроек (для локальных / промежуточных / производственных), которые наследуются от base.py. Поэтому, в отличие от других людей, мне не пришлось перезаписывать БАЗЫ ДАННЫХ ['default'], поскольку БАЗЫ ДАННЫХ не установлены в base.py
SimpleTestCase все еще пытался подключиться к моей тестовой базе данных и выполнить миграции. Когда я создал файл config / settings / test.py, который ни для чего не устанавливал DATABASES, мои модульные тесты работали без него. Это позволило мне использовать модели с полями внешнего ключа и уникальных ограничений. (Обратный поиск внешнего ключа, который требует поиска в БД, не выполняется.)
(Django 2.0.6)
Фрагменты кода PS
PROJECT_ROOT_DIR/config/settings/test.py:from.baseimport*#other test settings#DATABASES = {# 'default': {# 'ENGINE': 'django.db.backends.sqlite3',# 'NAME': 'PROJECT_ROOT_DIR/db.sqlite3',# }#}
cli, run from PROJECT_ROOT_DIR:./manage.py test path.to.app.test --settings config.settings.test
path/to/app/test.py:from django.test importSimpleTestCasefrom.models import*#^assume models.py imports User and defines Classified and UpgradePriceclassTestCaseWorkingTest(SimpleTestCase):def test_case_working(self):self.assertTrue(True)def test_models_ok(self):
obj =UpgradePrice(title='test',price=1.00)self.assertEqual(obj.title,'test')def test_more_complex_model(self):
user =User(username='testuser',email='hi@hey.com')self.assertEqual(user.username,'testuser')def test_foreign_key(self):
user =User(username='testuser',email='hi@hey.com')
ad =Classified(user=user,headline='headline',body='body')self.assertEqual(ad.user.username,'testuser')#fails with error:def test_reverse_foreign_key(self):
user =User(username='testuser',email='hi@hey.com')
ad =Classified(user=user,headline='headline',body='body')print(user.classified_set.first())self.assertTrue(True)#throws exception and never gets here
При использовании средства запуска теста носа (django-носа) вы можете сделать что-то вроде этого:
my_project/lib/nodb_test_runner.py:
from django_nose importNoseTestSuiteRunnerclassNoDbTestRunner(NoseTestSuiteRunner):"""
A test runner to test without database creation/deletion
Used for integration tests
"""def setup_databases(self,**kwargs):passdef teardown_databases(self, old_config,**kwargs):pass
В вашем settings.pyвы можете указать там тестового раннера, т.е.
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
ИЛИ
Я хотел, чтобы он запускал только определенные тесты, поэтому я запускаю его так:
python manage.py test integration_tests/integration_*--noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner
Ответы:
Вы можете создать подкласс DjangoTestSuiteRunner и переопределить методы setup_databases и teardown_databases для передачи.
Создайте новый файл настроек и установите TEST_RUNNER на новый класс, который вы только что создали. Затем, когда вы запускаете свой тест, укажите новый файл настроек с флагом --settings.
Вот что я сделал:
Создайте собственный бегун тестового костюма, подобный этому:
Создайте собственные настройки:
Когда вы запускаете свои тесты, запустите его, как показано ниже, с флагом --settings, установленным для вашего нового файла настроек:
ОБНОВЛЕНИЕ: апрель / 2018
Начиная с Django 1.8, модуль был перемещен в .
django.test.simple.DjangoTestSuiteRunner
'django.test.runner.DiscoverRunner'
Для получения дополнительной информации ознакомьтесь с разделом официальной документации о пользовательских средствах запуска тестов.
источник
--testrunner
параметра.Обычно тесты в приложении можно разделить на две категории.
Django поддерживает как модульные, так и интеграционные тесты.
Модульные тесты не требуют установки и удаления базы данных, и мы должны унаследовать их от SimpleTestCase .
Для интеграционных тестовых случаев наследование от TestCase, в свою очередь, наследуется от TransactionTestCase, и он будет настраивать и разрушать базу данных перед запуском каждого теста.
Эта стратегия гарантирует, что база данных будет создана и уничтожена только для тестовых случаев, которые обращаются к базе данных, и поэтому тесты будут более эффективными.
источник
Из
django.test.simple
Так что переопределите
DiscoverRunner
вместоDjangoTestSuiteRunner
.Используйте так:
источник
Я решил унаследовать
django.test.runner.DiscoverRunner
этотrun_tests
метод и внести в него несколько дополнений .Мое первое добавление проверяет необходимость настройки базы данных и позволяет задействовать нормальную
setup_databases
функциональность, если требуется база данных. Мое второе добавление позволяет нормальномуteardown_databases
запускать, еслиsetup_databases
метод был разрешен.В моем коде предполагается, что любой TestCase, который наследуется от
django.test.TransactionTestCase
(и, следовательно,django.test.TestCase
) требует настройки базы данных. Я сделал это предположение, потому что документы Django говорят:https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.SimpleTestCase
MySite / скрипты / settings.py
Наконец, я добавил следующую строку в файл settings.py моего проекта.
MySite / settings.py
Теперь, когда выполняются только тесты, не зависящие от db, мой набор тестов работает на порядок быстрее! :)
источник
Обновлено: также см. Этот ответ для использования стороннего инструмента
pytest
.@ Сезар прав. После случайного запуска
./manage.py test --settings=no_db_settings
без указания имени приложения моя база данных разработки была уничтожена.Для более безопасного использования используйте то же самое
NoDbTestRunner
, но в сочетании со следующимmysite/no_db_settings.py
:Вам необходимо создать базу данных, вызываемую
_test_mysite_db
с помощью инструмента внешней базы данных. Затем выполните следующую команду, чтобы создать соответствующие таблицы:Если вы используете Юг, также выполните следующую команду:
ХОРОШО!
Теперь вы можете запускать модульные тесты невероятно быстро (и безопасно):
источник
В качестве альтернативы изменению ваших настроек, чтобы сделать NoDbTestRunner «безопасным», вот модифицированная версия NoDbTestRunner, которая закрывает текущее соединение с базой данных и удаляет информацию о соединении из настроек и объекта соединения. Работает для меня, протестируйте его в своей среде, прежде чем полагаться на него :)
источник
__getitem__
. Используйтеconnections._connections.default
для доступа к объекту.Другое решение - просто наследовать тестовый класс от
unittest.TestCase
любого из тестовых классов Django. Документы Django ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests ) содержат следующее предупреждение об этом:Однако, если ваш тест не использует базу данных, это предупреждение не должно вас беспокоить, и вы можете воспользоваться преимуществами отсутствия необходимости запускать каждый тестовый пример в транзакции.
источник
Вышеупомянутые решения тоже подходят. Но следующее решение также сократит время создания базы данных, если будет большее количество миграций. Во время модульного тестирования запуск syncdb вместо запуска всех южных миграций будет намного быстрее.
источник
Мой веб-хостинг позволяет создавать и удалять базы данных только из своего веб-интерфейса, поэтому при попытке запуска я получал сообщение об ошибке «Получена ошибка при создании тестовой базы данных: в разрешении отказано».
python manage.py test
.Я надеялся использовать параметр --keepdb для django-admin.py, но, похоже, он больше не поддерживается с Django 1.7.
В итоге я изменил код Django в ... / django / db / backends / creation.py, в частности, функции _create_test_db и _destroy_test_db.
Потому что
_create_test_db
я закомментировалcursor.execute("CREATE DATABASE ...
строку и заменил ееpass
такtry
блок не был пустым.Потому что
_destroy_test_db
я просто закомментировалcursor.execute("DROP DATABASE
- мне не нужно было ничего заменять, потому что в блоке уже была другая команда (time.sleep(1)
) .После этого мои тесты прошли нормально, хотя я настроил тестовую версию моей обычной базы данных отдельно.
Это, конечно, не лучшее решение, потому что оно сломается при обновлении Django, но у меня была локальная копия Django из-за использования virtualenv, поэтому, по крайней мере, я могу контролировать, когда / если я обновлюсь до более новой версии.
источник
Другое решение, о котором не упоминалось: это было легко реализовать, потому что у меня уже есть несколько файлов настроек (для локальных / промежуточных / производственных), которые наследуются от base.py. Поэтому, в отличие от других людей, мне не пришлось перезаписывать БАЗЫ ДАННЫХ ['default'], поскольку БАЗЫ ДАННЫХ не установлены в base.py
SimpleTestCase все еще пытался подключиться к моей тестовой базе данных и выполнить миграции. Когда я создал файл config / settings / test.py, который ни для чего не устанавливал DATABASES, мои модульные тесты работали без него. Это позволило мне использовать модели с полями внешнего ключа и уникальных ограничений. (Обратный поиск внешнего ключа, который требует поиска в БД, не выполняется.)
(Django 2.0.6)
Фрагменты кода PS
источник
При использовании средства запуска теста носа (django-носа) вы можете сделать что-то вроде этого:
my_project/lib/nodb_test_runner.py
:В вашем
settings.py
вы можете указать там тестового раннера, т.е.TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
ИЛИ
Я хотел, чтобы он запускал только определенные тесты, поэтому я запускаю его так:
источник