Cómo probar o simular el contenido de “if __name__ == ‘__main__'”

Digamos que tengo un módulo con lo siguiente:

def main(): pass if __name__ == "__main__": main() 

Quiero escribir una prueba de unidad para la mitad inferior (me gustaría lograr una cobertura del 100%). Descubrí el módulo incorporado runpy que realiza el mecanismo de configuración / __name__ -setting, pero no puedo averiguar cómo simular o verificar de otro modo que se llama a la función main () .

Esto es lo que he intentado 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() 

if __name__ == '__main__' otra alternativa que es excluir el if __name__ == '__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 agregue otro caso de prueba para el script (solo por tener una cobertura del 100%) será solo una duplicada.

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

 [report] exclude_lines = if __name__ == .__main__.: 

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

Espero que esto pueda ayudar.

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

Por ejemplo, podrías 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.

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 scripts que terminaban con este copypasta exacto:

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

No es horrible, claro, pero tampoco 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 coloque esta gem 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 no hace nada a menos que la secuencia de comandos se ejecute como una secuencia de comandos. Así que 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) 

Cariño Ahora puede escribir un main() comprobable, invocarlo como un script, tener una cobertura de prueba del 100% y no necesita ignorar ningún código en su informe de cobertura.

Un método consiste en ejecutar los módulos como scripts (por ejemplo, os.system (…)) y comparar su salida stdout y stderr con los valores esperados.

Mi solución es usar imp.load_source() y forzar que se imp.load_source() una excepción al principio de main() al no proporcionar un argumento CLI requerido, proporcionar un argumento mal formado, 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 tu clase de prueba, puedes usar esta función así:

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