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

## 対処したい課題

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

```py3: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が飛ぶ。

```shell-session
>>> 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:

https://docs.python.org/3.6/library/argparse.html#action

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

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

## やったこと

次のようにしてみる。

```py3: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`` 等をパースするオプション部分をコメントアウトした場合

```shell-session
$ 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)
```

コメントインした場合

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

OK
```

## 感想

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

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

## 確認に使った環境

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

## 参考

* https://github.com/dmiyakawa/python_argparse_test_version
* [Capture and test sys.stdout/sys.stderr in unittest.TestCase](http://schinckel.net/2013/04/15/capture-and-test-sys.stdout-sys.stderr-in-unittest.testcase/)