How to test or mock “if __name__ == '__main__'” contents

前端 未结 7 594
無奈伤痛
無奈伤痛 2020-12-07 20:07

Say I have a module with the following:

def main():
    pass

if __name__ == \"__main__\":
    main()

I want to write a unit test for the b

相关标签:
7条回答
  • 2020-12-07 20:18

    You can do this using the imp module rather than the import statement. The problem with the import statement is that the test for '__main__' runs as part of the import statement before you get a chance to assign to runpy.__name__.

    For example, you could use imp.load_source() like so:

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

    The first parameter is assigned to __name__ of the imported module.

    0 讨论(0)
  • 2020-12-07 20:18

    Python 3 solution:

    import os
    from importlib.machinery import SourceFileLoader
    from importlib.util import spec_from_loader, module_from_spec
    from importlib import reload
    from unittest import TestCase
    from unittest.mock import MagicMock, patch
        
    
    class TestIfNameEqMain(TestCase):
        def test_name_eq_main(self):
            loader = SourceFileLoader('__main__',
                                      os.path.join(os.path.dirname(os.path.dirname(__file__)),
                                                   '__main__.py'))
            with self.assertRaises(SystemExit) as e:
                loader.exec_module(module_from_spec(spec_from_loader(loader.name, loader)))
    

    Using the alternative solution of defining your own little function:

    # module.py
    def main():
        if __name__ == '__main__':
            return 'sweet'
        return 'child of mine'
    

    You can test with:

    # Override the `__name__` value in your module to '__main__'
    with patch('module_name.__name__', '__main__'):
        import module_name
        self.assertEqual(module_name.main(), 'sweet')
    
    with patch('module_name.__name__', 'anything else'):
        reload(module_name)
        del module_name
        import module_name
        self.assertEqual(module_name.main(), 'child of mine')
    
    0 讨论(0)
  • 2020-12-07 20:18

    My solution is to use imp.load_source() and force an exception to be raised early in main() by not providing a required CLI argument, providing a malformed argument, setting paths in such a way that a required file is not found, 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)
    

    Then in your test class you can use this function like this:

    def testMain(self):
        mainCond(self, 'path/to/main.py', cliArgsStr='-d FailingArg')
    
    0 讨论(0)
  • 2020-12-07 20:23

    One approach is to run the modules as scripts (e.g. os.system(...)) and compare their stdout and stderr output to expected values.

    0 讨论(0)
  • 2020-12-07 20:30

    I will choose another alternative which is to exclude the if __name__ == '__main__' from the coverage report , of course you can do that only if you already have a test case for your main() function in your tests.

    As for why I choose to exclude rather than writing a new test case for the whole script is because if as I stated you already have a test case for your main() function the fact that you add an other test case for the script (just for having a 100 % coverage) will be just a duplicated one.

    For how to exclude the if __name__ == '__main__' you can write a coverage configuration file and add in the section report:

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

    More info about the coverage configuration file can be found here.

    Hope this can help.

    0 讨论(0)
  • 2020-12-07 20:37

    I found this solution helpful. Works well if you use a function to keep all your script code. The code will be handled as one code line. It doesn't matter if the entire line was executed for coverage counter (though this is not what you would actually actually expect by 100% coverage) The trick is also accepted pylint. ;-)

    if __name__ == '__main__': \
        main()
    
    0 讨论(0)
提交回复
热议问题