unitarias test pruebas data python django django-unittest django-nose

python - pruebas - Django Unit Testing tarda mucho tiempo en crear una base de datos de prueba



pruebas unitarias django (4)

Desde hace algún tiempo, las pruebas de mi unidad llevan más tiempo del esperado. He intentado depurarlo un par de veces sin mucho éxito, ya que los retrasos son antes de que mis pruebas comiencen a ejecutarse. Esto ha afectado mi capacidad para hacer algo cercano al desarrollo guiado por pruebas (quizás mis expectativas son demasiado altas), por lo que quiero ver si puedo solucionarlo de una vez por todas.

Cuando se ejecuta una prueba, hay un retraso de 70 a 80 segundos entre el inicio y el comienzo real de la prueba. Por ejemplo, si ejecuto una prueba para un módulo pequeño (usando time python manage.py test myapp ), obtengo

<... bunch of unimportant print messages I print from my settings> Creating test database for alias ''default''... ...... ---------------------------------------------------------------- Ran 6 tests in 2.161s OK Destroying test database for alias ''default''... real 1m21.612s user 1m17.170s sys 0m1.400s

Aproximadamente 1m18 de 1m: 21 están entre los

Creating test database for alias ''default''...

y el

.......

línea. En otras palabras, la prueba dura menos de 3 segundos, pero la inicialización de la base de datos parece estar tomando 1: 18min

Tengo alrededor de 30 aplicaciones, la mayoría con 1 a 3 modelos de base de datos, por lo que esto debería dar una idea del tamaño del proyecto. Uso SQLite para realizar pruebas unitarias y he implementado algunas de las mejoras sugeridas. No puedo publicar todo mi archivo de configuración, pero estoy feliz de agregar cualquier información que sea necesaria.

Yo uso un corredor

from django.test.runner import DiscoverRunner from django.conf import settings class ExcludeAppsTestSuiteRunner(DiscoverRunner): """Override the default django ''test'' command, exclude from testing apps which we know will fail.""" def run_tests(self, test_labels, extra_tests=None, **kwargs): if not test_labels: # No appnames specified on the command line, so we run all # tests, but remove those which we know are troublesome. test_labels = ( ''app1'', ''app2'', .... ) print (''Testing: '' + str(test_labels)) return super(ExcludeAppsTestSuiteRunner, self).run_tests( test_labels, extra_tests, **kwargs)

y en mi configuración:

TEST_RUNNER = ''config.test_runner.ExcludeAppsTestSuiteRunner''

También he intentado usar django-nose con django-nose-exclude

He leído mucho sobre cómo acelerar la prueba por sí mismos, pero no he encontrado ninguna pista sobre cómo optimizar o evitar la inicialización de la base de datos. He visto las sugerencias para tratar de no probar con la base de datos, pero no puedo o no saber cómo evitar eso por completo.

Por favor déjame saber si

  1. Esto es normal y esperado
  2. No se espera (y ojalá una solución o una ventaja sobre qué hacer)

Nuevamente, no necesito ayuda sobre cómo acelerar la prueba, sino la inicialización (o sobrecarga). Quiero que el ejemplo de arriba tome 10 segundos en lugar de 80 segundos.

Muchas gracias

Ejecuto la prueba (para una sola aplicación) con --verbose 3 y descubrí que todo esto está relacionado con las migraciones:

Rendering model states... DONE (40.500s) Applying authentication.0001_initial... OK (0.005s) Applying account.0001_initial... OK (0.022s) Applying account.0002_email_max_length... OK (0.016s) Applying contenttypes.0001_initial... OK (0.024s) Applying contenttypes.0002_remove_content_type_name... OK (0.048s) Applying s3video.0001_initial... OK (0.021s) Applying s3picture.0001_initial... OK (0.052s) ... Many more like this

Aplasté todas mis migraciones pero aun así lento.


Resumen

¡Usa pytest !

Operaciones

  1. pip install pytest-django
  2. pytest --nomigrations lugar de ./manage.py test

Resultado

  • ./manage.py test cuesta 2 min 11.86 seg.
  • pytest --nomigrations cuesta 2.18 seg.

Consejos

  • Puede crear un archivo llamado pytest.ini en el directorio raíz de su proyecto y especificar las opciones de línea de comandos predeterminadas y / o la configuración de Django allí.

    # content of pytest.ini [pytest] addopts = --nomigrations DJANGO_SETTINGS_MODULE = yourproject.settings

    Ahora simplemente puede ejecutar pruebas con pytest y ahorrarle un poco de escritura.

  • Puede acelerar las pruebas posteriores aún más agregando --reuse-db a las opciones de línea de comando predeterminadas.

    [pytest] addopts = --nomigrations --reuse-db

    Sin embargo, tan pronto como se cambie el modelo de su base de datos, debe ejecutar pytest --create-db una vez para forzar la recreación de la base de datos de prueba .

  • Si necesita habilitar el parche gevent monkey durante la prueba, puede crear un archivo llamado pytest en el directorio raíz de su proyecto con el siguiente contenido, chmod +x pytest el bit de ejecución ( chmod +x pytest ) y ejecutar ./pytest para probar en lugar de pytest :

    #!/usr/bin/env python # -*- coding: utf-8 -*- # content of pytest from gevent import monkey monkey.patch_all() import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "yourproject.settings") from django.db import connection connection.allow_thread_sharing = True import re import sys from pytest import main if __name__ == ''__main__'': sys.argv[0] = re.sub(r''(-script/.pyw|/.exe)?$'', '''', sys.argv[0]) sys.exit(main())

    Puede crear un archivo test_gevent.py para probar si el parche gevent monkey es exitoso:

    # -*- coding: utf-8 -*- # content of test_gevent.py import time from django.test import TestCase from django.db import connection import gevent def f(n): cur = connection.cursor() cur.execute("SELECT SLEEP(%s)", (n,)) cur.execute("SELECT %s", (n,)) cur.fetchall() connection.close() class GeventTestCase(TestCase): longMessage = True def test_gevent_spawn(self): timer = time.time() d1, d2, d3 = 1, 2, 3 t1 = gevent.spawn(f, d1) t2 = gevent.spawn(f, d2) t3 = gevent.spawn(f, d3) gevent.joinall([t1, t2, t3]) cost = time.time() - timer self.assertAlmostEqual(cost, max(d1, d2, d3), delta=1.0, msg=''gevent spawn not working as expected'')

Referencias


Inicialización de la base de datos de hecho lleva demasiado tiempo ...

Tengo un proyecto con aproximadamente la misma cantidad de modelos / tablas (aproximadamente 77), y aproximadamente 350 pruebas y toma 1 minuto en total para ejecutar todo. Deving en una máquina errante con 2 cpus asignados y 2GB de ram. También utilizo py.test con el complemento pytest-xdist para ejecutar varias pruebas en paralelo.

Otra cosa que puedes hacer es decirle a django que reutilice la base de datos de prueba y que solo la vuelva a crear cuando tengas cambios de esquema. También puede usar SQLite para que las pruebas usen una base de datos en memoria. Ambos enfoques se explican aquí: https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database

EDITAR : En caso de que ninguna de las opciones anteriores funcione, una opción más es hacer que las pruebas unitarias se hereden de django SimpleTestCase o usar un corredor de prueba personalizado que no cree una base de datos como se explica en esta respuesta aquí: pruebas unitarias django sin db .

Luego puedes simular las llamadas de django a la base de datos utilizando una biblioteca como esta (que admití que escribí): https://github.com/stphivos/django-mock-queries

De esta manera, puede ejecutar sus pruebas unitarias de forma local y dejar que su servidor de CI se preocupe por ejecutar pruebas de integración que requieren una base de datos, antes de fusionar su código con una rama de desarrollo / maestro estable que no es la de producción.


La solución final que soluciona mi problema es forzar a Django a deshabilitar la migración durante las pruebas, lo que se puede hacer desde esta configuración

TESTING = len(sys.argv) > 1 and sys.argv[1] == ''test'' if TESTING: print(''========================='') print(''In TEST Mode - Disableling Migrations'') print(''========================='') class DisableMigrations(object): def __contains__(self, item): return True def __getitem__(self, item): return "notmigrations" MIGRATION_MODULES = DisableMigrations()

o utilice https://pypi.python.org/pypi/django-test-without-migrations

Ahora mi prueba completa toma alrededor de 1 minuto y una aplicación pequeña toma 5 segundos.

En mi caso, las migraciones no son necesarias para las pruebas, ya que actualizo las pruebas a medida que migro y no las utilizo para agregar datos. Esto no funcionará para todos