Doctest

วิธีที่ง่ายที่สุดในการทำ TDD บน Python คือ ใช้ module doctest แล้วแนบคำสั่งที่ต้องการทดสอบพร้อมผลลัพท์ (เขียนรูปแบบเดียวกับการสั่งงาน Python shell) ลงไปที่ docstring ของฟังก์ชัน เช่นนี้

    def say_hi(name):
        '''
        >>> say_hi('World')
        Hello World!
        >>> say_hi('Robert')
        Hi Robert!
        '''

        print('Hi {}!'.format(name))

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

เซฟเป็นไฟล์ to_test.py แล้วเรียกโปรแกรมนี้ผ่าน terminal จะเห็นผลลัพท์ดังนี้

    $ python to_test.py
    **********************************************************************
    File "to_test.py", line 3, in __main__.say_hi
    Failed example:
        say_hi('World')
    Expected:
        Hello World!
    Got:
        Hi World!
    **********************************************************************
    1 items had failures:
       1 of   2 in __main__.say_hi
    ***Test Failed*** 1 failures.

ซึ่งหมายความว่า test เราไม่ผ่านอยู่ 1 test case เมื่อส่งข้อความ 'World' ให้กับฟังก์ชั่น คราวนี้ลองปรับปรุงบรรทัดที่สั่ง print ให้เป็น

        print('Hello' if name=='World' else 'Hi', '{}!'.format(name))

เมื่อสั่ง $ python to_test.py อีกรอบ จะไม่ได้ค่าอะไรคืนมาแล้ว ซึ่งก็คือ test ผ่านหมดนั่นเองครับ

หรือจะขอดูรายงานแบบเต็มๆ ก็ย่อมได้ โดยการเพิ่ม argument -v เข้าไปเช่นนี้

$ python to_test.py -v
Trying:
    say_hi('World')
Expecting:
    Hello World!
ok
Trying:
    say_hi('Robert')
Expecting:
    Hi Robert!
ok
1 items had no tests:
    __main__
1 items passed all tests:
   2 tests in __main__.say_hi
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

สำหรับส่วน docstring เราสามารถแนบคำอธิบายอื่นๆ ลงไปกับ test ได้ โดยต้องเว้นบรรทัดว่างเอาไว้เพื่อบอกให้รู้ว่าส่วนไหนที่หมด output จาก test แล้ว และสามารถเขียนส่วน input test หลายบรรทัดได้ สำหรับกรณี error สามารถใส่ ... ตามตัวอย่าง เพื่อบอกว่าไม่ต้องสนใจ output ส่วนที่เป็นรายงานอย่างละเอียดก็ได้ครับ

    def say_hi(*names):
        r'''
        Says hi to everyone, except world.

        >>> say_hi('World')
        Hello World!
        >>> say_hi('Robert')
        Hi Robert!
        >>> say_hi(
        ...         'Batman',
        ...         'Joker',
        ... )
        ... 
        Hi Batman!
        Hi Joker!

        Raise exception if no argument given.
        >>> say_hi()
        Traceback (most recent call last):
          ...
        Warning: No one to greet!
        '''

        if not names:
            raise Warning('No one to greet!')

        for name in names:
            print('Hello' if name=='World' else 'Hi', '{}!'.format(name))

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

นอกจากนี้ ใน production code จริง การเรียกฟังก์ชัน doctest.testmod() ลงไปอาจไม่เหมาะสมนัก เราสามารถยกส่วนนั้นทิ้งไปได้ (เหลือแต่ code เปล่าๆ กับ docsting ที่มี test case อยู่) แล้วสั่งผ่าน shell เช่นนี้แทน

    $ python -m doctest to_test.py

หรือถ้าส่วน test case นั้นใหญ่มากจนไม่ควรจะไปอยู่กับ docstring เราอาจยก test case มาเขียนเป็นไฟล์ใหม่ที่บรรจุเฉพาะข้อมูล test ไปเลยก็ได้ ดังนี้

ไฟล์ to_test.py

    def say_hi(*names):
        '''Says hi to everyone, except world.'''

        if not names:
            raise Warning('No one to greet!')

        for name in names:
            print('Hello' if name=='World' else 'Hi', '{}!'.format(name))

ไฟล์ info_test.txt

Test Module
===========

    >>> from to_test import *

Test Function ``say_hi``
------------------------

``say_hi`` is a function that will greet everyone with 'Hi'.

    >>> say_hi('Robert')
    Hi Robert!

``say_hi`` can also greet many people at the same time.

    >>> say_hi(
    ...         'Batman',
    ...         'Joker',
    ... )
    ... 
    Hi Batman!
    Hi Joker!

But ``say_hi`` would do traditional 'Hello World!' when met World.

    >>> say_hi('World')
    Hello World!

Lastly, ``say_hi`` will talk no word when no one is listening.

    >>> say_hi()
    Traceback (most recent call last):
      ...
    Warning: No one to greet!

เรียกผ่าน shell ไม่ต่างจากเดิม เพียงแค่เปลี่ยน argument เป็นไฟล์ที่เขียน test เอาไว้แค่นั้นครับ

$ python -m doctest info_test.txt

Nattawut Phetmak

Jack of all Trades

blog comments powered by Disqus