TDD Advent Calendar 2013 3日目: vim と py.test で TDD
えせ (vimmer|Pythonista) の comutt です。
TDD Advent Calendar 2013 3日目です。
3日目担当なのに、日付は4日になって6時間ほど経過しています。ごめんなさい。 記事投稿日は詐称してます!
vim と py.test で TDD
vim と py.test で TDD する方法を書いてみます。
内容はかなり手抜き気味なので突っ込みどころ満載なのですが、お許しを!
使うもの一覧
- vim
+python3
feature 付きでコンパイルされた vim
- py.test
- Python 用のユニットテストフレームワークの一つです
- PyHamcrest
- Python 用の Hamcrest ライクなアサーションライブラリです
- pytest.vim
- py.test を vim から使うための vim プラグインです
- pyflakes-vim
- vim 上で Python コードを on-the-fly でコンパイルしてエラー箇所を表示してくれる vim プラグインです
- 本家のは Python3 に対応していないので、対応版を comutt/pyflakes-vim に置いてあります
- 2013/12/04(つまり書いてる今日)にプルリクエストを出してみたので、そのうち取り込まれるかもしれません
セットアップ
vim
+python3
または +python3/dyn
feature がバンドルされていることを確認してください。
vim --version | grep python
+python
でもいいのですが、 Python2 はメソッド名に日本語が使えないので可能なら Python3 を使いましょう。
ソースからコンパイルする場合は、 --enable-python3interp
(+python3/dyn
の場合は --enable-python3interp=dynamic
) オプション付きで configure
した上でコンパイルします。
ちなみに、おそらく私の環境の問題ではありますが、 MacOS X 10.7 上で --enable-python3interp=dynamic
でコンパイルされた vim で python3
コマンドを使おうとすると、segfault でクラッシュしてしまいました。
プラグインは好きな方法で入れて下さい。
ここでは、 neobundle.vim を使った方法で説明します。
py.test と PyHamcrest
pip
でインストール
pip install pytest PyHamcrest
pytest.vim と pyflakes-vim
.vimrc
に以下の行を追加します
""" pyflakes-vim """ 本家のは Python3 に対応していないため、 comutt/pyflakes-vim を使います NeoBundle "comutt/pyflakes-vim", "python3-support" let g:pyflakes_python_version = 3 """ pytest.vim NeoBundle "alfredodeza/pytest.vim"
vim の設定
pytest.vim のコマンドでよく使うもののキーバインドを設定しておきます。
以下は私が使っている設定です。
""" Binding for Pytest " ファイル全体に py.test を実行 nmap <silent><Leader>pf <Esc>:Pytest file<CR> " フォーカスのあたってるクラスに py.test を実行 nmap <silent><Leader>pc <Esc>:Pytest class<CR> " フォーカスのあたってるメソッドに py.test を実行 nmap <silent><Leader>pm <Esc>:Pytest method<CR> " エラーメッセージなどのログウィンドウ表示をトグル nmap <silent><Leader>ps <Esc>:Pytest session<CR>
実際にやってみる
ディレクトリ構成
以下のようなディレクトリ構成でやってみます。
python-helloworld/ ├── classes │ ├── __init__.py │ ├── helloworld.py │ └── tests │ ├── __init__.py │ └── test_helloworld.py └── helloworld.py
py.test は __init__.py
が無いディレクトリをパッケージルートディレクトリとみなすため、必ず __init__.py
を適切に配置する必要があります。
ソースコード
プロダクションコード: helloworld.py
# helloworld.py #!/usr/bin/env python3 class HelloWorld: def greet(self): return "Hello, World!"
テストコード: test_helloworld.py
import pytest from hamcrest import * from classes.helloworld import HelloWorld class TestHelloWorld: @pytest.fixture def sut(self): return HelloWorld() def testGreet_HelloWorldメッセージが返ること(self, sut): expected = "Hello, World!" actual = sut.greet() assert_that(actual, equal_to(expected)) # PyHamcrest を使わずに assert する場合は以下のように書く assert actual == expected
とりあえずテストを実行する
テストコードの説明は後に回して、テストコードを実行してみましょう。
テストコードの実行は、テストコードファイルを開いた状態で、
:Pytest file
, :Pytest class
, :Pytest method
などを使って実行します。
:Pytest class
を使うときは対象のクラススコープ内にカーソルがある状態で実行し、 :Pytest method
はメソッドスコープ内にカーソルがある状態で実行します。
ただし、上記のようにテストコードにメソッド名に日本語を含む場合、pytest.vim プラグインがうまくメソッド認識をしてくれず、 :Pytest method
では実行できません。
実行すると、以下のにグリーンバーが表示されます。
失敗した場合は以下のように表示されます。
:Pytest session
を実行すると、エラー内容の詳細を知ることができます。(再度 :Pytest session
を実行すると閉じる)
py.test を使ってみて便利だと思うところ
それほど使い込んでるわけではないのですが、以下の点が便利だと思うところです。
(1) @pytest.fixture
デコレータでテストフィスクチャを作成できる
@pytest.fixture def sut(self): return Testee() def test_XXX(self, sut): # some setup, exercises, asserions... sut.exercise()
@pytest.fixture デコレータを使ったメソッドは各テストメソッド実行前にフィクスチャ生成処理が実行され、 テストメソッドの引数(デコレータを使ったメソッド名と同名の引数)にインスタンスが渡されます。
unittest.TestCase
を使う場合、私は以下のように書いていたのですが、 @pytest.fixture
を使う方が私は self.
を書かなくていいのもあって好きです。
def setUp(self): self._sut = Testee() def test_XXX(self): # some setup, exercises, asserions... self._sut.exercise()
(2) tmpdir が便利
tmpdir が便利すぎます。手放せません。
tmpdir が使いたいがために、 py.test を使ってると言ってもいいくらいです。
たとえば、準備処理でディレクトリを作成してファイルを書き込む、というような場合以下のように書きます。
def test_XXX(self, sut, tmpdir): dataDirectory = tmpdir.mkdir('data') sampleFile = dataDirectory.join("sample.txt") sampleFile.write("First line\nSecond line\n")
こうすることで、システムのテンポラリディレクトリに pytest-XXX (XXXはテスト実行ごとに採番されるシーケンスナンバー)というディレクトリが作成され、そこをルートディテクトリとしてテンポラリディレクトリ・ファイル作成がされます。
pytest-XXX ディレクトリはある程度(直近数回分*1)の数だけ保持され、自動的に古い物から削除されます。楽ちん!
おわりに
突然おわりかよ、と思われた方すみません。
ここまで書いて力尽きてしまいました。
TDD とタイトルに入れるからには、本来は jedi-vim などのリファクタプラグインの紹介もするべきでしたが、私自身がまだ使いこなせてないという問題があり、省略しました。
また、 Quick JUnit のようにテストケース<->プロダクションコードの簡単なスイッチができると俄然 TDD がすすみます。
プラグインがあるはずと調べてみたのですが、うまいこと使えるものを見つけられず、現在はゴミみたいな vimscript で切り替えています。 どなたか良いプラグインがあったら教えてください!
*1:すみません、正確な保持数は調べていません