【doctest】Pythonプログラムをdoctestでテストする方法

Pythonプログラムをdoctestを使ってテストする方法について解説します。
目次
doctestとは
doctestは、Python標準で用意されているテストモジュールの一種で、docstring内に記載したテスト内容を対話的にテストするためのものです。doctestの公式ドキュメントはこちらを参照してください。
docstringとは、以下の例のようにPythonプログラムのクラスや関数の定義の先頭に”””で囲まれる文字列として記載するもので、各処理などの説明を記載するものを言います。
class Sample:
    """Sampleクラス"""
    def add_and_double(self, x, y):
        """xとyを足して2倍した値を返却する
        Args:
            x: 入力値1
            y: 入力値2
        Raises:
            ValueError: int以外の場合
        Returns:
            (x + y) * 2 の計算結果
        """なお、上記のdocstringの記載スタイルはGoogleスタイルと呼ばれるもので、他にも有名なところではNumPyスタイルといった記載方法もあります。
さて、doctestに戻りますが、doctestはdocstring内にテストを記載することでテストケースの実行ができます。また、docstring内に入出力例が記載されるのでドキュメントとしても分かりやすくなるという利点があります。
本記事では、doctestの基本的な使い方について紹介します。
より本格的にテストを実行する場合には、pytestやPython標準のunittestといったテストフレームワークを使用するのがおすすめです。pytestやunittestについては以下でまとめていますので興味があれば参考にしてください。
doctestの使い方
基本的な使い方
ここからは具体的にdoctestを使用したテストの例を見ていきましょう。doctestを使用する際には、以下のプログラム例(doctest_sample.py)のように実装します。
今回はSampleクラスに定義したadd_and_doubleという簡単なメソッドをテストする例を使って説明していきます。まずは、プログラム全体を示して、細部については以降で順に説明していきます。
class Sample:
    """Sampleクラス"""
    def add_and_double(self, x, y):
        """xとyを足して2倍した値を返却する
        Args:
            x: 入力値1
            y: 入力値2
        Raises:
            ValueError: int以外の場合
        Returns:
            (x + y) * 2 の計算結果
        >>> tmp = Sample()
        >>> tmp.add_and_double(1, 1)
        4
        >>> tmp.add_and_double(2, 2)
        8
        >>> tmp.add_and_double("1", 1)
        Traceback (most recent call last):
        ...
        ValueError
        """
        # intでない場合は、ValueErrorとする
        if not isinstance(x, int) or not isinstance(y, int):
            raise ValueError
        # 計算処理
        result = x + y
        result *= 2
        return result
if __name__ == "__main__":
    import doctest
    # doctestを実行
    doctest.testmod()なお、doctestではテストOKの場合、基本は何も表示されません。
python doctest_sample.py
そのため、上記のようにプログラムを実行しても何も表示されません。
基本的な構成
doctestでは、docstring内に以下のようにテストコードを記載します。以下は一部抜粋しています。
    def add_and_double(self, x, y):
        """xとyを足して2倍した値を返却する
        ...(省略)...
        >>> tmp = Sample()
        >>> tmp.add_and_double(1, 1)
        4
        ...(省略)...
        """docstring内で、「>>>」で記載した行はPythonで実際に実行するプログラム行になっています。上記はSampleクラスをtmpでインスタンス化し、tmp.add_and_double(1,1)を実行しています。この時、正解は(1 + 1) × 2 = 4なので、その下に「4」という比較すべき答えを記載します。
doctest自体は以下の部分で実行されています。
if __name__ == "__main__":
    import doctest
    # doctestを実行
    doctest.testmod()上記はdoctestをインポートして、doctest.testmod()が具体的にテストを実行しています。
if __name__ == “__main__”:のブロックに記載しておくと、外部プログラムからライブラリとして呼び出されたときには実行されません。コマンドラインで__main__として呼び出されたときのみテストが実行されることになります。
なお、テスト成功の場合は、何も結果に表示はされません。
テストが失敗する場合
では、テストが失敗する場合を見てみましょう。例えば以下のようにすると、メソッドの返却値とテスト値が異なるので失敗となります。
    def add_and_double(self, x, y):
        """xとyを足して2倍した値を返却する
        ...(省略)...
        >>> tmp = Sample()
        >>> tmp.add_and_double(1, 1)
        8
        ...(省略)...
        """**********************************************************************
File "d:\PythonProjects\python-tech-sample-source\python-tech-sample-source\python-libraries\doctest\doctest_sample.py", line 29, in __main__.Sample.add_and_double
Failed example:
    tmp.add_and_double(1, 1)
Expected:
    8
Got:
    4
**********************************************************************
1 items had failures:
   1 of   4 in __main__.Sample.add_and_double
***Test Failed*** 1 failures.上記のようにメソッドからの返却値は4で、テストとして期待していたのが8なので一致せずにFailedということになります。
なお、この例は、返却値は正しいわけですので、テストケースをミスしていた場合です。テストの失敗としては「テスト対象プログラムの処理結果が誤っている場合」や上記のように「テストケース自体が誤っているような場合」等、色々ありますのでエラーを見つつ、プログラムを修正していくようにしてください。
例外の確認方法
次に、例外処理をテストする場合のテストメソッドの記載方法です。
    def add_and_double(self, x, y):
        """xとyを足して2倍した値を返却する
        ...(省略)...
        >>> tmp.add_and_double("1", 1)
        Traceback (most recent call last):
        ...
        ValueError
        """今回のテスト対象プログラムsample.pyのadd_and_doubleメソッドでは、intではない引数が渡された場合にValueErrorを返却することになっています。
この場合出力結果は「Traceback (most recent call last):~省略~ValueError」というような出力結果となります。
doctestでこのような値との一致をみるときは、先頭の「Traceback (most recent call last):」と最後の「ValueError」部分を記載して、途中は「…」と記載することで一致を確認することができます。
もし「tmp.add_and_double(1, 1)」のように例外をあげないような処理を記載するとテストは失敗となります。
ちょっとした注意点
doctestを使用する際にちょっとした注意点があります。テスト対象のプログラム名を「doctest.py」としてしまうと、以下のようにtestmodがないということでAttributeErrorが出力されてしまいます。
AttributeError: module 'doctest' has no attribute 'testmod'
私がdoctestのサンプルプログラムを作ろうと思って安易に「doctest.py」としたことで、少しはまってしまいました。
皆さんが普段テストするようなファイルがこのようなファイル名になっていることは、ほぼないかと思うので大丈夫だと思いますが、覚え書きということで記載しておきます。
処理結果の詳細表示方法
上記で説明した通りdoctestでは、テストOKの場合にはなにも出力されません。詳細表示を必ず表示したい場合には、以下に紹介する方法が使用できます。
実行時に「-v」を指定して実行する
処理結果の詳細表示をテストOKでも表示したい場合は、以下のようにプログラム実行時のコマンドライン引数で「-v」を指定します。
python doctest_sample.py -v
この際の出力結果は以下のようになります。
> python .\doctest_sample.py -v
Trying:
    tmp = Sample()
Expecting nothing
ok
Trying:
    tmp.add_and_double(1, 1)
Expecting:
    4
ok
Trying:
    tmp.add_and_double(2, 2)
Expecting:
    8
ok
Trying:
    tmp.add_and_double("1", 1)
Expecting:
    Traceback (most recent call last):
    ...
    ValueError
ok
2 items had no tests:
    __main__
    __main__.Sample
1 items passed all tests:
   4 tests in __main__.Sample.add_and_double
4 tests in 3 items.
4 passed and 0 failed.
Test passed.doctest.testmodにverbose=Trueを設定する
処理結果の詳細表示をテストOKでも表示したい場合のもう一つの方法としては、以下のようにdoctest.testmodの引数でverbose=Trueを設定します。
if __name__ == "__main__":
    import doctest
    # doctestを実行
    doctest.testmod(verbose=True)上記のように実装しておくと実行時に「-v」をつけなくても詳細が表示されます。
> python .\doctest_sample.py   
Trying:
    tmp = Sample()
Expecting nothing
ok
Trying:
    tmp.add_and_double(1, 1)
Expecting:
    4
ok
Trying:
    tmp.add_and_double(2, 2)
Expecting:
    8
ok
Trying:
    tmp.add_and_double("1", 1)
Expecting:
    Traceback (most recent call last):
    ...
    ValueError
ok
2 items had no tests:
    __main__
    __main__.Sample
1 items passed all tests:
   4 tests in __main__.Sample.add_and_double
4 tests in 3 items.
4 passed and 0 failed.
Test passed.まとめ
Pythonプログラムをdoctestを使ってテストする方法について解説しました。
doctestは、Python標準で用意されているテストモジュールの一種でdocstring内にテストを記載することでテストケースの実行ができます。また、docstring内に入出力例が記載されるのでドキュメントとしても分かりやすくなるという利点があります。
手軽にテストできるモジュールとして是非doctestを使ってみてもらえればと思います。
より本格的にテストを実行する場合には、pytestやPython標準のunittestといったテストフレームワークを使用するのがおすすめです。pytestやunittestについては以下でまとめていますので興味があれば参考にしてください。
doctestの公式ドキュメントはこちらを参照してください。
上記で紹介しているソースコードについてはgithubにて公開しています。参考にしていただければと思います。

