nose は Python のテストフレームワークのひとつ。 特徴のひとつとしてプラグイン機構があるためテストランナーの挙動をカスタマイズできる点が挙げられる。
プラグインの書き方は次のページに詳しく書かれている。 Writing Plugins — nose 1.3.7 documentation
今回は上記のページを元に簡単なプラグインをいくつか書いてみることにする。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.11.3 BuildVersion: 15D21 $ python --version Python 3.5.1
プラグインを書いてみる
プラグインは nose_helloworld という名前のパッケージとして用意する。 まずはプロジェクト用のディレクトリを用意しておこう。
$ mkdir nose_helloworld
$ cd nose_helloworld
ディレクトリの中に移動したら、さらに同じ名前でディレクトリを作る。 ただし、今度のディレクトリは Python のパッケージにするためのものだ。 Python のパッケージは __init__.py というファイルの入ったディレクトリとして表現される。
$ mkdir nose_helloworld
ちなみに Python のモジュールとパッケージまわりの話はこちらの記事に書いたので併せて参照してもらいたい。
それでは、先ほどの説明通りディレクトリの中に __init__.py を作ろう。 これで nose_helloworld ディレクトリは Python の nose_helloworld パッケージとして認識できるようになる。 今回は、この init.py の中に nose のプラグインを書くことにする。
nose のプラグインは nose.plugins.Plugin のサブクラスとして定義する。 書き方にはいくつかの流儀があるけど、基本的にはスーパークラスにあるメソッドのオーバーライドするだけで良い。 他にはクラスの docstring がコマンドラインのヘルプメッセージになるので書いておいた方が良い。 あとはプラグイン名を name メンバに入れておくのも忘れずに。 ちなみに、オーバーライドしたメソッドの finalize() はテストが完了したときに呼び出されるコールバックだ。 最初のプラグインでは、ここでログを出すことにする。
$ cat << 'EOF' > nose_helloworld/__init__.py # -*- coding: utf-8 -*- import logging from nose.plugins import Plugin LOG = logging.getLogger('nose.plugins.helloworld') class HelloWorldPlugin(Plugin): """My first plugin""" name = 'helloworld' def finalize(self, result, *args, **kwargs): LOG.info('HelloWorld Plugin: finalize') EOF
プラグインで使えるコールバックは、次の IPluginInterface クラスで確認できる。 この中で <メソッド>._new = True となっているのが使って良いものみたい。 github.com
さて、これでパッケージの中にプラグインが用意できた。 次はパッケージをインストールできるようにする。 なぜなら nose のプラグイン機構は setuptools というパッケージング用の API を使っているためだ。 具体的には pkg_resources というものだけど、これはパッケージをインストールするためのセットアップスクリプトの中に指定する。
次のセットアップスクリプトでは setup() 関数の中の entry_points 引数に 'nose.plugins.0.10' という項目を用意している。 nose のプラグインになるクラスはここで指定することになる。
$ cat << 'EOF' > setup.py # -*- coding: utf-8 -*- from setuptools import setup def main(): setup( name='nose_helloworld', version='0.0.1', zip_safe=False, packages=['nose_helloworld'], install_requires=[ 'nose==1.3.7', ], entry_points = { 'nose.plugins.0.10': [ 'helloworld = nose_helloworld:HelloWorldPlugin' ], }, ) if __name__ == '__main__': main() EOF
パッケージをインストールする
これで nose_helloworld パッケージをインストールする用意ができた。 セットアップスクリプトに install サブコマンドを渡して実行しよう。
$ python setup.py install
これで nose_helloworld パッケージがインストールされた。
$ pip freeze | grep helloworld nose-helloworld==0.0.1
プラグインを使う
パッケージをインストールすると nose のテストランナーである nosetests コマンドに --with-helloworld というオプションが追加される。 これは先ほどインストールしたパッケージの中に HelloWorldPlugin にある name メンバと docstring から生成されている。 このオプションを有効にすることで作成した HelloWorldPlugin が有効になるという寸法だ。
$ nosetests --help | grep -A 1 helloworld --with-helloworld Enable plugin HelloWorldPlugin: My first plugin for nose [NOSE_WITH_HELLOWORLD]
それでは HelloWorldPlugin を有効にして nosetests を実行してみよう。
$ nosetests -vv --with-helloworld nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$'] ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK nose.plugins.helloworld: INFO: HelloWorld Plugin: finalize
nose.plugins.helloworld という名前でログが出力されたことがわかる。 ちゃんとテストが完了したときにプラグインのコードが実行された!
ちなみにヘルプメッセージにもあったようにプラグインは環境変数を使って有効にすることもできる。 プラグインを表すクラスの name メンバに 'helloworld' が格納されている場合は NOSE_WITH_HELLOWORLD という環境変数名になる。
$ export NOSE_WITH_HELLOWORLD=1 $ nosetests -vv nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$'] ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK nose.plugins.helloworld: INFO: HelloWorld Plugin: finalize
プラグインにオプションを追加する
先ほどの例ではこれ以上ないほどシンプルなプラグインを扱った。 次はプラグインに動作を変更するためのオプションを追加してみよう。
先ほどのプラグインを上書きする形で変更していく。 今度は options() というメソッドと configure() というメソッドをプラグインに実装しよう。 options() メソッドは argparse の API を使ってプラグインが有効なときに使えるコマンドラインオプションが追加できる。 同様に configure() メソッドでは、それらのオプションの値を利用してプラグインを設定できる。 次のプラグインでは --message というオプションを追加した。 先ほどは固定だった finalize() メソッドで出力されるログのメッセージを、このオプションで指定されたものに差し替えてみよう。
$ cat << 'EOF' > nose_helloworld/__init__.py # -*- coding: utf-8 -*- import logging from nose.plugins import Plugin LOG = logging.getLogger('nose.plugins.helloworld') class HelloWorldPlugin(Plugin): """My first plugin""" name = 'helloworld' def options(self, parser, env, *args, **kwargs): super(HelloWorldPlugin, self).options(parser, env, *args, **kwargs) parser.add_option( '--message', default='Hello, World!', help='Greeting message', ) def configure(self, options, conf, *args, **kwargs): super(HelloWorldPlugin, self).configure(options, conf, *args, **kwargs) self.message = options.message def finalize(self, result, *args, **kwargs): LOG.info('HelloWorld Plugin: {msg}'.format(msg=self.message)) EOF
プラグインを上書きしたらパッケージをインストールし直そう。
$ python setup.py install
もう一度 nosetests コマンドをプラグインを有効にして実行してみる。
$ nosetests -vv --with-helloworld nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$'] ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK nose.plugins.helloworld: INFO: HelloWorld Plugin: Hello, World!
先ほどとは finalize() メソッドで出力されるログのメッセージが変更されていることがわかる。
次にコマンドラインオプションとして --message を追加しよう。 ここに出力してほしいログメッセージを指定する。
$ nosetests -vv --with-helloworld --message 'Hello, Plugin!' nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$'] ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK nose.plugins.helloworld: INFO: HelloWorld Plugin: Hello, Plugin!
--message オプションで指定したメッセージが出力されていることがわかる。
別のコールバックも実装してみる
次は nose のテストランナーがそれぞれのテストを実行する直前に呼び出されるコールバックをプラグインに実装してみよう。 これには beforeTest() というメソッドをプラグインに実装する。
$ cat << 'EOF' > nose_helloworld/__init__.py # -*- coding: utf-8 -*- import logging from nose.plugins import Plugin LOG = logging.getLogger('nose.plugins.helloworld') class HelloWorldPlugin(Plugin): """My first plugin""" name = 'helloworld' def beforeTest(self, test, *args, **kwargs): LOG.info('HelloWorld Plugin: {test}'.format(test=test)) EOF
用意できたらパッケージをインストールし直そう。
$ python setup.py install
動作確認のためにシンプルなテストを用意する。 これは、実際には何もテストしていない。
$ cat << 'EOF' > test_helloworld.py #!/usr/bin/env python # -*- coding: utf-8 -*- from nose.tools import ok_ class Test(object): def test(self): ok_(True) EOF
準備ができたら nosetests コマンドを実行する。
$ nosetests -vv --with-helloworld nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$'] nose.plugins.helloworld: INFO: HelloWorld Plugin: test_helloworld.Test.test test_helloworld.Test.test ... ok ---------------------------------------------------------------------- Ran 1 test in 0.002s OK
今度はテストランナーが見つけたテストの test_helloworld.Test.test が実行される直前にコールバックが実行されていることがわかる。
まとめ
今回は Python のテストフレームワーク nose のプラグインを書く方法について調べてみた。 nose は自作パッケージを用意して、そこにプラグインとなるクラスを定義することで挙動をカスタマイズできる。 この仕組みは nose の中でも使われていて、いくつかの組み込みプラグインが用意されている。 例えばテストのカバレッジを調べるための --with-coverage オプションも、今回調べたプラグインの機構を利用しているようだ。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログを見る