ライブラリ:doctest

Python の doctest というライブラリをご紹介します。

import doctest

doctest は、名前に doc + test とあるとおり、ドキュメントによってテストを行うためのライブラリです。 具体的には、関数やクラスの中の最初のコメント―― docstring の中にテストコードを書いてしまおうというものです。

具体的に見ていきましょう。

以下、 int 型の数値が偶数かどうかをチェックして返す関数を作っていきながら、 doctest を使ってみることにします。

偶数かどうかをチェックチェックする関数

偶数かどうかをチェックする関数を考えます。

def is_even0(number):
    return number % 2 == 0

ここに doctest でテストケースを追加していきます。

最初の一歩

is_even1() と名前を変えて、 docstring 内に関数の説明とテストを書いてみます。

def is_even1(number):
    """check if number is even or not
    if even, return true, else false.

    >>> is_even1(2)
    True

    >>> is_even1(5)
    False
    """

    return number % 2 == 0


if __name__ == "__main__":
    import doctest  # ライブラリの読み込み
    doctest.testmod()  # このモジュール内のテストの実行

これが doctest を利用するときの最小構成です。

docstring 内の

    >>> is_even1(2)
    True

というブロックがひとつのテストケースになります。

書式としては Python の対話型モードのように >>> のあとにコードを書いて、期待する結果をその次の行に >>> をつけずに書く形になります。

複数のテストケースを書く場合は、空行をはさんでから書いていきます。

ここでは実際に is_even1(2)is_even(5) のふたつのテストケースが書かれています。

この docstring 内に書かれたテストを走らせるには、上記スクリプトを保存して -v オプションをつけて実行します。

$ python even_module.py -v
Trying:
    is_even1(2)
Expecting:
    True
ok
Trying:
    is_even1(5)
Expecting:
    False
ok
ok
1 items had no tests:
    __main__
1 items passed all tests:
   2 tests in __main__.is_even1
2 tests in 2 items.
2 passed and 0 failed.

-v オプションをつけなかった場合は、テストに失敗したときにだけ出力が行われ、成功したときには何も表示しません。

テストを実際に走らせているのは、コードの一番下、 if __name__ == ... 以降の部分です。

if __name__ == ... を書かずに次のような形で doctest を実行することもできます。

$ python -m doctest even_module.py -v

結果は if __name__ == ... と書いた場合と同じです。

つづいて、テストケースを追加してみます。

doctest 二歩目

is_even1() では int 型の値が渡されることを前提としていました。次はそうではなく、他の型が渡される可能性も考慮した関数を作ってみます。

int 以外の型が渡されたときにはエラーをあげるようにしましょう。そのための doctest のテストコードも追加することにします。

def is_even2(number):
    """check if number is even or not
    if even, return true, else false.

    @param
    number : int

    >>> is_even2(2)
    True

    >>> is_even2(5)
    False

    >>> is_even2("%s")
    Traceback (most recent call last):
    ...
    TypeError: not an integer
    """

    if type(number) != int:
        raise TypeError("not an integer")
    return number % 2 == 0


if __name__ == "__main__":
    import doctest  # ライブラリの読み込み
    doctest.testmod()  # このモジュール内のテストの実行

追加されたコードは

    if type(number) != int:
        raise TypeError("not an integer")

で、追加されたテストケースは

    >>> is_even2("%s")
    Traceback (most recent call last):
    ...
    TypeError: not an integer

です。

新たなテストケースでは、何らかの値が返ってくるかどうかではなく、想定した例外があがるかどうかをチェックしています。

特定の例外はあがることを期待する場合は次のように書きます。

    Traceback (most recent call last):
    ...
    TypeError: not an integer

最初の 2 行は定型文となっています。 2 行目は「省略」の意味を込めて ... と書く形になっているようです。

以上です。

ほんのちょっとだけですが、 doctest の基本的な使い方を見てみました。

もっと踏み込んで使いたい場合は、もろもろドキュメントや参考情報をご参照ください。

参考