it-swarm.dev

Cómo probar o simular "if __name__ == '__main__'" content

Digamos que tengo un módulo con lo siguiente:

def main():
    pass

if __== "__main__":
    main()

Quiero escribir una prueba unitaria para la mitad inferior (me gustaría lograr una cobertura del 100%). Descubrí el módulo integrado runpy que realiza el mecanismo de configuración de importación/__name__-, pero no puedo entender cómo burlarme o no. compruebe que se llama a la función main () .

Esto es lo que he probado hasta ahora:

import runpy
import mock

@mock.patch('foobar.main')
def test_main(self, main):
    runpy.run_module('foobar', run_name='__main__')
    main.assert_called_once_with()
59
Nikolaj

Elegiré otra alternativa que sea excluir el if __== '__main__' del informe de cobertura, por supuesto, puede hacerlo solo si ya tiene un caso de prueba para su función main () en sus pruebas.

En cuanto a por qué elijo excluir en lugar de escribir un nuevo caso de prueba para todo el script, es porque si, como dije, ya tiene un caso de prueba para su función main(), el hecho de que agrega otro caso de prueba para el script (solo por tener una cobertura del 100%) será solo una duplicada.

Para saber cómo excluir el if __== '__main__' puede escribir un archivo de configuración de cobertura y agregarlo en el informe de la sección:

[report]

exclude_lines =
    if __== .__main__.:

Se puede encontrar más información sobre el archivo de configuración de cobertura aquí .

Espero que esto pueda ayudar.

47
mouad

Puede hacerlo utilizando el módulo imp en lugar de la instrucción import. El problema con la instrucción import es que la prueba para '__main__' se ejecuta como parte de la instrucción de importación antes de que tenga la oportunidad de asignar a runpy.__name__.

Por ejemplo, podría usar imp.load_source() así:

import imp
runpy = imp.load_source('__main__', '/path/to/runpy.py')

El primer parámetro se asigna a __name__ del módulo importado.

11
David Heffernan

Vaya, llego un poco tarde a la fiesta, pero recientemente me encontré con este problema y creo que se me ocurrió una solución mejor, así que aquí está ...

Estaba trabajando en un módulo que contenía una docena de secuencias de comandos que terminaban con esta copia exacta:

if __== '__main__':
    if '--help' in sys.argv or '-h' in sys.argv:
        print(__doc__)
    else:
        sys.exit(main())

No es horrible, claro, pero tampoco es comprobable. Mi solución fue escribir una nueva función en uno de mis módulos:

def run_script(name, doc, main):
    """Act like a script if we were invoked like a script."""
    if name == '__main__':
        if '--help' in sys.argv or '-h' in sys.argv:
            sys.stdout.write(doc)
        else:
            sys.exit(main())

y luego coloca esta gema al final de cada archivo de script:

run_script(__name__, __doc__, main)

Técnicamente, esta función se ejecutará incondicionalmente si su script se importó como un módulo o se ejecutó como un script. Sin embargo, esto está bien porque la función en realidad no do nada a menos que el script se ejecute como un script. Entonces, la cobertura de código ve que la función se ejecuta y dice "sí, ¡100% de cobertura de código!" Mientras tanto, escribí tres pruebas para cubrir la función en sí:

@patch('mymodule.utils.sys')
def test_run_script_as_import(self, sysMock):
    """The run_script() func is a NOP when name != __main__."""
    mainMock = Mock()
    sysMock.argv = []
    run_script('some_module', 'docdocdoc', mainMock)
    self.assertEqual(mainMock.mock_calls, [])
    self.assertEqual(sysMock.exit.mock_calls, [])
    self.assertEqual(sysMock.stdout.write.mock_calls, [])

@patch('mymodule.utils.sys')
def test_run_script_as_script(self, sysMock):
    """Invoke main() when run as a script."""
    mainMock = Mock()
    sysMock.argv = []
    run_script('__main__', 'docdocdoc', mainMock)
    mainMock.assert_called_once_with()
    sysMock.exit.assert_called_once_with(mainMock())
    self.assertEqual(sysMock.stdout.write.mock_calls, [])

@patch('mymodule.utils.sys')
def test_run_script_with_help(self, sysMock):
    """Print help when the user asks for help."""
    mainMock = Mock()
    for h in ('-h', '--help'):
        sysMock.argv = [h]
        run_script('__main__', h*5, mainMock)
        self.assertEqual(mainMock.mock_calls, [])
        self.assertEqual(sysMock.exit.mock_calls, [])
        sysMock.stdout.write.assert_called_with(h*5)

Blam! Ahora puede escribir una main() comprobable, invocarla como un script, tener una cobertura de prueba del 100% y no tener que ignorar ningún código en su informe de cobertura.

6
robru

Un enfoque es ejecutar los módulos como scripts (por ejemplo, sistema os. (...)) y comparar su salida stdout y stderr con los valores esperados.

2
Mr Fooz

Mi solución es usar imp.load_source() y forzar una excepción que se genere temprano en main() al no proporcionar un argumento CLI requerido, proporcionar un argumento con formato incorrecto, establecer rutas de manera que no se encuentre un archivo requerido etc.

import imp    
import os
import sys

def mainCond(testObj, srcFilePath, expectedExcType=SystemExit, cliArgsStr=''):
    sys.argv = [os.path.basename(srcFilePath)] + (
        [] if len(cliArgsStr) == 0 else cliArgsStr.split(' '))
    testObj.assertRaises(expectedExcType, imp.load_source, '__main__', srcFilePath)

Luego, en su clase de prueba, puede usar esta función de esta manera:

def testMain(self):
    mainCond(self, 'path/to/main.py', cliArgsStr='-d FailingArg')
0
polsar