Чи є можливість записати django unittests без встановлення db? Я хочу перевірити ділову логіку, яка не вимагає налаштування db. І хоча це швидко налаштовує db, я справді не потребує цього в деяких ситуаціях.
Чи є можливість записати django unittests без встановлення db? Я хочу перевірити ділову логіку, яка не вимагає налаштування db. І хоча це швидко налаштовує db, я справді не потребує цього в деяких ситуаціях.
Відповіді:
Ви можете підкласифікувати 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'
Для отримання додаткової інформації перегляньте офіційний розділ документа про користувацькі тестові бігуни.
--testrunner
параметра.
Зазвичай тести в додатку можна класифікувати на дві категорії
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.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 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
метод.
Перше моє додавання перевіряє, чи потрібна настройка db, і чи дозволяє нормальна setup_databases
функціональність починати, якщо необхідний db. Моє друге доповнення дозволяє нормальному teardown_databases
запуску, якщо setup_databases
методу було дозволено запустити.
Мій код передбачає, що будь-який TestCase, який успадковує django.test.TransactionTestCase
(і таким чином django.test.TestCase
), потребує налаштування бази даних. Я зробив це припущення, оскільки документи Джанго кажуть:
Якщо вам потрібна будь-яка інша, більш складна і важка для Django функція, наприклад ... Тестування або використання ORM ..., тоді вам слід використовувати TransactionTestCase або TestCase.
https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.SimpleTestCase
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 мого проекту.
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
Тепер, використовуючи лише тести, не залежні від db, мій тестовий набір працює на порядок швидше! :)
Оновлено: також див. Цю відповідь щодо використання стороннього інструменту pytest
.
@Cesar має рацію. Після випадкового запуску ./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
Якщо ви використовуєте Південь, також запустіть таку команду:
./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
__getitem__
. Використовуйте connections._connections.default
для доступу до об'єкта.
Іншим рішенням буде те, щоб ваш тестовий клас просто успадкував unittest.TestCase
замість будь-якого з тестових класів Джанго. Документи Django ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests ) містять таке попередження про це:
Використання unittest.TestCase дозволяє уникнути витрат на виконання кожного тесту в транзакції та промивання бази даних, але якщо ваші тести взаємодіють із базою даних, їх поведінка буде змінюватися залежно від порядку, який виконує їх тестувальник. Це може призвести до одиничних тестів, які проходять при запуску в ізоляції, але провалюються під час запуску в наборі.
Однак якщо ваш тест не використовує базу даних, це попередження не повинно вас стосуватись, і ви можете скористатися перевагами від того, що не потрібно запускати кожен тестовий випадок в транзакції.
Вищезазначені рішення теж чудово. Але наступне рішення також скоротить час створення db, якщо буде більше міграцій. Під час тестування одиниць, запуск syncdb замість запуску всіх південних міграцій буде набагато швидшим.
SOUTH_TESTS_MIGRATE = False # Щоб відключити міграцію та використовувати замість цього syncdb
Мій веб-хост дозволяє лише створювати та видаляти бази даних з їх веб-інтерфейсу, тому я отримував помилку "Помилка створення тестової бази даних: Дозвіл відхилено" при спробі запуску python manage.py test
.
Я сподівався використовувати параметр --keepdb для django-admin.py, але, схоже, більше не підтримується Django 1.7.
Що я в кінцевому підсумку робив, - це змінити код Django у ... / django / db / backends / create.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. Отже, на відміну від інших людей, мені не довелося перезаписувати DATABASES ['default'], оскільки DATABASES не встановлено в base.py
SimpleTestCase все ж намагався підключитися до моєї тестової бази даних та запустити міграцію. Коли я створив файл config / settings / test.py, який нічого не встановлював DATABASES, тоді мої модульні тести проходили без нього. Це дозволило мені використовувати моделі, які мали зовнішній ключ та унікальні поля обмеження. (Зворотний пошук зовнішнього ключа, для якого потрібен пошук db, не вдається.)
(Джанго 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='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
Використовуючи нос-тест-бігун (джанго-ніс), ви можете зробити щось подібне:
my_project/lib/nodb_test_runner.py
:
from django_nose import NoseTestSuiteRunner
class NoDbTestRunner(NoseTestSuiteRunner):
"""
A test runner to test without database creation/deletion
Used for integration tests
"""
def setup_databases(self, **kwargs):
pass
def 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