LoginSignup
0
0

More than 5 years have passed since last update.

argparseモジュールのversionをテストする

Posted at

対処したい課題

こんなパーサがあったとする。

main.py
__version__ = '0.0.1'

def prepare_parser():
    parser = ArgumentParser(description=(__doc__),
                            formatter_class=RawDescriptionHelpFormatter)
    parser.add_argument('-d', '--debug', action='store_true',
                        help='Show debug log')
    parser.add_argument('-v', '--version', action='version',
                        version=__version__,
                        help='Show version and exit')
    return parser

このverison部分がちゃんと使えることをテストしたい、とする。

厄介なことに、 parser.parse_args(['-v']) とかするとその時点でSystemExitが飛ぶ。

>>> from main import prepare_parser
>>> parser = prepare_parser()
>>> args = parser.parse_args(['-v'])
0.0.1
$     <-- SystemExitが飛ぶのでインタープリタが終了した

これはargparseのversionアクションの仕様だ。

'version' - This expects a version= keyword argument in the add_argument() call, and prints version information and exits when invoked:

ユニットテストの途中で sys.exit() が無造作に飛ぶのは避けたい。またバージョンがちゃんと出力されていることは確認しときたい。

patch ( https://docs.python.jp/3/library/unittest.mock-examples.html#patch-decorators )もあり得るが、出来ればargparseの中身はブラックボックスとして扱いたい。

やったこと

次のようにしてみる。

tests.py
from contextlib import contextmanager
from io import StringIO
import unittest
import sys

from main import prepare_parser, __version__

@contextmanager
def capture_stdout(command, *args, **kwargs):
    out, sys.stdout = sys.stdout, StringIO()
    try:
        command(*args, **kwargs)
    finally:
        sys.stdout.seek(0)
        yield sys.stdout.read()
        sys.stdout = out


class MyTest(unittest.TestCase):
    def test_version(self):
        parser = prepare_parser()
        with self.assertRaises(SystemExit):
            with capture_stdout(parser.parse_args, ['-v']) as output:
                self.assertIsNotNone(output)
                self.assertEqual(__version__, output.rstrip())

        with self.assertRaises(SystemExit):
            with capture_stdout(parser.parse_args,
                                ['--version']) as output:
                self.assertIsNotNone(output)
                self.assertEqual(__version__, output.rstrip())


if __name__ == '__main__':
    unittest.main()

SystemExit が飛ばされることを期待しつつ、かつ例外が飛んだ際にstdoutに送られていた文字列をコンテクストマネージャにyieldしてもらうことで

  • versionを標準出力に表示すること
  • sys.exit()等で終了すること

をテスト出来るに至った。

-v 等をパースするオプション部分をコメントアウトした場合

$ python tests.py
usage: tests.py [-h] [-d]
tests.py: error: unrecognized arguments: -v
F
======================================================================
FAIL: test_version (__main__.MyTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests.py", line 26, in test_version
    self.assertEqual(__version__, output.rstrip())
AssertionError: '0.0.1' != ''
- 0.0.1
+


----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

コメントインした場合

$ python tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

感想

あんまりやりたい類のテストではない気はする。
このテストは脆弱か……?よくわからない。

もっと良い方法ないだろか

確認に使った環境

  • macOS Sierra 10.12.5
  • Python 3.5.2, Python 3.6.1 (pyenv)

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0