doctestを使ってJython本の第5章のチェックが完了

doctest++

Jython2.1から2.2への変化で

#2.1
>>> id(a)
27458013

#2.2
>>> id(a)
8

idが連番になったりとか

#2.1
>>> int(1)
<jclass org.python.core.PyInteger at 32762551>

#2.2
>>> int(1)
<type 'int'>

実装クラスの名前がさらけ出されなくなったりとか、といくつか改善点があるんだけどそのせいで2.1で書いたコードのかなりの部分を修正しないといけない。

→doctest(Python標準の「ドキュメント中の対話的実行ぽい部分」をテストするライブラリ)を使えばいいんじゃない?と気がつく。

# C:\Home\Projects\jythonbook\test.py

def _test():
    import sys
    sys.path.insert(0, r"C:\Home\Projects\jythonbook")
    import doctest, test
    s = file(r"genko\05.txt").read()
    s = s.replace("</pre>", "")
    s = s.replace("__main__", "test")
    test.__doc__ = s
    doctest.testmod(test)
    print "test finished."

if __name__ == "__main__":
    print "test start"
    _test()

出力結果を見やすく改行してある部分や、CPythonやJython2.1での出力結果を見せたい部分とかでテストを抑制したいケースがある。
「>>>」の前に「\n!」を挿入して対話的実行だと見なされないようにした。

そうすると

!>>> hoge()
{some long long long print}

>>> x = _

のような「一旦オブジェクトを作って、それを読者に見せてから変数に代入」というパターンのコードでxに入るものが変わるようになってエラー。

>>> x = hoge()

!>>> x
{some long long long print}

こういう書き方で大して問題があるわけでもないのでそういうスタイルに変更した。

実行すると

test start
*****************************************************************
Failure in example: id(a)
from line #1386 of test
Expected: 5123456
Got: 5
*****************************************************************
1 items had failures:
   1 of 189 in test
***Test Failed*** 1 failures.
test finished.

こんな感じの出力が出る。第5章にある全部のコードを実行して、そのうち何個がエラー、とな。最初98個くらいあったんだけど、preの置換と、__main__のtestへの置換をしたら40個になった。あとはその40個をプチプチつぶすだけ。とはいえ、今までコードをコンソールに貼り付けて実行して目視で正しいか確認していたのが、実行するところまでは自動でやってくれるようになったのでとても楽になった。

        • -

2000行の原稿の中に189件のコードがあるわけか。

「呼吸をするようにコードを書く」ってかっこいいですよね。僕の原稿は「息継ぎをするようにコードを書く」です。コードが句読点です(ぉぃぉぃ

        • -

追記。テストスクリプトの名前を__main__.pyにした。

def _test():
    import sys
    sys.path.insert(0, r"C:\Home\Projects\jythonbook\test")
    import doctest, __main__
    s = file(r"..\genko\04.txt").read()
    s = s.replace("</pre>", "")
    __main__.__doc__ = s
    doctest.testmod(__main__)
    print "test finished."

if __name__ == "__main__":
    print "test start"
    _test()

うっかりスルーしているミスとかに気づけるからいいな。例えばimportし忘れていると、doctestが機械的に上から実行していくからエラーになる。
jarray.array([72, 105, 33], "b")が2.1ではarray([72, 105, 33], byte)と表示されたのに2.2からarray('b',[72, 105, 33]) になった、なんてのは見落としそう。

        • -
Failure in example: Counter.__dict__
from line #2025 of __main__
Expected: {'__doc__': None, 'numCounter': 0, '__module__': '__main__'}
Got:
{'__dict__': <attribute '__dict__' of 'Counter' objects>, 'numCounter': 0, '__module__': '__main__'}

へー、Jython2.2からは__dic__が見えるようになったんだー。

        • -

あー。ある名前xを使って、また別のところでそれを参照して使っているにもかかわらず、その二つの間でxに値を代入するコードを入れていてエラーになっていた。こういうのなんか、読者が順番に実行したらはまるのに自分ではスルーしてしまいそうだ。doctest++;

        • -

やったー。4章のエラーも全部消したー!