2014/09/02

ライブラリ:doctest

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

import doctest

doctest は、名前に doc + test とあるとおり、ドキュメントによってテストを行うためのライブラリです。

具体的には、関数やクラスの内部のいちばん最初にあらわれるコメント、いわゆる docstring の中にテストコードを書いてしまおうというものになります。

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

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


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

偶数かどうかをチェックする関数、こんな感じになるでしょうか。
def is_even0(number):
    return number % 2 == 0

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


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__ == ... 以降の部分です。この、 import doctest と doctest.testmod() のペアは doctest を走らせるための基本パターンです。

また、 if __name__ == ... を書かないで、実行時に doctest を import して走らせることも可能です。そうしたい場合には次のように -m オプションで 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 の基本的な使い方を見てみました。


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


参考
doctest introduction - Python Testing
25.2. doctest — Test interactive Python examples — Python公式ドキュメント
Pythonで簡単な単体テストをはじめよう - doctest - tomoemonの日記
Python でテスト - Qiita [キータ]

0 件のコメント: