¿Existe la posibilidad de escribir django unittests sin configurar un db? Quiero probar la lógica de negocios que no requiere la configuración de db. Y aunque es rápido configurar un db, realmente no lo necesito en algunas situaciones.
¿Existe la posibilidad de escribir django unittests sin configurar un db? Quiero probar la lógica de negocios que no requiere la configuración de db. Y aunque es rápido configurar un db, realmente no lo necesito en algunas situaciones.
Respuestas:
Puede subclasificar DjangoTestSuiteRunner y anular los métodos setup_databases y teardown_databases para pasar.
Cree un nuevo archivo de configuración y configure TEST_RUNNER en la nueva clase que acaba de crear. Luego, cuando esté ejecutando su prueba, especifique su nuevo archivo de configuración con el indicador --settings.
Aquí esta lo que hice:
Cree un corredor de traje de prueba personalizado similar a este:
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
Crea una configuración personalizada:
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
Cuando esté ejecutando sus pruebas, ejecútelo de la siguiente manera con el indicador --settings establecido en su nuevo archivo de configuración:
python manage.py test myapp --settings='no_db_settings'
ACTUALIZACIÓN: abril / 2018
Desde Django 1.8, el módulo se movió a .django.test.simple.DjangoTestSuiteRunner
'django.test.runner.DiscoverRunner'
Para obtener más información, consulte la sección de documentación oficial sobre corredores de prueba personalizados.
--testrunner
opción.
Generalmente las pruebas en una aplicación se pueden clasificar en dos categorías
Django admite pruebas unitarias y de integración.
Las pruebas unitarias no requieren la configuración y el desmantelamiento de la base de datos, que debemos heredar de SimpleTestCase .
from django.test import SimpleTestCase
class ExampleUnitTest(SimpleTestCase):
def test_something_works(self):
self.assertTrue(True)
Para los casos de prueba de integración, heredar de TestCase a su vez hereda de TransactionTestCase y configurará y destruirá la base de datos antes de ejecutar cada prueba.
from django.test import TestCase
class ExampleIntegrationTest(TestCase):
def test_something_works(self):
#do something with database
self.assertTrue(True)
Esta estrategia asegurará que la base de datos se cree y destruya solo para los casos de prueba que acceden a la base de datos y, por lo tanto, las pruebas serán más eficientes
De django.test.simple
warnings.warn(
"The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
"use django.test.runner.DiscoverRunner instead.",
RemovedInDjango18Warning)
Entonces anular en DiscoverRunner
lugar de 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
Usar así:
python manage.py test app --testrunner=app.filename.NoDbTestRunner
Elegí heredar django.test.runner.DiscoverRunner
y hacer un par de adiciones al run_tests
método.
Mi primera adición verifica si es necesario configurar una base de datos y permite que la setup_databases
funcionalidad normal se active si es necesaria una base de datos. Mi segunda adición permite teardown_databases
que se ejecute lo normal si setup_databases
se permitió la ejecución del método.
Mi código asume que cualquier TestCase que hereda de django.test.TransactionTestCase
(y por lo tanto django.test.TestCase
) requiere que se configure una base de datos. Hice esta suposición porque los documentos de Django dicen:
Si necesita cualquiera de las otras características específicas de Django más complejas y pesadas como ... Probar o usar el ORM ... entonces debería usar TransactionTestCase o TestCase en su lugar.
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)
Finalmente, agregué la siguiente línea al archivo settings.py de mi proyecto.
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
Ahora, cuando ejecuto solo pruebas no dependientes de db, ¡mi conjunto de pruebas ejecuta un orden de magnitud más rápido! :)
Actualizado: también vea esta respuesta para usar una herramienta de terceros pytest
.
@Cesar tiene razón. Después de ejecutarse accidentalmente ./manage.py test --settings=no_db_settings
, sin especificar el nombre de una aplicación, mi base de datos de desarrollo se borró.
Para una forma más segura, use lo mismo NoDbTestRunner
, pero junto con lo siguiente 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'
Necesita crear una base de datos llamada _test_mysite_db
usando una herramienta de base de datos externa. Luego ejecute el siguiente comando para crear las tablas correspondientes:
./manage.py syncdb --settings=mysite.no_db_settings
Si está utilizando South, también ejecute el siguiente comando:
./manage.py migrate --settings=mysite.no_db_settings
¡OKAY!
Ahora puede ejecutar pruebas unitarias increíblemente rápido (y seguro):
./manage.py test myapp --settings=mysite.no_db_settings
Como alternativa a la modificación de su configuración para que NoDbTestRunner sea "seguro", aquí hay una versión modificada de NoDbTestRunner que cierra la conexión de la base de datos actual y elimina la información de conexión de la configuración y el objeto de conexión. Funciona para mí, pruébalo en tu entorno antes de confiar en él :)
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__
. Úselo connections._connections.default
para acceder al objeto.
Otra solución sería tener su clase de prueba simplemente heredando en unittest.TestCase
lugar de cualquiera de las clases de prueba de Django. Los documentos de Django ( https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests ) contienen la siguiente advertencia al respecto:
El uso de unittest.TestCase evita el costo de ejecutar cada prueba en una transacción y vaciar la base de datos, pero si sus pruebas interactúan con la base de datos, su comportamiento variará en función del orden en que el corredor de pruebas las ejecute. Esto puede conducir a pruebas unitarias que pasan cuando se ejecutan de forma aislada pero fallan cuando se ejecutan en un conjunto.
Sin embargo, si su prueba no utiliza la base de datos, esta advertencia no tiene por qué preocuparle y puede obtener los beneficios de no tener que ejecutar cada caso de prueba en una transacción.
Las soluciones anteriores también están bien. Pero la siguiente solución también reducirá el tiempo de creación de db si hay más migraciones. Durante las pruebas unitarias, ejecutar syncdb en lugar de ejecutar todas las migraciones hacia el sur será mucho más rápido.
SOUTH_TESTS_MIGRATE = False # Para deshabilitar las migraciones y usar syncdb en su lugar
Mi servidor web solo permite crear y descartar bases de datos desde su GUI web, por lo que recibí un error "Tengo un error al crear la base de datos de prueba: Permiso denegado" al intentar ejecutar python manage.py test
.
Esperaba usar la opción --keepdb para django-admin.py, pero parece que ya no se admite a partir de Django 1.7.
Lo que terminé haciendo fue modificar el código de Django en ... / django / db / backends / creation.py, específicamente las funciones _create_test_db y _destroy_test_db.
Porque _create_test_db
comenté la cursor.execute("CREATE DATABASE ...
línea y la reemplacé pass
para que el try
bloque no estuviera vacío.
Para _destroy_test_db
acabo comentada cursor.execute("DROP DATABASE
- Yo no tenía necesidad de reemplazarlo con nada porque ya había otro comando en el bloque ( time.sleep(1)
).
Después de eso, mis pruebas funcionaron bien, aunque configuré una versión test_ de mi base de datos regular por separado.
Por supuesto, esta no es una gran solución, porque se romperá si se actualiza Django, pero tenía una copia local de Django debido al uso de virtualenv, así que al menos tengo control sobre cuándo / si me actualizo a una versión más nueva.
Otra solución no mencionada: esto fue fácil de implementar porque ya tengo múltiples archivos de configuración (para local / puesta en escena / producción) que heredan de base.py. Así que, a diferencia de otras personas, no tuve que sobrescribir BASES DE DATOS ['predeterminado'], ya que BASES DE DATOS no está configurado en base.py
SimpleTestCase todavía intentó conectarse a mi base de datos de prueba y ejecutar migraciones. Cuando hice un archivo config / settings / test.py que no configuró BASES DE DATOS para nada, entonces las pruebas de mi unidad se ejecutaron sin él. Me permitió usar modelos que tenían clave externa y campos de restricción únicos. (La búsqueda de clave externa inversa, que requiere una búsqueda de db, falla).
(Django 2.0.6)
Fragmentos de código 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
Al usar el corredor de prueba de nariz (django-nose), puede hacer algo como esto:
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
En su settings.py
puede especificar el corredor de prueba allí, es decir
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
O
Lo quería solo para ejecutar pruebas específicas, así que lo ejecuto así:
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner