226 votes

Comment écrire des tests pour la partie argparse d'un module python ?

J'ai un module Python qui utilise la bibliothèque argparse. Comment puis-je écrire des tests pour cette section du code de base ?

314voto

Viktor Kerkez Points 11222

Vous devriez refactoriser votre code et déplacer le parsing vers une fonction :

def parse_args(args):
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser.parse_args(args)

Alors dans votre main vous devez juste l'appeler avec :

parser = parse_args(sys.argv[1:])

(où le premier élément de sys.argv qui représente le nom du script est supprimé pour ne pas l'envoyer comme commutateur supplémentaire pendant l'opération CLI).

Dans vos tests, vous pouvez ensuite appeler la fonction d'analyseur avec la liste d'arguments de votre choix :

def test_parser(self):
    parser = parse_args(['-l', '-m'])
    self.assertTrue(parser.long)
    # ...and so on.

Ainsi, vous n'aurez jamais à exécuter le code de votre application juste pour tester le parseur.

Si vous devez modifier et/ou ajouter des options à votre analyseur syntaxique plus tard dans votre application, créez une méthode d'usine :

def create_parser():
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser

Vous pouvez le manipuler ultérieurement si vous le souhaitez, et un test pourrait ressembler à ceci :

class ParserTest(unittest.TestCase):
    def setUp(self):
        self.parser = create_parser()

    def test_something(self):
        parsed = self.parser.parse_args(['--something', 'test'])
        self.assertEqual(parsed.something, 'test')

41voto

munsu Points 796

"portion argparse" est un peu vague, aussi cette réponse se concentre-t-elle sur une partie : la portion parse_args méthode. C'est la méthode qui interagit avec votre ligne de commande et récupère toutes les valeurs passées. En fait, vous pouvez simuler ce que parse_args afin qu'il n'ait pas besoin de récupérer les valeurs de la ligne de commande. Le site mock paquet peut être installé via pip pour les versions 2.6-3.2 de python. Il fait partie de la bibliothèque standard comme unittest.mock à partir de la version 3.3.

import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2

@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(kwarg1=value, kwarg2=value))
def test_command(mock_args):
    pass

Vous devez inclure tous les arguments de votre méthode de commande dans le fichier Namespace même s'ils ne sont pas adoptés. Donnez à ces args une valeur de None . (voir le docs ) Ce style est utile pour tester rapidement les cas où des valeurs différentes sont transmises pour chaque argument de la méthode. Si vous choisissez de simuler Namespace pour la non-utilisation totale d'argparse dans vos tests, assurez-vous qu'il se comporte de la même manière que la version réelle de Namespace classe.

Voici un exemple utilisant le premier extrait de la bibliothèque argparse.

# test_mock_argparse.py
import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2

def main():
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                        help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                        const=sum, default=max,
                        help='sum the integers (default: find the max)')

    args = parser.parse_args()
    print(args)  # NOTE: this is how you would check what the kwargs are if you're unsure
    return args.accumulate(args.integers)

@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(accumulate=sum, integers=[1,2,3]))
def test_command(mock_args):
    res = main()
    assert res == 6, "1 + 2 + 3 = 6"

if __name__ == "__main__":
    print(main())

27voto

Ceasar Bautista Points 1006

Faites votre main() fonction prendre argv comme un argument plutôt que de le laisser lire de sys.argv comme il le fera par défaut :

# mymodule.py
import argparse
import sys

def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('-a')
    process(**vars(parser.parse_args(args)))
    return 0

def process(a=None):
    pass

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

Ensuite, vous pouvez tester normalement.

import mock

from mymodule import main

@mock.patch('mymodule.process')
def test_main(process):
    main([])
    process.assert_call_once_with(a=None)

@mock.patch('foo.process')
def test_main_a(process):
    main(['-a', '1'])
    process.assert_call_once_with(a='1')

17voto

김민준 Points 21

Je ne voulais pas modifier le script de service original script, alors j'ai simplement simulé la sys.argv partie dans argparse.

from unittest.mock import patch

with patch('argparse._sys.argv', ['python', 'serve.py']):
    ...  # your test code here

Cela se casse si l'implémentation d'argparse change, mais c'est suffisant pour un test rapide script. La sensibilité est beaucoup plus importante que la spécificité dans les script de test de toute façon.

10voto

Steve Barnes Points 9505
  1. Remplissez votre liste d'arg en utilisant sys.argv.append() et ensuite appeler parse() Vérifiez les résultats et répétez.
  2. Appelez depuis un fichier batch/bash avec vos drapeaux et un drapeau dump args.
  3. Mettez toute votre analyse d'arguments dans un fichier séparé et dans le fichier if __name__ == "__main__": appelez parse et dump/évaluez les résultats puis testez ceci à partir d'un fichier batch/bash.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X