LoginSignup
7
5

More than 5 years have passed since last update.

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

Posted at

TL;DR

TextTestRunner を上手に使おう!

はじめに

みなさん、ユニットテスト書いてますか?
この記事が目に留まったあなたは、少なくとも ユニットテストに興味があり、Pythonの unittest モジュール について調べたことがある のではないでしょうか。
この unittest モジュールは、非常に強力である一方、最小限の労力で利用することもできるスグレモノです。

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

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

検証環境

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

サンプルコード&実行結果

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

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ステップに凝縮されています。

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

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

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

記事を書きながら調べている最中に知ったのですが、ここで指定する3つの引数は、 unittest.TextTestResult をインスタンス化する際に利用されるようです。
だからどう、ということがあるのかないのか…… それは君の目で確かめてみてほしい。

あとがき

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

7
5
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
7
5