¿Cómo puedo llamar a un comando personalizado manage.py de Django directamente desde un controlador de prueba?


178

Quiero escribir una prueba unitaria para un comando de Django manage.py que realiza una operación de backend en una tabla de base de datos. ¿Cómo invocaría el comando de administración directamente desde el código?

No quiero ejecutar el comando en el shell del sistema operativo desde tests.py porque no puedo usar el entorno de prueba configurado usando la prueba manage.py (prueba de base de datos, prueba de bandeja de salida de correo electrónico ficticio, etc.)

Respuestas:


320

La mejor manera de probar tales cosas: extraer la funcionalidad necesaria del comando en sí a una función o clase independiente. Ayuda a abstraerse de "cosas de ejecución de comandos" y escribir pruebas sin requisitos adicionales.

Pero si por alguna razón no puede desacoplar el comando de formulario lógico, puede llamarlo desde cualquier código usando el método call_command como este:

from django.core.management import call_command

call_command('my_command', 'foo', bar='baz')

19
+1 para poner la lógica comprobable en otro lugar (¿método de modelo? ¿Método de administrador? ¿Función independiente?) Para que no tenga que meterse con la maquinaria call_command. También hace que la funcionalidad sea más fácil de reutilizar.
Carl Meyer

37
Incluso si extrae la lógica, esta función sigue siendo útil para probar el comportamiento específico de su comando, como los argumentos requeridos, y para asegurarse de que llama a la función de su biblioteca que hace el trabajo real.
Igor Sobreira

El párrafo inicial se aplica a cualquier situación límite. Saque su propio código de lógica de negocios fuera del código que está limitado a interactuar con algo, como un usuario. Sin embargo, si escribe la línea de código, podría tener un error, por lo que las pruebas deberían llegar más allá de cualquier límite.
Phlip

Creo que esto sigue siendo útil para algo como call_command('check'), para asegurarse de que las comprobaciones del sistema estén pasando en una prueba.
Adam Barnes

22

En lugar de hacer el truco call_command, puede ejecutar su tarea haciendo:

from myapp.management.commands import my_management_task
cmd = my_management_task.Command()
opts = {} # kwargs for your command -- lets you override stuff for testing...
cmd.handle_noargs(**opts)

10
¿Por qué haría esto cuando call_command también permite capturar stdin, stdout, stderr? ¿Y cuando la documentación especifica la forma correcta de hacer esto?
Boatcoder

17
Esa es una muy buena pregunta. Hace tres años, tal vez hubiera tenido una respuesta para ti;)
Nate

1
Ditto Nate - cuando su respuesta fue lo que encontré hace un año y medio - simplemente construí sobre eso ...
Danny Staple

2
Después de la excavación, pero hoy esto me ayudó: no siempre estoy usando todas las aplicaciones de mi base de código (dependiendo del sitio de Django utilizado), y call_commandnecesito que se cargue la aplicación probada INSTALLED_APPS. Entre tener que cargar la aplicación solo con fines de prueba y usar esto, elegí esto.
Mickaël

call_commandes probablemente lo que la mayoría de la gente debería probar primero. Esta respuesta me ayudó a solucionar un problema en el que necesitaba pasar nombres de tablas Unicode al inspectdbcomando. python / bash estaban interpretando los argumentos de la línea de comandos como ascii, y eso estaba bombardeando la get_table_descriptionllamada en profundidad en django.
bigh_29

18

el siguiente código:

from django.core.management import call_command
call_command('collectstatic', verbosity=3, interactive=False)
call_command('migrate', 'myapp', verbosity=3, interactive=False)

... es igual a los siguientes comandos escritos en la terminal:

$ ./manage.py collectstatic --noinput -v 3
$ ./manage.py migrate myapp --noinput -v 3

Consulte ejecutar comandos de administración de los documentos de django .


14

La documentación de Django sobre call_command no menciona que se outdebe redirigir a sys.stdout. El código de ejemplo debería leer:

from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
import sys

class ClosepollTest(TestCase):
    def test_command_output(self):
        out = StringIO()
        sys.stdout = out
        call_command('closepoll', stdout=out)
        self.assertIn('Expected output', out.getvalue())

1

Sobre la base de la respuesta de Nate, tengo esto:

def make_test_wrapper_for(command_module):
    def _run_cmd_with(*args):
        """Run the possibly_add_alert command with the supplied arguments"""
        cmd = command_module.Command()
        (opts, args) = OptionParser(option_list=cmd.option_list).parse_args(list(args))
        cmd.handle(*args, **vars(opts))
    return _run_cmd_with

Uso:

from myapp.management import mycommand
cmd_runner = make_test_wrapper_for(mycommand)
cmd_runner("foo", "bar")

La ventaja aquí es que si ha utilizado opciones adicionales y OptParse, esto lo resolverá por usted. No es del todo perfecto, y aún no canaliza los resultados, pero usará la base de datos de prueba. A continuación, puede probar los efectos de la base de datos.

Estoy seguro de que el uso del módulo simulado de Micheal Foords y también el recableado de la salida estándar durante la duración de una prueba significaría que también podría sacar más provecho de esta técnica: probar la salida, las condiciones de salida, etc.


4
¿Por qué te tomarías todo este problema en lugar de simplemente usar call_command?
Boatcoder
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.