コマンドラインオプションのテストを書く
見てしまった、、、
で、optparseをテストしたいのだが、どうすんべ
昨日の今日なのでやりましょう、頼まれていませんがー(> <)
テスト対象のサンプルとして http://d.hatena.ne.jp/t2y-1979/20101124/1290534464 から optparse モジュール を利用している parse_option() 関数だけ抜き出してきます。
#!/usr/bin/env python # -*- coding: utf-8 -*- import optparse import os import sys # Globals VERSION = "0.30" def parse_option(): usage = "usage: %prog [option] eratos.log" ver = "%s %s" % ("%prog", VERSION) parser = optparse.OptionParser(usage, version=ver) parser.add_option("-f", "--from", dest="date_from", metavar="YYYYMMDDHHMM", help="filter with from date") parser.add_option("-t", "--to", dest="date_to", metavar="YYYYMMDDHHMM", help="filter with date to") parser.add_option("-q", "--query", dest="query", metavar="STRING", help="filter with string") parser.add_option("-o", "--outputfile", dest="output", metavar="OUTPUT_FILE", default="checked.log", help = "output file name") parser.add_option("-e", "--error", dest="error", action="store_true", default=False, help="filter with error log message") parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, help="print log on standard output") opts, args = parser.parse_args() if args and os.access(args[0], os.R_OK): return opts, args else: print parser.print_help() sys.exit(0) def main(): opts, args = parse_option() if __name__ == "__main__": main()
Pikzie (ピクジー) のデータ駆動テストを利用してテストを書きます*1。デコレータでデータを登録できるのですっきり書けます。
# -*- coding: utf-8 -*- import sys import tempfile import pikzie from StringIO import StringIO # target modules for test from log_analyzer import VERSION, parse_option class TestCommandLineParser(pikzie.TestCase): """Test for command line parser""" def setup(self): self.log_file = tempfile.NamedTemporaryFile() self.log_name = self.log_file.name sys.stdout, sys.stderr = StringIO(), StringIO() def teardown(self): self.log_file.close() @pikzie.data("ver01", ["prog", "--version"]) def test_version(self, argv): sys.argv = argv self.assert_raise_call(SystemExit, parse_option) self.assert_equal(sys.stdout.getvalue().split()[-1], VERSION) @pikzie.data("from03", ["prog", "--from", "20101124" ]) @pikzie.data("from02", ["prog", "-f", "201011241952", "-e"]) @pikzie.data("from01", ["prog", "-f", "201011241952"]) def test_date_from(self, argv): argv.append(self.log_name) sys.argv = argv opts, args = parse_option() self.assert_equal(opts.date_from, argv[2]) self.assert_equal(args, argv[-1:]) @pikzie.data("err_true03", ["prog", "-e", "-v", "--error"]) @pikzie.data("err_true02", ["prog", "-t", "201011242045", "-e"]) @pikzie.data("err_true01", ["prog", "-e"]) def test_error_true(self, argv): argv.append(self.log_name) sys.argv = argv opts, args = parse_option() self.assert_true(opts.error) self.assert_equal(args, argv[-1:]) @pikzie.data("err_false03", ["prog", "--outputfile", "t.log"]) @pikzie.data("err_false02", ["prog", "-q", "クエリ", "-v"]) @pikzie.data("err_false01", ["prog", "-f", "201011242035"]) def test_error_false(self, argv): argv.append(self.log_name) sys.argv = argv opts, args = parse_option() self.assert_false(opts.error) self.assert_equal(args, argv[-1:]) @pikzie.data("fail05", ["prog", "-v", "--detarame", "-e"]) @pikzie.data("fail04", ["prog", "-o", "-t", "201011242036"]) @pikzie.data("fail03", ["prog", "-q", "a", "--outputfile"]) @pikzie.data("fail02", ["prog", "-f", "--error", "20101124"]) @pikzie.data("fail01", ["prog", "--from"]) def test_parser_fail(self, argv): argv.append(self.log_name) sys.argv = argv self.assert_raise_call(SystemExit, parse_option) @pikzie.data("noargs01", ["prog", "-v"]) def test_no_args(self, argv): sys.argv = argv self.assert_raise_call(SystemExit, parse_option)
テストを実行します。
$ python test_log_analyzer_optparse.py ................ Finished in 0.043 seconds 16 test(s), 33 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
全部、成功しました!テストはやりだすと成功するのが嬉しくて楽しくなってきます。
Pikzie の良いところの1つはテストが失敗したときの見易さもあります。ここではわざと test_date_from() のテストが失敗するように "from02" のデータを変更します。
(変更前) @pikzie.data("from02", ["prog", "-f", "201011241952", "-e"]) (変更後) @pikzie.data("from02", ["prog", "-v", "-f", "201011241952", "-e"])
テストを実行します。
$ python test_log_analyzer_optparse.py .F.............. 1) Failure: TestCommandLineParser.test_date_from (from02): self.assert_equal(opts.date_from, argv[2]) data: ['prog', '-v', '-f', '201011241952', '-e', '/tmp/tmpeX2M2B'] test_log_analyzer_optparse.py:33: self.assert_equal(opts.date_from, argv[2]) expected: <'201011241952'> but was: <'-f'> Finished in 0.039 seconds 16 test(s), 31 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
どのテストの失敗して、原因が何なのかが "expected" と "but was" の diff が付いていて分かり易いです。またデータ駆動テストなので他の "from01" や "from03" のテスト実行には影響がありません。"from02" のテストだけエラーだと分かるので、"from02" で登録したデータに問題があるんじゃ、、、とデバッグし易いですね。
次に parse_option() 関数内では print でヘルプを出力しています。
...
print parser.print_help()
...
テスト結果に支障はありませんが、StringIO モジュール 等を使うと不要な出力をターミナルへ表示させなくて済みます。
... sys.stdout, sys.stderr = StringIO(), StringIO() ...
さらにバージョン表示のテストにも応用できたりします。
... def test_version(self, argv): sys.argv = argv self.assert_raise_call(SystemExit, parse_option) self.assert_equal(sys.stdout.getvalue().split()[-1], VERSION) ...