Python unittest para argparse

Tengo una función dentro de un módulo que crea un argparse :

 def get_options(prog_version='1.0', prog_usage='', misc_opts=None): options = [] if misc_opts is None else misc_opts parser = ArgumentParser(usage=prog_usage) if prog_usage else ArgumentParser() parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(prog_version)) parser.add_argument('-c', '--config', dest='config', required=True, help='the path to the configuration file') for option in options: if 'option' in option and 'destination' in option: parser.add_argument(option['option'], dest=option.get('destination', ''), default=option.get('default', ''), help=option.get('description', ''), action=option.get('action', 'store')) return parser.parse_args() 

Un ejemplo de myapp.py sería:

 my_options = [ { "option": "-s", "destination": "remote_host", "default": "127.0.0.1", "description": "The remote server name or IP address", "action": "store" }, ] # Get Command Line Options options = get_options(misc_opts=my_options) print options.config print options.remote_host 

y esto se llamará como:

 $> python myapp.py -c config.yaml $> config.yaml 127.0.0.1 

Ahora, estoy tratando de crear una prueba de unidad para esta función, pero mi problema es que no puedo pasar los parámetros de la línea de comandos a través del código de prueba.

 # mytest.py import unittest from mymodule import get_options class argParseTestCase(unittest.TestCase): def test_parser(self): options = get_options() # ...pass the command line arguments... self.assertEquals('config.yaml', options.config) # ofcourse this fails because I don't know how I will pass the command line arguments 

Mi problema es que necesito pasar los argumentos de la línea de comandos a get_options() pero no sé cómo hacerlo correctamente.

Llamada correcta esperada: python mytest.py ( -c config.yaml debe pasarse dentro del código de prueba de alguna manera).

Lo que está “trabajando” / no está funcionando en este momento:

  1. python mytest.py -c config.yaml tampoco funciona. Devuelve AttributeError: 'module' object has no attribute 'config' ya que espera que llame a argParseTestCase en argParseTestCase lugar. En otras palabras, python mytest.py -c argParseTestCase “funciona” pero, por supuesto, sería un AssertionError: 'config.yaml' != 'argParseTestCase' devolución AssertionError: 'config.yaml' != 'argParseTestCase'
  2. python mytest.py -v para ejecutar la prueba de la unidad en modo detallado también falla. Vuelve:

    test_parser ( main .argParseTestCase) … mytest.py 1.0 ERROR ERROR: test_parser ( main .argParseTestCase)
    Seguimiento (última llamada más reciente): Archivo “tests / unit_tests / mytest.py”, línea 376, en test_parser options = get_options () Archivo “/root/test/lib/python2.7/site-packages/mymodule.py” , linea 61, en get_options devuelve parser.parse_args ()
    Archivo “/usr/local/lib/python2.7/argparse.py”, línea 1701, en parse_args args, argv = self.parse_known_args (args, namespace)
    Archivo “/usr/local/lib/python2.7/argparse.py”, línea 1733, en el espacio de nombres parse_known_args, args = self._parse_known_args (args, namespace)
    Archivo “/usr/local/lib/python2.7/argparse.py”, línea 1939, en _parse_known_args start_index = consume_optional (start_index)
    Archivo “/usr/local/lib/python2.7/argparse.py”, línea 1879, en consumer_optional take_action (action, args, option_string)
    Archivo “/usr/local/lib/python2.7/argparse.py”, línea 1807, en acción de toma de acción (yo, espacio de nombres, valores de argumento, cadenas de opciones)
    Archivo “/usr/local/lib/python2.7/argparse.py”, línea 1022, en la llamada parser.exit (mensaje = formatter.format_help ())
    Archivo “/usr/local/lib/python2.7/argparse.py”, línea 2362, en la salida _sys.exit (estado) SystemExit: 0

Su stack de mensajes de error es difícil de leer porque está en forma de cita en lugar de código. Pero creo que el argumento -v está produciendo un sys.exit . version es como help : se supone que muestra un mensaje y luego sale. La -v es utilizada por unittest , pero también es leída por su analizador.

Hay un módulo argparse unittest, test/test_argparse.py . Puede que necesite una instalación de Python de desarrollo para ver eso. Algunas pruebas son sencillas, otras utilizan una estructura de pruebas especializada. Parte de ese código especial crea argumentos de la misma manera que lo hace con las options .

Son dos temas especiales:

  • generando la entrada. parse_args usa sys.argv[1:] menos que su parámetro argv no sea None . Por lo tanto, puede probar un analizador modificando la lista sys.argv ( unittest ya usó sus valores de línea de comando), o pasando un argumento de palabra clave argv=None a su función y a parse_args . Tratar de hacer una línea de comandos para que el código de get_options funcione con get_options es demasiado complicado.

  • Atrapando la salida, especialmente el sys.exit generado por errores. Una opción es subclase ArgumentParser y asignarle un error y / o método de exit . Otra es envolver la llamada de función en un bloque try .

unittest toma el argumento -c , pero con una syntax y un significado diferentes

  -c, --catch Catch control-C and display results 

y -v es verbose , no version .

=============

Esto prueba el argumento de config (en un formulario de archivo autónomo)

 import unittest import sys #from mymodule import get_options def get_options(argv=None, prog_version='1.0', prog_usage='', misc_opts=None): # argv is optional test list; uses sys.argv[1:] is not provided from argparse import ArgumentParser options = [] if misc_opts is None else misc_opts parser = ArgumentParser(usage=prog_usage) if prog_usage else ArgumentParser() parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(prog_version)) parser.add_argument('-c', '--config', dest='config', help='the path to the configuration file') for option in options: if 'option' in option and 'destination' in option: parser.add_argument(option['option'], dest=option.get('destination', ''), default=option.get('default', ''), help=option.get('description', ''), action=option.get('action', 'store')) args = parser.parse_args(argv) print('args',args) return args class argParseTestCase(unittest.TestCase): def test_config(self): sys.argv[1:]=['-c','config.yaml'] options = get_options() self.assertEquals('config.yaml', options.config) def test_version(self): sys.argv[1:]=['-v'] with self.assertRaises(SystemExit): get_options() # testing version message requires redirecting stdout # similarly for a misc_opts test if __name__=='__main__': unittest.main() 

Prefiero pasar explícitamente argumentos en lugar de confiar en atributos disponibles globalmente como sys.argv (que parser.parse_args() hace internamente). Por lo tanto, normalmente uso argparse pasando la lista de argumentos por mí mismo (a main() y posteriormente get_options() y donde sea que los necesite):

 def get_options(args, prog_version='1.0', prog_usage='', misc_opts=None): # ... return parser.parse_args(args) 

y luego pasar en los argumentos

 def main(args): get_options(args) if __name__ == "__main__": main(sys.argv[1:]) 

De esa manera puedo reemplazar y probar cualquier lista de argumentos que me gustan.

 options = get_options(['-c','config.yaml']) self.assertEquals('config.yaml', options.config)