10 ответов:
вы можете подкласс DjangoTestSuiteRunner и переопределить setup_databases и teardown_databases методы для передачи.
создайте новый файл настроек и установите TEST_RUNNER в новый класс, который вы только что создали. Затем, когда вы запускаете свой тест, укажите свой новый файл настроек с флагом --settings.
вот что я сделал:
создать пользовательский тестовый костюм бегун похож на это:
from django.test.simple import DjangoTestSuiteRunner class NoDbTestRunner(DjangoTestSuiteRunner): """ A test runner to test without database creation """ def setup_databases(self, **kwargs): """ Override the database creation defined in parent class """ pass def 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'.более подробная информация официальный док раздел о пользовательских тестовых бегунов.
Как правило, тесты в приложении можно разделить на две категории
- модульные тесты, они проверяют отдельные фрагменты кода в инсоляции и не требуют перехода в базу данных
- интеграционные тестовые случаи, которые на самом деле идут в базу данных и проверить полностью интегрированную логику.
Django поддерживает как модульные, так и интеграционные тесты.
модульные тесты, не требуют установки и срывают базу данных и эти мы должен наследовать от SimpleTestCase.
from django.test import SimpleTestCase class ExampleUnitTest(SimpleTestCase): def test_something_works(self): self.assertTrue(True)для интеграционных тестовых случаев наследование от TestCase в свою очередь наследует от TransactionTestCase, и он будет устанавливать и разрывать базу данных перед запуском каждого теста.
from django.test import TestCase class ExampleIntegrationTest(TestCase): def test_something_works(self): #do something with database self.assertTrue(True)эта стратегия гарантирует, что база данных в созданных и уничтоженных только для тестовых случаев, которые обращаются к базе данных и, следовательно, тесты будут более эффективными
С
django.test.simplewarnings.warn( "The django.test.simple module and DjangoTestSuiteRunner are deprecated; " "use django.test.runner.DiscoverRunner instead.", RemovedInDjango18Warning)так переопределить
DiscoverRunnerвместоDjangoTestSuiteRunner.from django.test.runner import DiscoverRunner class NoDbTestRunner(DiscoverRunner): """ A test runner to test without database creation/deletion """ def setup_databases(self, **kwargs): pass def 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, такие как ... Тестирование или использование ОРМ ... затем вы должны использовать TransactionTestCase или TestCase вместо этого.
https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.SimpleTestCase
mysite/scripts/settings.py
from django.test import TransactionTestCase from django.test.runner import DiscoverRunner class MyDiscoverRunner(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 = None if 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() return self.suite_result(suite, result)наконец, я добавил следующие линия к моему проекту settings.py файл.
mysite/settings.py
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'теперь, когда выполняются только тесты, не зависящие от БД, мой набор тестов работает на порядок быстрее! :)
обновление: см. Также ответ для использования стороннего инструмента
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использование внешнего инструмента базы данных. Затем запустите следующая команда для создания соответствующих таблиц:./manage.py syncdb --settings=mysite.no_db_settingsесли вы используете South, также выполните следующую команду:
./manage.py migrate --settings=mysite.no_db_settingsОК!
теперь вы можете запускать модульные тесты невероятно быстро (и безопасно):
./manage.py test myapp --settings=mysite.no_db_settings
в качестве альтернативы изменению настроек, чтобы сделать NoDbTestRunner "безопасным", вот модифицированная версия NoDbTestRunner, которая закрывает текущее соединение с базой данных и удаляет информацию о соединении из настроек и объекта подключения. Работает для меня, проверьте его в своей среде, прежде чем полагаться на него :)
class NoDbTestRunner(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 data from 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 """ pass def teardown_databases(self, old_config, **kwargs): """ Override the database teardown defined in parent class """ pass
другое решение было бы иметь ваш тестовый класс просто наследовать от
unittest.TestCaseвместо любого из тестовых классов Django. Документы Django (https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests) содержат следующее предупреждение об этом:использование unittest.TestCase позволяет избежать затрат на выполнение каждого теста в транзакции и очистку базы данных, но если ваши тесты взаимодействуют с базой данных, их поведение будет отличаться в зависимости от прикажите, чтобы тестовый бегун выполнял их. Это может привести к модульным тестам, которые проходят при запуске в изоляции, но терпят неудачу при запуске в комплекте.
однако, если ваш тест не использует базу данных, то это предупреждение не должно вас волновать, и вы можете пожинать плоды не запуска каждого теста в транзакции.
вышеуказанные решения тоже хороши. Но следующее решение также сократит время создания БД, если будет больше миграций. Во время модульного тестирования запуск syncdb вместо запуска всех миграций на юг будет намного быстрее.
SOUTH_TESTS_MIGRATE = False # для отключения миграции и использования syncdb вместо этого
мой веб-хост позволяет создавать и удалять базы данных только из своего веб-графического интерфейса, поэтому я получал ошибку" Got an error creating the test database: Permission denied " при попытке запустить
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)).после этого мои тесты прошли нормально - хотя я действительно настроил test_ версию моей обычной базы данных отдельно.
это не отличное решение конечно, потому что он сломается, если Django будет обновлен, но у меня была локальная копия Django из-за использования virtualenv, поэтому, по крайней мере, у меня есть контроль над тем, когда/если я обновлюсь до более новой версии.
другое решение не упоминается: это было легко для меня реализовать, потому что у меня уже есть несколько файлов настроек (для локального / промежуточного / производственного), которые наследуют от base.py . Так что в отличие от других людей мне не пришлось перезаписывать базы данных ['default'], так как базы данных не установлены base.py
SimpleTestCase все еще пытался подключиться к моей тестовой базе данных и запустить миграции. Когда я сделал a config/settings/test.py файл, который не устанавливал базы данных ни на что, затем мои модульные тесты выполнялись без него. Это позволило мне использовать модели, которые имели внешний ключ и уникальные поля ограничений. (Обратный поиск внешнего ключа, который требует поиска БД, завершается неудачей.)
(Django 2.0.6)
фрагменты кода PS
PROJECT_ROOT_DIR/config/settings/test.py: from .base import * #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 import SimpleTestCase from .models import * #^assume models.py imports User and defines Classified and UpgradePrice class TestCaseWorkingTest(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='[email protected]') self.assertEqual(user.username,'testuser') def test_foreign_key(self): user = User(username='testuser',email='[email protected]') 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='[email protected]') ad = Classified(user=user,headline='headline',body='body') print(user.classified_set.first()) self.assertTrue(True) #throws exception and never gets here
Comments