【Python】doctestでdictを扱う

課題

テストの自動化にdoctestは良いな、と思っていたらjsonのテストを行うとテストが通らない問題が発生した。

from datetime import datetime,timedelta
import json

def testFunc(test):
    """
   >>> testFunc("Hello!")
    {"test":"Hello!"}
    """
    
    return {"test":test}
Failed example:
    testFunc("Hello!")
Expected:
    {"test":"Hello!"}
Got:
    {'test': 'Hello!'}
1 items had no tests:
    test
**********************************************************************
1 items had failures:
   1 of   1 in test.testFunc
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.

解決方法

以下のようにすると良いらしい。

#method1
>>> sorted(testFunc("Hello!").items())
[("test","Hello!")]

又は

#method2
>>> testFunc("Hello!")== {"test":"Hello!"}
True

なお、method2は期待する出力と関数内での出力の順番が異なっていても問題なくテストは通る

from datetime import datetime,timedelta
import json

def func(test):
    """
    >>> testFunc("Hello!")=={"hello":"World","test":"Hello!"}
    True
    """
    
    return {
        "test":test",
        "hello":"World"
    }

流石に改行を入れるとテストコードが通らないが、きちんとバックスラッシュ()を入れれば改行やインデントも入れられるため、可読性を上げられる。

#NG
>>> testFunc("Hello!")=={
            "test":"Hello!",
            "hello":"World"
        }
    True

#OK
>>> testFunc("Hello!")=={\
            "test":"Hello!",\
            "hello":"World"\
        }
    True
#NG
Failed example:
    testFunc("Hello!")=={
Exception raised:
    Traceback (most recent call last):
      File "C:\ProgramData\Anaconda3\lib\doctest.py", line 1329, in __run
        compileflags, 1), test.globs)
      File "<doctest test.testFunc[0]>", line 1
        testFunc("Hello!")=={
                            ^
    SyntaxError: unexpected EOF while parsing
1 items had no tests:
    test
**********************************************************************
1 items had failures:
   1 of   1 in test.testFunc
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.

#OK
Trying:
    testFunc("Hello!")=={            "test":"Hello!",            "hello":"World"        }
Expecting:
    True
ok
1 items had no tests:
    test
1 items passed all tests:
   1 tests in test.testFunc
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

原因

doctestはあくまで、出力が完全一致しているか確認するだけで、等価性のチェックは行わないらしい。 例えば、以下の値は並び順が異なるが本来は同じ値である。

#Example1
{
  "key":"Hello ",
  "value":"World"
}
#Example2
{
  "value":"World!",
  "key":"Hello "
}

しかし、pytdocでのテストでは並び順が異るためエラーと認識される

どうやら、課題で挙げた問題はこの延長線上にあるようだ。

所感

doctestはドキュメントをコード内に書きながらテストコードを書けるため便利だが、大きなdictやlistが出力されるコードではunittestのほうが使い勝手が良いかもしれない。 ただ、現状はそこまで大きな出力を扱うコードを書く予定がないので、まずはdoctestでドキュメントを書きながらテストコードを充実させていくことを主眼に置きたい。

参考

stackoverflow.com