104 votes

Comment définir sys.argv pour que je puisse le tester unitairement?

Je voudrais définir

sys.argv

afin de pouvoir tester unitairement en passant différentes combinaisons. Ce qui suit ne fonctionne pas:

#!/usr/bin/env python
import argparse, sys

def test_parse_args():
    global sys.argv
    sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"]
    setup = get_setup_file()
    assert setup == "/home/fenton/project/setup.py"

def get_setup_file():
    parser = argparse.ArgumentParser()
    parser.add_argument('-f')
    args = parser.parse_args()
    return args.file

if __name__ == '__main__':
    test_parse_args()

Ensuite, exécutez le fichier:

pscripts % ./test.py                                                                                           
  File "./test.py", line 4
    global sys.argv
              ^
SyntaxError: invalid syntax
pscripts %

131voto

Jason Antman Points 370

Changer sys.argv à l'exécution est une méthode assez fragile pour tester. Vous devriez utiliser la fonctionnalité patch de mock, qui peut être utilisée comme un gestionnaire de contexte pour substituer un objet (ou attribut, méthode, fonction, etc.) par un autre, dans un bloc de code donné.

L'exemple suivant utilise patch() pour "remplacer" efficacement sys.argv avec la valeur de retour spécifiée (testargs).

try:
    # python 3.4+ should use builtin unittest.mock not mock package
    from unittest.mock import patch
except ImportError:
    from mock import patch

def test_parse_args():
    testargs = ["prog", "-f", "/home/fenton/project/setup.py"]
    with patch.object(sys, 'argv', testargs):
        setup = get_setup_file()
        assert setup == "/home/fenton/project/setup.py"

21voto

hpaulj Points 6132

test_argparse.py, le fichier de test officiel de argparse, utilise plusieurs moyens de définir/utiliser argv:

parser.parse_args(args)

args est une liste de 'mots', par exemple ['--foo','test'] ou --foo test'.split().

old_sys_argv = sys.argv
sys.argv = [old_sys_argv[0]] + args
try:
    return parser.parse_args()
finally:
    sys.argv = old_sys_argv

Cela ajoute les arguments sur sys.argv.

Je viens de tomber sur un cas (en utilisant mutually_exclusive_groups) où ['--foo','test'] produit un comportement différent que '--foo test'.split(). C'est un point subtil impliquant l'id des chaînes comme test.

10voto

icktoofay Points 60218

global expose uniquement les variables globales dans votre module, et sys.argv est dans sys, pas dans votre module. Au lieu d'utiliser global sys.argv, utilisez import sys.

Vous pouvez éviter de devoir changer sys.argv tout simplement : laissez simplement get_setup_file prendre facultativement une liste d'arguments (par défaut à None) et passez cela à parse_args. Lorsque get_setup_file est appelé sans arguments, cet argument sera None, et parse_argssys.argv. Lorsqu'il est appelé avec une liste, elle sera utilisée comme arguments du programme.

7voto

xjcl Points 608

J'aime utiliser unittest.mock.patch(). La différence avec patch.object() est que vous n'avez pas besoin d'une référence directe à l'objet que vous voulez patcher mais utilisez une chaîne.

from unittest.mock import patch

with patch("sys.argv", ["file.py", "-h"]):
    print(sys.argv)

1voto

sigmavirus24 Points 4768

Cela ne fonctionne pas car vous n'appelez pas réellement get_setup_file. Votre code devrait être :

import argparse

def test_parse_args():
    sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"]
    setup = get_setup_file()  # << Vous avez besoin des parenthèses
    assert setup == "/home/fenton/project/setup.py"

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