"unittest.main()"の少しリッチな使い方 - テスト結果をファイル出力しよう

# TL;DR

***[TextTestRunner](https://docs.python.org/ja/3.6/library/unittest.html#unittest.TextTestRunner) を上手に使おう！***

# はじめに

みなさん、ユニットテスト書いてますか？
この記事が目に留まったあなたは、少なくとも **ユニットテストに興味があり、Pythonの [unittest モジュール](https://docs.python.org/ja/3.6/library/unittest.html) について調べたことがある** のではないでしょうか。
この unittest モジュールは、非常に強力である一方、最小限の労力で利用することもできるスグレモノです。

この後者の「**最小限の労力で利用することもできる**」というのが曲者で、一度ラクな手順を覚えてしまったが最後、本来備わっているポテンシャルを引き出すことなく、惰性でラクな手順を続けてしまう……ということがまれによくあります。
タイトルにも書いた [unittest.main()](https://docs.python.org/ja/3.6/library/unittest.html#unittest.main) はその代表とも言え、この1ステップをスクリプトの末尾に書くだけで、スクリプト実行時に同ファイル内のテストコードを走査してくれるシロモノです。

そんな~~エンジニアをダメにする~~ unittest.main() ですが、*引数をよしなに渡すだけ*で、*テスト結果をファイルに出力*したり、*出力する内容の詳細度を変更*したり、より便利にカスタマイズが可能です。
詳解は、公式ドキュメントに譲ることとして、ここでは具体的な実装例を示します。

# 検証環境

* Windows10 Pro 64bit
* Python 3.6.0 \| Anaconda 4.3.0 (64-bit)

# サンプルコード＆実行結果

> 出力される内容の確認のため、あえてERRORとなるようなテストを書いています（TestArithmetic.test_div）。

```python:sample.py
# coding=utf-8

import enum
import pathlib
import unittest

from datetime import datetime


class TestArithmetic(unittest.TestCase):
    """Test of arithmetic."""

    def test_add(self):
        """Test of addition."""
        self.assertEqual(1 + 2, 3)

    def test_div(self):
        """Test of division."""
        # error!
        self.assertEqual(1 / 0, 1)


def _main():
    """The main process at script execution."""
    @enum.unique
    class Verbosity(enum.IntEnum):
        NOTHING = 0
        SIMPLE = 1
        DETAIL = 2

    CURRENT_PATH = pathlib.Path(__file__)
    EXEC_DATETIME = datetime.today()
    FILE_MOD_DATETIME = datetime.fromtimestamp(CURRENT_PATH.stat().st_mtime)
    LOG_PATH = CURRENT_PATH.with_name(
        CURRENT_PATH.stem + f'_{EXEC_DATETIME.date()}.log')

    with pathlib.Path(LOG_PATH).open('w') as fw:
        fw.write(
            f'[Date of script modification] {str(FILE_MOD_DATETIME)}\n'
            f'[Date of this test execution] {str(EXEC_DATETIME)}\n'
            '\n')

        unittest.main(
            testRunner=unittest.TextTestRunner(
                stream=fw,
                descriptions=False,
                verbosity=Verbosity.DETAIL))


if __name__ == '__main__':
    _main()
```

出力されるログファイルはこちら：

```sample_2018-06-23.log
[Date of script modification] 2018-06-25 07:52:19.137042
[Date of this test execution] 2018-06-25 07:53:06.383679

test_add (__main__.TestArithmetic) ... ok
test_div (__main__.TestArithmetic) ... ERROR

======================================================================
ERROR: test_div (__main__.TestArithmetic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "sample.py", line 20, in test_div
    self.assertEqual(1 / 0, 1)
ZeroDivisionError: division by zero

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (errors=1)
```

## 解説

このサンプルコードのうち、本題にとって重要な部分は、以下の1ステップに凝縮されています。

```python
unittest.main(
    testRunner=unittest.TextTestRunner(
        stream=fw,
        descriptions=False,
        verbosity=Verbosity.DETAIL))
```

この5行のコードについて、1行ずつ見ていきます。

1行目は、言わずとしれた unittest.main() の呼び出しです。ここは説明不要でしょう。
2行目は、testRunner引数の指定をしています。ここでは、標準モジュールの [unittest.TextTestRunner](https://docs.python.org/ja/3.6/library/unittest.html#unittest.TextTestRunner)を指定しています。ここで指定する、TextTestRunnerクラスの設定に依って、unittest.main()の挙動が変わります。
3行目は、テスト結果の出力先（出力ストリーム）を指定しています。デフォルトでは [sys.stderr](https://docs.python.org/ja/3.6/library/sys.html#sys.stderr) が出力先となります。
4行目は、テスト結果として、テストの説明（テストメソッドのdocstringの1行目）を出力するか否かを指定します。
5行目は、出力する内容の詳細度を3段階（0～2）で指定します。

記事を書きながら調べている最中に知ったのですが、ここで指定する3つの引数は、 [unittest.TextTestResult](https://docs.python.org/ja/3.6/library/unittest.html#unittest.TextTestResult) をインスタンス化する際に利用されるようです。
だからどう、ということがあるのかないのか…… ~~それは君の目で確かめてみてほしい。~~

# あとがき

私自身は、Visual Stduio Codeのスニペットに `def _main():` 以下を登録して、使いまわしています。
今回は、最も単純な、単一ファイルのユニットテストが対象でしたが、複数ファイルを対象にする場合の記事も、いずれ書きたいと思います。