¿Cómo configuro sys.argv para que pueda probarlo por unidad?

Me gustaria establecer

sys.argv 

Así que puedo hacer una prueba de unidad pasando en diferentes combinaciones. Lo siguiente no funciona:

 #!/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() 

Luego ejecutando el archivo:

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

Cambiar sys.argv en tiempo de ejecución es una forma de prueba bastante frágil. Debe usar la funcionalidad de parches de mock , que se puede usar como administrador de contexto para sustituir un objeto (o atributo, método, función, etc.) con otro, dentro de un bloque de código dado.

El siguiente ejemplo utiliza patch() para “reemplazar” sys.argv con el valor de retorno especificado ( 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" 

global solo expone las variables globales dentro de su módulo, y sys.argv está en sys , no en su módulo. En lugar de usar global sys.argv , use import sys .

sys.argv embargo, puede evitar tener que cambiar sys.argv en absoluto: simplemente deje que get_setup_file opcionalmente tome una lista de argumentos (por defecto a None ) y pase eso a parse_args . Cuando se llama a get_setup_file sin argumentos, ese argumento será None , y parse_args volverá a sys.argv . Cuando se llama con una lista, se utilizará como argumentos del progtwig.

test_argparse.py , el archivo oficial de argparse la argparse , usa varios medios para configurar / usar argv :

 parser.parse_args(args) 

donde args es una lista de ‘palabras’, por ejemplo, ['--foo','test'] o --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 

Esto empuja los sys.argv en sys.argv .

Acabo de encontrar un caso (utilizando ['--foo','test'] mutually_exclusive_groups ) donde ['--foo','test'] produce un comportamiento diferente al de '--foo test'.split() . Es un punto sutil que involucra la id de cadenas como test .

No funciona porque en realidad no estás llamando a get_setup_file . Su código debe leer:

 import argparse def test_parse_args(): sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"] setup = get_setup_file() # << You need the parentheses assert setup == "/home/fenton/project/setup.py" 

Muy buena pregunta.

El truco para configurar pruebas unitarias consiste en hacerlas repetibles. Esto significa que tiene que eliminar las variables, para que las pruebas sean repetibles. Por ejemplo, si está probando una función que debe funcionar correctamente dada la fecha actual, entonces obligue a que funcione para fechas específicas, donde la fecha elegida no importa, pero las fechas elegidas coinciden en tipo y rango con las reales.

Aquí sys.argv será una lista de longitud al menos una. Así que crea un “fakemain” que sea llamado con una lista. A continuación, compruebe las diversas longitudes probables y los contenidos. Luego puede llamar a su principal falso desde el que pasa sys.argv, sabiendo que fakemain funciona, o alterar la parte “if name …” para realizar la función normal en condiciones de prueba no unitarias.

Normalmente tendrás argumentos de comando. Necesitas probarlos. Aquí es cómo hacer una prueba de unidad.

  • Supongamos que el progtwig se puede ejecutar como: % myprogram -f setup.py

  • Creamos una lista para imitar este comportamiento. Ver linea (4)

  • Luego, nuestro método que analiza args, toma una matriz como un argumento que está predeterminado a None . Ver linea (7)
  • Luego, en la línea (11), pasamos esto a parse_args , que usa la matriz si no es None . Si es None entonces por defecto usa sys.argv .
     1: #! / Usr / bin / env python
     2: importar argparse
     3: def test_parse_args ():
     4: my_argv = ["-f", "setup.py"]
     5: setup = get_setup_file (my_argv)
     6: valida la configuración == "setup.py"
     7: def get_setup_file (argv = None):
     8: analizador = argparse.ArgumentParser ()
     9: parser.add_argument ('- f')
     10: # si argv es 'Ninguno', entonces será predeterminado ver 'sys.argv'        
     11: args = parser.parse_args (argv) 
     12: devuelve args.f
     13: si __name__ == '__main__':
     14: test_parse_args ()

Puede adjuntar una envoltura alrededor de su función, que prepara sys.argv antes de llamar y la restaura al salir:

 def run_with_sysargv(func, sys_argv): """ prepare the call with given sys_argv and cleanup afterwards. """ def patched_func(*args, **kwargs): old_sys_argv = list(sys.argv) sys.argv = list(sys_argv) try: return func(*args, **kwargs) except Exception, err: sys.argv = old_sys_argv raise err return patched_func 

Entonces simplemente puedes hacer

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

Debido a que los errores se pasan correctamente, no debe interferir con las instancias externas utilizando el código de prueba, como pytest .

Logré esto creando un administrador de ejecución que establecería los argumentos de mi elección y los eliminaría al salir:

 import sys class add_resume_flag(object): def __enter__(self): sys.argv.append('--resume') def __exit__(self, typ, value, traceback): sys.argv = [arg for arg in sys.argv if arg != '--resume'] class MyTestClass(unittest.TestCase): def test_something(self): with add_resume_flag(): ...