web-dev-qa-db-fra.com

Comment appeler une commande personnalisée Django manage.py directement à partir d'un pilote de test?

Je souhaite écrire un test unitaire pour une commande Django manage.py qui effectue une opération d’arrière-plan sur une table de base de données. Comment puis-je appeler la commande de gestion directement à partir du code?

Je ne souhaite pas exécuter la commande sur le shell du système d'exploitation à partir de tests.py car je ne peux pas utiliser l'environnement de test configuré à l'aide de manage.py test (base de données de test, boîte d'envoi de courrier factice de test, etc.).

158
MikeN

La meilleure façon de tester de telles choses - extraire la fonctionnalité nécessaire de la commande elle-même vers une fonction ou une classe autonome. Il est utile d’abstraire du "matériel d’exécution de commande" et d’écrire le test sans exigences supplémentaires.

Mais si, pour une raison quelconque, vous ne pouvez pas découpler une commande de formulaire logique, vous pouvez l’appeler depuis n’importe quel code en utilisant la méthode call_command comme ceci:

from Django.core.management import call_command

call_command('my_command', 'foo', bar='baz')
287
Alex Koshelev

Plutôt que de faire appel à astuce call_command, vous pouvez exécuter votre tâche en procédant comme suit:

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)
21
Nate

le code suivant:

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

... est égal aux commandes suivantes tapées dans terminal:

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

Voir commandes de gestion en cours d'exécution à partir de Django docs .

16
Artur Barseghyan

Le documentation Django sur la call_command ne mentionne pas que out doit être redirigé vers sys.stdout. L'exemple de code devrait se lire comme suit:

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())
10
Alan Viars

En me basant sur la réponse de Nate, j'ai ceci:

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

Usage:

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

L'avantage ici est que si vous avez utilisé des options supplémentaires et OptParse, cela vous aidera. Ce n'est pas tout à fait parfait - et il ne filme pas encore les sorties - mais il utilisera la base de données de test. Vous pouvez ensuite tester les effets de base de données.

Je suis sûr que l’utilisation du module fictif Micheal Foords et le recâblage stdout pour la durée d’un test signifieraient que vous pourriez également tirer davantage parti de cette technique - testez la sortie, les conditions de sortie, etc.

1
Danny Staple