test - Pruebas de unidad django sin un DB
test post django (9)
Como alternativa a la modificación de su configuración para hacer que NoDbTestRunner sea "seguro", aquí hay una versión modificada de NoDbTestRunner que cierra la conexión actual de la base de datos 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
¿Existe la posibilidad de escribir django unittests sin configurar un db? Quiero probar la lógica empresarial que no requiere la configuración de db. Y aunque es rápido configurar un db, realmente no lo necesito en algunas situaciones.
De django.test.simple
warnings.warn(
"The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
"use django.test.runner.DiscoverRunner instead.",
RemovedInDjango18Warning)
Así que anule 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
Use así:
python manage.py test app --testrunner=app.filename.NoDbTestRunner
Elegí heredar de django.test.runner.DiscoverRunner
y hacer un par de adiciones al método run_tests
.
Mi primera adición comprueba si la configuración de un db es necesaria y permite que la funcionalidad normal setup_databases
funcionamiento si es necesario un db. Mi segunda adición permite que se ejecuten las setup_databases
normales de teardown_databases
si se permitió ejecutar el método setup_databases
.
Mi código asume que cualquier TestCase que herede 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 Django dicen:
Si necesita alguna de las otras funciones más complejas y pesadas específicas de Django, como ... Prueba o uso de ORM ... entonces debería usar TransactionTestCase o TestCase en su lugar.
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)
Finalmente, agregué la siguiente línea al archivo settings.py de mi proyecto.
mysite / settings.py
TEST_RUNNER = ''mysite.scripts.settings.MyDiscoverRunner''
Ahora, cuando ejecuto únicamente pruebas que no dependen de db, ¡mi suite de pruebas se ejecuta en un orden de magnitud más rápido! :)
Generalmente, las pruebas en una aplicación se pueden clasificar en dos categorías
- Pruebas unitarias: prueban los fragmentos individuales de código en la insolación y no requieren ir a la base de datos
- Casos de prueba de integración que van a la base de datos y prueban la lógica completamente integrada.
Django es compatible con pruebas de unidad y de integración.
Las pruebas unitarias, no requieren configurar y desmontar la base de datos, y debemos heredarlas de SimpleTestCase.
from django.test import SimpleTestCase
class ExampleUnitTest(SimpleTestCase):
def test_something_works(self):
self.assertTrue(True)
Para casos de prueba de integración heredadas de TestCase 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 creada y destruida solo para los casos de prueba que acceden a la base de datos y, por lo tanto, las pruebas serán más eficientes.
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 cantidad de 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 eliminar bases de datos desde su GUI web, por lo que recibí el error "Obtuve un error al crear la base de datos de prueba: Permiso denegado" al intentar ejecutar la python manage.py test
.
Esperaba usar la opción --keepdb para django-admin.py pero ya no parece ser compatible 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.
Para _create_test_db
comenté el cursor.execute("CREATE DATABASE ...
y lo reemplacé con pass
para que el bloque try
no esté vacío.
Para _destroy_test_db
acabo de comentar cursor.execute("DROP DATABASE
- No necesité 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 de prueba de mi base de datos regular por separado.
Esta no es una gran solución, por supuesto, porque se romperá si Django se actualiza, pero tuve una copia local de Django debido al uso de virtualenv por lo que al menos tengo control sobre cuándo / si actualizo a una versión más nueva.
Otra solución sería que su clase de prueba simplemente herede de 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 sobre esto:
El uso de unittest.TestCase evita el costo de ejecutar cada prueba en una transacción y purgar la base de datos, pero si las pruebas interactúan con la base de datos, su comportamiento variará en función del orden en que el examinador 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 debe preocuparlo y puede aprovechar los beneficios de no tener que ejecutar cada caso de prueba en una transacción.
Puede subclase DjangoTestSuiteRunner y anular los métodos setup_databases y teardown_databases para aprobar.
Cree un nuevo archivo de configuración y establezca TEST_RUNNER en la nueva clase que acaba de crear. Luego, cuando esté ejecutando su prueba, especifique su nuevo archivo de configuración con la bandera --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 django.test.simple.DjangoTestSuiteRunner
se movió a ''django.test.runner.DiscoverRunner''
.
Para obtener más información, consulte la sección oficial de doc. Sobre los corredores de prueba personalizados.
Actualizado: también vea esta respuesta para usar una herramienta pytest
.
@Cesar tiene razón. Después de ejecutar 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 manera más segura, use el mismo NoDbTestRunner
, pero junto con el 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. A continuación, 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
¡DE ACUERDO!
Ahora puede ejecutar pruebas unitarias increíblemente rápidas (y seguras) de la siguiente manera:
./manage.py test myapp --settings=mysite.no_db_settings