Development Tip

"if __name__ == '__main__'"콘텐츠를 테스트하거나 모의하는 방법

yourdevel 2020. 11. 28. 12:33
반응형

"if __name__ == '__main__'"콘텐츠를 테스트하거나 모의하는 방법


다음과 같은 모듈이 있다고 가정합니다.

def main():
    pass

if __name__ == "__main__":
    main()

하반부에 대한 단위 테스트를 작성하고 싶습니다 (100 % 커버리지를 달성하고 싶습니다). import / -setting 메커니즘 을 수행 하는 runpy 내장 모듈을 발견 __name__했지만, main () 함수가 호출 되었는지 모의하거나 확인하는 방법을 알아낼 수 없습니다 .

이것이 내가 지금까지 시도한 것입니다.

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__'커버리지 보고서에서 를 제외하는 다른 대안을 선택하겠습니다. 물론 테스트에서 main () 함수에 대한 테스트 케이스가 이미있는 경우에만 그렇게 할 수 있습니다.

전체 스크립트에 대해 새 테스트 케이스를 작성하는 대신 제외하기로 선택한 이유는 내가 이미 언급했듯이 main()함수에 대한 테스트 케이스가 이미있는 경우 스크립트에 대해 다른 테스트 케이스를 추가한다는 사실 때문입니다. 100 % 적용)은 복제 된 것입니다.

제외하는 방법 if __name__ == '__main__'은 커버리지 구성 파일을 작성하고 섹션 보고서에 추가 할 수 있습니다.

[report]

exclude_lines =
    if __name__ == .__main__.:

커버리지 구성 파일에 대한 자세한 정보는 여기 에서 찾을 수 있습니다 .

이것이 도움이되기를 바랍니다.


명령문이 imp아닌 모듈을 사용하여이를 수행 할 수 있습니다 import. 문의 문제 import'__main__'에 할당 할 기회를 얻기 전에 import 문의 일부로 실행 테스트가 실행된다는 것 runpy.__name__입니다.

예를 들어 다음 imp.load_source()과 같이 사용할 수 있습니다 .

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

첫 번째 매개 변수는 __name__가져온 모듈에 할당됩니다 .


우와, 파티에 조금 늦었지만 최근에이 문제를 만났고 더 나은 해결책을 찾은 것 같아서 여기 있습니다.

저는이 정확한 카피 파스타로 끝나는 12 개 정도의 스크립트를 포함하는 모듈을 작업하고있었습니다.

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

끔찍하지는 않지만 테스트 할 수도 없습니다. 내 솔루션은 내 모듈 중 하나에 새 함수를 작성하는 것입니다.

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())

그런 다음이 gem을 각 스크립트 파일의 끝에 배치합니다.

run_script(__name__, __doc__, main)

기술적으로이 함수는 스크립트를 모듈로 가져 왔거나 스크립트로 실행했는지에 관계없이 무조건 실행됩니다. 그러나 스크립트가 스크립트로 실행되지 않는 한 함수는 실제로 아무것도 수행 하지 않기 때문에 괜찮습니다 . 따라서 코드 커버리지는 함수가 실행되는 것을보고 "예, 100 % 코드 커버리지!"라고 말합니다. 한편, 함수 자체를 다루기 위해 세 가지 테스트를 작성했습니다.

@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)

꽝! 이제 테스트 가능을 작성하고 main(), 스크립트로 호출하고, 100 % 테스트 범위를 가지며, 범위 보고서의 코드를 무시할 필요가 없습니다.


한 가지 접근 방식은 모듈을 스크립트 (예 : os.system (...))로 실행하고 stdout 및 stderr 출력을 예상 값과 비교하는 것입니다.


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')

참고URL : https://stackoverflow.com/questions/5850268/how-to-test-or-mock-if-name-main-contents

반응형