見出し画像

【Python】SimplePrograms line 14 Doctest-based testing


プログラム

14行プログラムです。

def median(pool):
    '''Statistical median to demonstrate doctest.
    >>> median([2, 9, 9, 7, 9, 2, 4, 5, 8])
    6 #change to 7 in order to pass the test
    '''
    copy = sorted(pool)
    size = len(copy)
    if size % 2 == 1:
        return copy[int((size - 1) / 2)]
    else:
        return (copy[int(size/2 - 1)] + copy[int(size/2)]) / 2
if __name__ == '__main__':
    import doctest
    doctest.testmod()

実行結果

~/python $ python line14.py
**********************************************************************
File "/data/data/com.termux/files/home/python/line14.py", line 3, in __main__.median
Failed example:
    median([2, 9, 9, 7, 9, 2, 4, 5, 8])
Expected:
    6 #change to 7 in order to pass the test
Got:
    7
**********************************************************************
1 items had failures:
   1 of   1 in __main__.median
***Test Failed*** 1 failures.
~/python $ 

解説

前回は単体テストの機能だったんですけど、今回は「ドキュメント兼テスト」です。
こちらの方がいいかもしれない。

関数「median」についてはこちらに解説しました。


まず、こちらのオレンジで囲んだ部分は全て文字列です。

わざとエラーケースで書かれてあるのでわかりにくいのですが、普通ならおそらくこのように書きます。

def median(pool):
    '''Statistical median.
    データが奇数個のとき、真ん中のデータが中央値となります
    >>> median([1, 2, 3, 4, 5])
    3
    
    データが偶数個のとき、真ん中の2つのデータの平均が中央値となります
    >>> median([1, 2, 3, 4])
    2.5
    
    もしデータが昇順に並んでいなければ、並べ替えて中央値を決定します
    >>> median([2, 9, 9, 7, 9, 2, 4, 5, 8])
    7
    '''
    copy = sorted(pool)
    size = len(copy)
    if size % 2 == 1:
        return copy[int((size - 1) / 2)]
    else:
        return (copy[int(size/2 - 1)] + copy[int(size/2)]) / 2
if __name__ == '__main__':
    import doctest
    doctest.testmod()

トリプルクォーター内が関数の説明です。
これを「-v」で実行すると、次のような結果が出力されます。

~/python $ python line14a.py -v
Trying:
    median([1, 2, 3, 4, 5])
Expecting:
    3
ok
Trying:
    median([1, 2, 3, 4])
Expecting:
    2.5
ok
Trying:
    median([2, 9, 9, 7, 9, 2, 4, 5, 8])
Expecting:
    7
ok
1 items had no tests:
    __main__
1 items passed all tests:
   3 tests in __main__.median
3 tests in 2 items.
3 passed and 0 failed.
Test passed.
~/python $

ようするに、文字列で

    >>> median([1, 2, 3, 4, 5])
    3

と書いた部分を探し出してインタプリタで実行し、結果を次の行と比較するわけです。
これは、
コードのドキュメントでもあり、
テストケースでもあり
テストの実行もできる
という優れものです。

インタプリタ言語なればこそとも言えますが、言語仕様にこういうものを取り込むというのはすごいですね。

Pythonのドキュメントには次のようにあります。

モジュールの docstring (ドキュメンテーション文字列) 中にある対話実行例のすべてが書かれている通りに動作するか検証することで、docstring の内容が最新かどうかチェックする。

テストファイルやテストオブジェクト中の対話実行例が期待通りに動作するかを検証することで、回帰テストを実現します。

入出力例を豊富に使ったパッケージのチュートリアルドキュメントが書けます。入出力例と解説文のどちらに注目するかによって、ドキュメントは「読めるテスト」にも「実行できるドキュメント」にもなります

https://docs.python.org/ja/3/library/doctest.html

「読めるテスト」
「実行できるドキュメント」
面白い。

ソフトウェアのドキュメントというのは実にたくさんあります。こういうものを利用すれば作成するドキュメントの量も抑えることができて、強いては生産性も向上するやもしれません。


文字列をどこにおいてもいいのかどうかが気になったので試してみました。

'''
(1)データが奇数個のとき、真ん中のデータが中央値となります
>>> median([1, 2, 3, 4, 5])
3
'''
def median(pool):
    '''Statistical median.
    (2)データが偶数個のとき、真ん中の2つのデータの平均が中央値と なります
    >>> median([1, 2, 3, 4])
    2.5
    '''
    copy = sorted(pool)
    size = len(copy)
    if size % 2 == 1:
        return copy[int((size - 1) / 2)]
    else:
        return (copy[int(size/2 - 1)] + copy[int(size/2)]) / 2
    '''
    (3)もしデータが昇順に並んでいなければ、並べ替えて中央値を決定します
    >>> median([2, 9, 9, 7, 9, 2, 4, 5, 8])
    7
    '''
if __name__ == '__main__':
    import doctest
    doctest.testmod()

結果は・・・。

~/python $ python line14b.py -v
Trying:
    median([1, 2, 3, 4, 5])
Expecting:
    3
ok
Trying:
    median([1, 2, 3, 4])
Expecting:
    2.5
ok
2 items passed all tests:
   1 tests in __main__
   1 tests in __main__.median
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
~/python $

ふーん。
3つ目の(3)は試験されないんだ。
個人的には(1)の場所に書くのが好きかな。


参考


この記事が気に入ったらサポートをしてみませんか?