はしくれエンジニアもどきのメモ

情報系技術・哲学・デザインなどの勉強メモ・備忘録です。

Python のdoctest を試す

Python のdoctest を試す

簡単にテストできるdoctest についてのメモ。 unittestモジュールより手軽に利用でき、 テスト駆動開発っぽいことができる。

環境

doctest

python で簡単にユニットテストできる。 docstring の中にテスト内容を書いておくだけでいい。 unittestモジュールの完全代替は難しいと思われる。

int値を入力して、偶数か奇数かをチェックする関数をつくる。

テスト内容をdocstring の中 に書いておく。


>>> 関数(入力値)
期待する出力値

偶数チェックの関数なので、 以下のようなテスト内容にしておく。


...
>>> check_even(2)
True
>>> check_even(0)
True
>>> check_even(1)
False
...

また、メインにdoctest を実行する処理を書いておく。 これで、メインで実行した場合のみテストが行われる。


if __name__ == '__main__':
    import doctest
    doctest.testmod()

関数の中身は、まだ実装せず、 全体のソースとして、以下のようにしておく。


def  check_even(n: int) -> bool:
'''
Function, check a even number.

Arg: int

Return: Bool
  True: even
  False: odd

  >>> check_even(2)
  True
  >>> check_even(0)
  True
  >>> check_even(1)
  False
'''
pass

if __name__ == '__main__':
    import doctest
    doctest.testmod()

テストの実行

テストするには、pythonコマンドに vオプションをつけておくと、詳細なテスト結果がわかる。


python ファイル名.py -v

中身を実装していないので、 テストを実行して失敗するかを確認する。

実際に実行すると以下のような出力になる。


> python check_even.py -v

Trying:
    check_even(2)
Expecting:
    True
**********************************************************************
File "check_even.py", line 18, in __main__.check_even
Failed example:
    check_even(2)
Expected:
    True
Got nothing
Trying:
    check_even(0)
Expecting:
    True
**********************************************************************
File "check_even.py", line 20, in __main__.check_even
Failed example:
    check_even(0)
Expected:
    True
Got nothing
Trying:
    check_even(1)
Expecting:
    False
**********************************************************************
File "check_even.py", line 22, in __main__.check_even
Failed example:
    check_even(1)
Expected:
    False
Got nothing
1 items had no tests:
    __main__
**********************************************************************
1 items had failures:
   3 of   3 in __main__.check_even
3 tests in 2 items.
0 passed and 3 failed.
***Test Failed*** 3 failures.

実行結果にはunittestモジュール同様、 3タイプ「OK」,「Failed」, 「Error」がある。 「Error」 は、テストを実行前にプログラムが失敗しているので、 まず「Error」を失くすことから始める。

doctest にテスト内容を書いておけば、 メイン(if __name__ == '__main__')にテスト実行の処理を書かなくてもテストを実行できる。 pythonコマンドに、 モジュールオプション(-m)をつける。


python -m doctest -v ファイル名.py

テストのFailure を確認したので、 関数の中身を実装する。


def check_even(n:int) -> bool:
'''
Function, check a even number.

Arg: int

Return: Bool
True: even
False: odd

>>> check_even(2)
True
>>> check_even(0)
True
>>> check_even(1)
False
'''

if n % 2 == 0:
return True

return False

> python -m doctest -v check_even.py
Trying:
check_even(2)
Expecting:
True
ok
Trying:
check_even(0)
Expecting:
True
ok
Trying:
check_even(1)
Expecting:
False
ok
1 items had no tests:
check_even
1 items passed all tests:
3 tests in check_even.check_even
3 tests in 2 items.
3 passed and 0 failed.
Test passed.

テストが通っていることを確認。

raise Exception のテスト

doctest でもraise Exception のテストができる。

入力値の型がint かどうかをチェックする。 int でなかったら、 raise NotIntErrorを出すテストを追加する。

... は、省略を表す。


>>> check_even(1.5)
Traceback (most recent call last):
...
check_even.NotIntError

型チェック実装前に、(テストを追加した以下のコードで)テストする。


class NotIntError(ValueError):
  pass

def check_even(n:int) -> bool:
  '''
  Function, check a even number.

  Arg: int

  Return: Bool
  True: even
  False: odd

  >>> check_even(2)
  True
  >>> check_even(0)
  True
  >>> check_even(1)
  False
  >>> check_even(1.5)
  Traceback (most recent call last):
  ...
  check_even.NotIntError
  '''

  if n % 2 == 0:
  return True

  return False

テスト結果はFailure になるはず。


> python -m doctest -v check_even.py
Trying:
    check_even(2)
Expecting:
    True
ok
Trying:
    check_even(0)
Expecting:
    True
ok
Trying:
    check_even(1)
Expecting:
    False
ok
Trying:
    check_even(1.5)
Expecting:
    Traceback (most recent call last):
    ...
    check_even.NotIntError
**********************************************************************
File "C:\Users\nabana\GoogleDrive\python_workspace\check_even.py", line 27, in check_even
Failed example:
    check_even(1.5)
Expected:
    Traceback (most recent call last):
    ...
    check_even.NotIntError
Got:
    False
2 items had no tests:
    check_even
    check_even.NotIntError
**********************************************************************
1 items had failures:
     1 of   4 in check_even.check_even
    4 tests in 3 items.
3 passed and 1 failed.
***Test Failed*** 1 failures.

Failure を確認できたので、型チェックを実装してテストする。


class NotIntError(ValueError):
    pass

def check_even(n:int) -> bool:
    '''
    Function, check a even number.

    Arg: int

    Return: Bool
      True: even
      False: odd

    >>> check_even(2)
    True
    >>> check_even(0)
    True
    >>> check_even(1)
    False
    >>> check_even(1.5)
    Traceback (most recent call last):
    ...
    check_even.NotIntError
    '''

    if not isinstance(n, int):
        raise NotIntError

    if n % 2 == 0:
        return True

    return False

テスト結果がパスするか確認する。


> python -m doctest -v check_even.py
Trying:
  check_even(2)
Expecting:
  True
ok
Trying:
  check_even(0)
Expecting:
  True
ok
Trying:
  check_even(1)
Expecting:
  False
ok
Trying:
  check_even(1.5)
Expecting:
  Traceback (most recent call last):
  ...
  check_even.NotIntError
ok
2 items had no tests:
  check_even
  check_even.NotIntError
1 items passed all tests:
 4 tests in check_even.check_even
4 tests in 3 items.
4 passed and 0 failed.
Test passed.