
doctest は python の標準ライブラリです。追加のインストールは不要で、python をインストールすると最初から使えるはずです。
doctest
一番最初にテストコードで doctest をテストしてみます。
def add(a, b): """ Add two numbers. Examples: >>> add(2, 3) 5 >>> add(-1, 1) 0 """ return a + b
実行コード:
python -m doctest -v your_module.py
実行したときのログは次のとおりです。
Trying:
add(2, 3)
Expecting:
5
ok
Trying:
add(-1, 1)
Expecting:
0
ok
1 items had no tests:
doctest-sample
1 items passed all tests:
2 tests in doctest-sample.add
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
-v をつけると上述のように、テストのログがコンソールに出力されるようになります。
-v をつけなかったときは、テストに成功した場合だと、なにも出力しません。しかし、テストに失敗した場合はコンソールにエラーの内容を出力します。なので -v は verbose(冗長な)オプションかもしれません。
*******************************************************
File "doctest-sample.py", line 8, in doctest-sample.add
Failed example:
add(-1, 1)
Expected:
2
Got:
0
*******************************************************
1 items had failures:
1 of 2 in doctest-sample.add
***Test Failed*** 1 failures.
また doctest を実行した際も __pycache__ フォルダが生成されて、フォルダの中にバイトコード化された .pyc ファイルができます。
これは結果的に -m オプションをつけるかどうかで、基本的には生成するかどうかが決まっています。(キャッシュを作ることが目的ではないが)
もしもキャッシュを作りたくないときは:
def add(a, b): """ Add two numbers. Examples: >>> add(2, 3) 5 >>> add(-1, 1) 0 """ return a + b if __name__ == "__main__": import doctest doctest.testmod()
これだと単純な命令でテストを実行できるし、キャッシュも生成されない。でも、テストコード用のファイルみたいになってしまうので痛し痒し。
python your_module.py
補足として、テスト結果を外部ファイルに出力したい場合もこの書き方になる。
import doctest
if __name__ == "__main__":
doctest.testfile('output.txt')
基本的な考え方として python は「再利用されるモジュール」だけ(自動的に)キャッシュにして残す設計だと思います。スクリプトの実行は限られた使い捨ての利用なので、キャッシュにする必要が(基本的に)ない、ということ。
本質的には
-mをつけたから__pycache__フォルダができたのだ、という理解は、ちょっと違うと思う。
テストの書き方
対話モードを示す >>> のあとにテストコードを示すだけです。テストコードのインデントの深さは関係していません。
def add(a, b): """ Add two numbers. Examples: >>> add(2, 3) 5 >>> add(-1, 1) 0 """ return a + b
基本的には対話モードで示した関数の実行例の返却値を次の行で示します。
例外をテストする場合は?
def divide(a, b): """ Divide a by b. >>> divide(4, 2) 2.0 >>> divide(1, 0) Traceback (most recent call last): ... ZeroDivisionError: division by zero """ return a / b
例外をテストしたいときの書き方は決まったやり方が必要です。
Traceback (most recent call last): を例外発生を示す呪文として記述する必要があります。さらに次の行はインデントを加えたうえで ... が必要。その上、最後に発生する例外 Error +その詳細も正確に明記する必要がある。なので、通常は division by zero の部分 message もしっかり書かないとダメ。
例外の message 部分を無視したいときは?
def divide(a, b): """ >>> divide(4, 2) 2.0 >>> divide(1, 0) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... ZeroDivisionError """ return a / b
+IGNORE_EXCEPTION_DETAIL オプションを使用する。
このときは ... の行をなくしても OK の判定がでます。これは以下のとおりだと思います。
個人的には、例外の詳細を IGNORE_EXCEPTION_DETAIL で吸収して、その手前の ... は慣例として付けておいたらいいと思います。
例外の
...は、この書き方で特別に省略が許されているケースみたいなので。
自分のテキスト出力で、一致の省略をしたいときは?
+ELLIPSIS オプションで一致する部分を省略できる。例えば 'Result:' だけに限定することができる。(後方一致がしたいなら ... completed などになる)
def verbose_result(): """ >>> verbose_result() # doctest: +ELLIPSIS 'Result: ...' """ return "Result: 123 completed"
細かいポイントだけど、返却されるテスト結果を 'Result: ...' つまり '' 文字列として評価している。この違いは次のケースで示すことができる。テスト実行中に print(つまり標準出力)に与えられたときは '' が不要です。
整理すると、戻り値として文字列を返却するときは文字列なので '' が必要。
def verbose_result(): """ >>> verbose_result() # doctest: +ELLIPSIS Result: ... """ print("Result: 123 completed")
なにも返却しない関数の場合は?
補足ですが、まったくなにも返却するデータの無い関数の場合は次のようにすることができます。
def noop(): """ >>> noop() """ return None
Trying:
noop()
Expecting nothing
ok
1 items had no tests:
doctest-sample2
1 items passed all tests:
1 tests in doctest-sample2.noop
1 tests in 2 items.
1 passed and 0 failed.
Test passed.