2016-02-14

Pythonのargparseをテストする

Pythonのargparseモジュールを利用するとPythonスクリプトに渡された引数のチェックを良い感じにやってくれます。自動的にヘルプコマンドも作ってくれたりするので、コマンドラインツールを作るときにはかなり便利です。

今回はargparseによるコマンドライン引数のパースをunittests + Mockでテストしてみました。Pythonのバージョンは2系です。

完成形はこんな感じ↓

import unittest
from mock import patch, Mock


class ParserError(Exception):
    pass

class ParserTestCase(unittest.TestCase):
    @patch('argparse.ArgumentParser.error')
    def test_parse_error(self, arg_error):
        arg_error.side_effect = ParserError('')
        parser = argparse.ArgumentParser(
            description='ExactTarget Command Line Interface Tool.')
        parser.add_argument('-v', '--version',
            action='version', version='%(prog)s ' + __version__)
        #...
        
        self.assertRaises(ParserError, parser.parse_args, [])
        self.assertEquals(1, arg_error.call_count)

parse_argsで引数をパースしたときにエラーが発生すると、ArgumentParserのインスタンスのerrorメソッドがコールされます。上記ではerrorメソッドを@patchのデコレータによってオーバーライドしており、side_effectで例外クラスを指定しています。side_effectで例外クラスを指定すると、モック実行時に指定した例外クラスが投げられます。これをassertRaisesで補足することで、例外が発生する=errorが呼ばれることを検証します。

sys.exit()がコールされるとSystemExitのExceptionがスローされるので、assertRaisesではSystemExitを補足してもOKですが、「sys.exit()がコールされたか」ではなく、errorメソッドが適切にコールされたかどうかを見たかったので上記のようなコードにしています。

また、errorメソッドの呼び出しであれば、assert_called_once_withなどの「呼ばれたかどうか」の検証でも良いのですが、argparseでExceptionをスローしない場合は後続の処理が行われてしまい、別のエラーが発生してうまくテストが出来ません。

このエントリーをはてなブックマークに追加