web-dev-qa-db-ja.com

「if __name__ == '__main__'」の内容をテストまたはモックする方法

次のようなモジュールがあるとします。

def main():
    pass

if __name__ == "__main__":
    main()

下半分の単体テストを記述したい(100%カバレッジを達成したい)。インポートを実行するrunpy組み込みモジュールを発見しました/ __name__- settingメカニズムですが、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()
63
Nikolaj

カバレッジレポートから_if __== '__main__'_を除外する別の方法を選択します。もちろん、テストにmain()関数のテストケースが既にある場合にのみ実行できます。

スクリプト全体に対して新しいテストケースを作成するのではなく、除外することを選択した理由については、すでに述べたようにmain()関数のテストケースがある場合、他のテストケースを追加するという事実がありますスクリプト(カバレッジが100%の場合のみ)は複製されたものになります。

_if __== '__main__'_を除外する方法については、カバレッジ構成ファイルを記述して、セクションレポートに追加できます。

_[report]

exclude_lines =
    if __== .__main__.:
_

カバレッジ構成ファイルの詳細については、 ここ を参照してください。

これがお役に立てば幸いです。

48
mouad

これは、impステートメントではなくimportモジュールを使用して行うことができます。 importステートメントの問題は、_'__main__'_に割り当てる機会を得る前に、_runpy.__name___のテストがインポートステートメントの一部として実行されることです。

たとえば、imp.load_source()を次のように使用できます。

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

最初のパラメーターは、インポートされたモジュールの___name___に割り当てられます。

12
David Heffernan

おっと、私はパーティーに少し遅れましたが、最近この問題に遭遇し、より良い解決策を考え出したと思うので、ここに...

私は、すべてこの正確なcopypastaで終わる12個程度のスクリプトを含むモジュールに取り組んでいました。

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

恐ろしいことではありません、確かですが、テストもできません。私の解決策は私のモジュールの1つに新しい関数を書くことでした:

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

次に、この宝石を各スクリプトファイルの最後に配置します。

_run_script(__name__, __doc__, main)
_

技術的には、スクリプトがモジュールとしてインポートされたか、スクリプトとして実行されたかに関係なく、この関数は無条件に実行されます。ただし、スクリプトがスクリプトとして実行されていない限り、関数は実際にはdoを実行しないため、これは問題ありません。したがって、コードカバレッジは関数の実行を確認し、「はい、100%コードカバレッジです!」その間、関数自体をカバーするために3つのテストを作成しました。

_@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%であり、カバレッジレポートのコードを無視する必要はありません。

6
robru

1つの方法は、モジュールをスクリプト(os.system(...)など)として実行し、それらのstdoutおよびstderr出力を期待値と比較することです。

2
Mr Fooz

私の解決策は、imp.load_source()を使用し、必須のCLI引数を提供せず、不正な引数を提供し、必要な方法でパスを設定することにより、main()の早い段階で例外を発生させることですファイルが見つからないなど.

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)

次に、テストクラスでこの関数を次のように使用できます。

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