Guard + Doctest で自動テストな環境

某社のエンジニアさんによるライブコーディングにて知った環境を自分でも作ってみたのでメモ。
どういう環境かというと、 Python のソースファイルを保存する度に 自動的に Doctest を走らせる、 という環境です。

「もっとこうした方が良いよ!」とか「これ間違ってるぞ!」とか 何かあったら言ってもらえるととても嬉しいです。

使うモノ

  • Guard::Shell(guard-shell)
  • Doctest
  • 好きなエディタとか

Ruby と Gem はインストールされている前提でオネシャス。 ちなみにMac環境です。 Homebrew でいろいろしてる環境です。

Guard::Shell

Guard は、ファイルの変更とかを検知して、 何かする Gem です(たぶん)。 Guard::Shel は、 その中?でも、任意のコマンドを実行できるやつです。 まず、下記のコマンドで Guard::Shell をインストールします。 (自分がインストールしたときには、何かエラーが出た様な気がしますが、 画面の指示に従って操作したら大丈夫だったような? 自分用メモとして致命的ですがスルーで……)

$ sudo gem install guard-shell

インストールができたら、試しにコマンドを実行してみる。

$ guard
command not found: guard

……。 PATH 通っていないやん。 なんかGem周りに PATH が通っていなかったようなので、 とりあえず下のディレクトリを PATH に追加。 Ruby がアップデートされたらアレな気もするけどシラネw

/usr/local/Cellar/ruby/1.9.3-p385/bin

これで guard コマンドが使えるようになりました。

Doctest

Python に標準で付属。

使ってみる

てきとーな例で、自動テストを実行するところまでやってみます。 今後 sumtest というディレクトリで作業をします。 まず始めに次のコマンドを実行すると、、

$ guard init
12:12:12 - INFO - Writing new Guardfile to /Path/to/dir/sumtest/Guardfile
12:12:12 - INFO - shell guard added to Guardfile, feel free to edit it

という出力と共に、ディレクトリ内に "Guardfile" というファイルが作成されます。 このファイルは、初期状態では次のようになっています。

# A sample Guardfile
# More info at https://github.com/guard/guard#readme

# Add files and commands to this file, like the example:
#   watch(%r{file/path}) { `command(s)` }
#
guard 'shell' do
  watch(/(.*).txt/) {|m| `tail #{m[0]}` }
end

.txt なファイルが変更されたときに、 そのファイルに対して tail コマンドを実行している感じです。

今回は、Python のソースファイルが変更されたときに Doctest を実行したいので、次のように書き換えます。

guard 'shell' do
  watch(/(.*).py$/) {|m|
    `python -m doctest -v #{m[0]}`
  }
end

watch の中の正規表現でファイル名が .py で終わるファイルを見てます。 /(.*).py/ にすると、.pyc とかのファイルに対しても コマンドを実行してしまうことになるのでアレです。

Guardfile を編集後、 sumtest 内にて guard を実行すると、、、

$ guard
12:12:12 - INFO - Guard uses TerminalTitle to send notifications.
12:12:12 - INFO - Guard is now watching at '/Path/to/dir/sumtest'
[1] guard(main)>

的な出力が出てきます。 これで準備は完了なので、 あとはお目当てのファイルを編集してあげればおkです。

sum.py という下記のファイルで実験してみます。

# -*- coding: utf-8 -*-
def sum(a, b):
    """
    足し算して返すよ
    >>> sum(1, 2)
    0
    """
    return a + b

このファイルを保存すると、、、

Trying:
    sum(1, 2)
Expecting:
    0
**********************************************************************
File "sum.py", line 4, in sum.sum
Failed example:
    sum(1, 2)
Expected:
    0
Got:
    3
1 items had no tests:
    sum
**********************************************************************
1 items had failures:
   1 of   1 in sum.sum
1 tests in 2 items.
0 passed and 1 failed.
***Test Failed*** 1 failures.

こんな感じでテストが実行され、そしてコケます。 コケるテストを書いたので当たり前ですw

次のように、テスト部分の 関数の実行結果を正しい値に書き換えて保存すると、、、

# -*- coding: utf-8 -*-
def sum(a, b):
    """
    足し算して返すよ
    >>> sum(1, 2)
    3
    """
    return a + b

ちゃんとテストが自動的に実行され、、、

Trying:
    sum(1, 2)
Expecting:
    3
ok
1 items had no tests:
    sum
1 items passed all tests:
   1 tests in sum.sum
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

はい。テストが通りました。

こんな感じでテスト自動実行の環境が一応できました。

最後に

pyautotest なるものが(複数個)公開されているのですが、 doctest でテストを自動実行する方法がイマイチ理解できず、 この記事の方法になりました。

車輪の再発明感が否めないので、 スマートな方法があったら参考となる資料を教えて頂けると本当に嬉しいです。