2010年10月26日火曜日

Python 3のunittestでprint関数をテスト

Python 3でprint関数の出力をテストしたい場合、doctestを使うのがPythonらしいやり方のようなのですが、doctestの方は使い方のサンプル程度にして、しつこいテストはunittestでテストした方がすっきりしそうなので、unittestでprint関数をテストする方法を探してみました。

するとどうやら、標準出力をio.StringIO()にしてしまうのが定番のようでしたので、さっそく試してみました。



以下がテスト対象とテストのコードのサンプルです。
class PrintNicoChannelInfo():
    def __init__(self, nicochannel):
        print('id: {}'.format(nicochannel))
        print('status: 200')
        print('title: 「俺の妹がこんなに可愛いわけがない」動画配信チャンネル')
        print('description: ニコニコチャンネル: 「俺の妹がこんなに可愛いわけがな
い」公式配信チャンネルです。TV放映終了後、一週間無料配信!')
        print('owner: 株式会社角川コンテンツゲート')
#!/usr/bin/env python3.1
import unittest2 as unittest
from io import StringIO
import sys
from nicochannel import PrintNicoChannelInfo

INFO_CH639 = '\n'.join(('id: ch639',
                        'status: 200',
                        'title: 「俺の妹がこんなに可愛いわけがない」動画配信チャンネル',
                        'description: ニコニコチャンネル: 「俺の妹がこんなに可愛いわけがない」公式配信チャンネルです。TV放映終了後、一週間無料配信!',
                        'owner: 株式会社角川コンテンツゲート',
                        ''))

class TestPrintNicoChannelInfo(unittest.TestCase):
    def setUp(self):
        self.held, sys.stdout = sys.stdout, StringIO()

    def test_ch639(self):
        PrintNicoChannelInfo('ch639')
        self.assertEqual(sys.stdout.getvalue(), INFO_CH639)

    def tearDown(self): 
        sys.stdout = self.held 

if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(TestPrintNicoChannelInfo)
    unittest.TextTestRunner(verbosity=2).run(suite)

上記のテストコードで "import unittest2 as unittest" にしてあるのは、Python 3.2で導入される予定の新しいunittestモジュールが、現在使用しているPython 3.1.2にはないので、外部モジュールとして先行リリースされているunittest2py3kを使用しているからです。
もうちょっと上手いやり方がありそうですが、一応動いているのでとりあえずこのままにしています。

さて、このテストを実行すると、結果は、
$ ./test_nicochannel.py 
test_ch639 (__main__.TestPrintNicoChannelInfo) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
のように表示されました。上手くいったようです。

なお、新しいunittestでテスト中にエラーが発生した場合、TextTestRunner()でverbosity=2を指定していると、
$ ./test_nicochannel.py
test_ch639 (__main__.TestPrintNicoChannelInfo) ... FAIL

======================================================================
FAIL: test_ch639 (__main__.TestPrintNicoChannelInfo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test_nicochannel.py", line 20, in test_ch639
    self.assertEqual(sys.stdout.getvalue(), INFO_CH639)
AssertionError: 'id: ch639\nstatus: 404\ntitle: 「俺の妹がこんなに可愛いわけがない」動画配信チャンネル\ndescription: ニコニコチャ [truncated]... != 'id: ch639\nstatus: 200\ntitle: 「俺の妹がこんなに可愛いわけがない」動画配信チャンネル\ndescription: ニコニコチャ [truncated]...
  id: ch639
- status: 404
?         ^ ^
+ status: 200
?         ^ ^
  title: 「俺の妹がこんなに可愛いわけがない」動画配信チャンネル
  description: ニコニコチャンネル: 「俺の妹がこんなに可愛いわけがない」公式配信チャンネルです。TV放映終了後、一週間無料配信!
  owner: 株式会社角川コンテンツゲート


----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
のように文字単位で差分を表示してくれました。ただし、全ての文字を半角扱いにしているらしく、いわゆる全角文字が混じるとズレが発生するのは避けられないようです。

従来のunittestの場合は、
$ ./test_nicochannel.py 
test_ch639 (__main__.TestPrintNicoChannelInfo) ... FAIL

======================================================================
FAIL: test_ch639 (__main__.TestPrintNicoChannelInfo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test_nicochannel.py", line 20, in test_ch639
    self.assertEqual(sys.stdout.getvalue(), INFO_CH639)
AssertionError: 'id: ch639\nstatus: 404\ntitle: 「俺の妹がこんなに可愛いわけがない」動画配信チャンネル\ndescription: ニコニコチャンネル: 「俺の妹がこんなに可愛いわけがない」公式配信チャンネルです。TV放映終了後、一週間無料配信!\nowner: 株式会社角川コンテンツゲート\n' != 'id: ch639\nstatus: 200\ntitle: 「俺の妹がこんなに可愛いわけがない」動画配信チャンネル\ndescription: ニコニコチャンネル: 「俺の妹がこんなに可愛いわけがない」公式配信チャンネルです。TV放映終了後、一週間無料配信!\nowner: 株式会社角川コンテンツゲート\n'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
のような表示になります。

0 件のコメント:

コメントを投稿