J'ai un module Python qui utilise la bibliothèque argparse. Comment puis-je écrire des tests pour cette section du code de base ?
Réponses
Trop de publicités?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')
"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())
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')
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.
- Remplissez votre liste d'arg en utilisant
sys.argv.append()
et ensuite appelerparse()
Vérifiez les résultats et répétez. - Appelez depuis un fichier batch/bash avec vos drapeaux et un drapeau dump args.
- 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.
- Réponses précédentes
- Plus de réponses