Nose 利用ノート¶
Note
関連リンク¶
- Nose
パッケージ配布元。
目的¶
Nose 自身は、その目的をドキュメントの冒頭に <nose is nicer testing for python> や <nose extends unittest to make testing easier> と謳っている。さらに、プログラムの狙いを次の 4 点に絞っている。
テストコードを書くのを簡単に。
テストを走らせるのを簡単に。
テスト環境の構築を簡単に。
やりたいことをやることを簡単に。
そして私が Nose を利用する目的は何かというと、主に「走らせるのを簡単に」ではないかと思う。 Python 標準の unittest
だけで単体テストをやろうとすると、
TestSuite を集めて TestRunner に渡すコードを書くという、これまでよくやってきた作業パターンが億劫に感じられるはずだ。
インストール¶
方法については 一般のサードパーティー製 Python パッケージのインストール手順 で図示したので、そちらを参照して欲しい。インストールが成功終了後は、Python 環境は次のように変化している。
Lib/site-packages/nose
フォルダーが存在する。当然その中には py モジュールが含まれている。Scripts
フォルダーに実行ファイルnosetests
が存在する。特に Windows の場合、これは exe ファイルである。
利用方法¶
nosetests と Nose ライブラリー本体の利用方法に分けて理解する。
nosetests¶
Nose をインストールすると、Python パッケージだけでなく、nosetests というスクリプトか実行ファイルが Scripts
フォルダーにインストールされる。
これは py ファイルからテストを自動的に発見し、実行することができる便利なツールだ。
引数なしで起動すると、おそらくカレントディレクトリーにあるすべての py ファイルから、すべてのテストを発見し、片っ端から実行するというはたらきをするのではないだろうか。
普通は nosetests にコマンドライン引数を指定して利用する。次のコマンドライン例は Nose のドキュメントから引用したものだ。モジュール名を指定したり、さらにテスト名を指定したり、あるいはモジュールフルパスプラステスト名という指定の仕方がサポートされているようだ。
bash$ nosetests test.module bash$ nosetests another.test:TestCase.test_method bash$ nosetests a.test:TestCase bash$ nosetests /path/to/test/file.py:test_function
ディレクトリーごと指示するやり方もある。その場合、複数パス指定が許される。
bash$ nosetests /path/to/tests /another/path/to/tests
なので、実は
-w
,--where
オプションは無用の長物。nosetests は豊富なコマンドラインオプションを提供している。
コマンドラインオプションと同等の設定を設定ファイルからも行える。
デフォルトの設定ファイルは
$HOME
にある.noserc
またはnose.cfg
だ。任意の設定ファイルパスをコマンドラインから
--config
オプションを利用することで指定できる。設定ファイルの書き方で注意が要るのは、設定項目を
[nosetests]
セクションに書かねばならないことだ。[nosetests] verbosity=2 with-doctest=true
テスト結果の出力書式は、標準の
unittest
のそれと基本的には同一。
次に、使えそうなオプションを調べてみよう。
collect-only
オプション – テスト名だけを調べる¶
--collect-only
オプションでテストを実行せずにテスト名だけを確認できる。
さらに
--with-id
を併用し、テストのインデックスリストも得られる。--verbosity
オプションを併用して、テスト名等を明示させるのがコツ。
bash$ nosetests --collect-only --with-id --verbosity=2
#1 A regular test case ... ok
#2 A very slow test case ... ok
#3 A test with attribute ... ok
#4 A test with attribute specific value ... ok
#5 testattr.test_tags ... ok
#6 testattr2.test_load_all_images ... ok
#7 testattr2.test_download_hardcore_images ... ok
#8 testeven.test_evens(0, 0) ... ok
#8 testeven.test_evens(1, 3) ... ok
testeven.test_evens(2, 6) ... ok
testeven.test_evens(3, 9) ... ok
testeven.test_evens(4, 12) ... ok
#9 test_choice (testrandom.TestSequenceFunctions) ... ok
#10 test_sample (testrandom.TestSequenceFunctions) ... ok
#11 test_shuffle (testrandom.TestSequenceFunctions) ... ok
#12 test_default_size (testwidget.WidgetTestCase) ... ok
#13 test_resize (testwidget.WidgetTestCase) ... ok
----------------------------------------------------------------------
Ran 17 tests in 0.101s
OK
attr
オプション – 属性を指定することで起動するテストを選択する¶
テストケースをいっぱい書いたはいいが、「今はこのテストだけをやりたいンだ」「このテストは通常はやりたくないンだ」という状況に陥りがち。そんなときには --attr
,
--eval-attr
オプションの仕組みをうまくテストコードに組み込む。
#!/usr/bin/env python
""" testattr2.py: Demonstrate Nose.
"""
from nose.plugins.attrib import attr
@attr(speed='slow')
def test_load_all_images():
"""A test cast that takes several minutes."""
# ...
pass
@attr(online=True)
def test_download_hardcore_images():
"""A test that makes sense only with network access."""
# ...
pass
# Other test cases...
bash$ nosetests -a '!online' testattr2.py
bash$ nosetests -A "speed != slow" testattr2.py
上のコマンドラインの実行では
test_download_hardcore_images
は実行されない。下のコマンドラインの実行では
test_load_all_images
は実行されない。
pdb-failures
オプション – テスト失敗時にデバッガーを起動¶
--pdb-failures
オプションを指定しておくと、テストが FAILURE になった地点で
Python の pdb デバッガが起動する。
通常使いたいのは
--pdb
ではなく--pdb-faillures
のほうだと思う。pdb はコンソールベースのデバッガ。正直なところ不慣れなツールだが、この際慣れておく。
bash$ nosetests --pdb-failures testeven.py
.> d:\home\yojyo\devel\all-note\notebook\source\_sample\nose\testeven.py(13)check_even()
-> assert val1 % 2 == 0 or val2 % 2 == 0
(Pdb) l
8 for i in range(0, 5):
9 yield check_even, i, i * 3
10
11 def check_even(val1, val2):
12 """Determine if either of numbers is even."""
13 -> assert val1 % 2 == 0 or val2 % 2 == 0
[EOF]
(Pdb) p val1, val1 % 2, val2 % 2
(1, 1, 1)
(Pdb)
エラーの発生とは関係なく、特定の箇所でステップ実行を有効にする方法もある。まずはステップ実行したいコードを含むモジュールで
from nose.tools import set_trace
をする。それから対象コードの直前で
set_trace()
すればよい。
with-coverage
オプション – コードカバレッジ¶
--with-coverage
オプションでテスト結果と共にコードカバレッジを測定できる。いつものテスト結果を出力した直後に、カバレッジを出力する。
チューニングの材料になるわけで、いずれ大掛かりなライブラリーを開発するつもりならば、この機能は覚えていて損はない。
この機能を利用するには、別途 coverage という別のパッケージが必要だ。インストールは難しくないので、Nose 環境の一部とみなして導入しておくとよさそうだ。
bash$ nosetests --with-coverage -v testrandom.py
test_choice (testrandom.TestSequenceFunctions) ... ok
test_sample (testrandom.TestSequenceFunctions) ... ok
test_shuffle (testrandom.TestSequenceFunctions) ... ok
Name Stmts Miss Cover Missing
------------------------------------------
... この行にファイルパスの情報が入るが省略 ...
testrandom 21 3 86% 25, 30-31
----------------------------------------------------------------------
Ran 3 tests in 0.010s
OK
with-profile
オプション – プロファイリング¶
Note
筆者環境では Nose 1.3.3 でこの機能が利用できなくなっている。
--with-profile
オプションでテストに関係した全関数に対する呼び出しの回数や時間の統計を取れる。いつものテスト結果を出力した直後に、プロファイル結果を出力する。
4101 function calls (4084 primitive calls) in 0.201 CPU seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
7/1 0.000 0.000 0.201 0.201 d:\python26\lib\site-packages\nose\suite.py:175(__call__)
7/1 0.002 0.000 0.201 0.201 d:\python26\lib\site-packages\nose\suite.py:196(run)
1 0.000 0.000 0.200 0.200 d:\python26\lib\unittest.py:463(__call__)
1 0.000 0.000 0.200 0.200 d:\python26\lib\site-packages\nose\suite.py:70(run)
25 0.000 0.000 0.121 0.005 d:\python26\lib\site-packages\nose\suite.py:92(_get_tests)
...
--profile-sort=SORT
オプションで、ソート順を何にするかを指定できる。オプション自体を指定しない場合はcumulative
がデフォルト扱いとなる。なお
SORT
に指定する値は Python Standard Library のStats.sort_stats
の引数と同じ。
オプションメモ¶
-h
または--help
でヘルプ表示。-V
または--version
でnosetests
のバージョンを表示。-v
または--verbosity
で表示を少々やかましくできる。テスト名確認時にはこれを併用するだろう。-m REGEX
系オプションで「テストとみなしたいファイル・ディレクトリー・関数・クラス名にマッチする」正規表現を指定できる。デフォルトで
(?:^|[\b_\.\-])[Tt]est
になっていることを押させておけばよい。-p
または--plugins
オプションで、有効なプラグインの一覧を表示。ただし出力順が何で決まるのかわからないので、適当に grep や sort にパイプして見やすくするべし。
ライブラリー¶
テストの書き方¶
テストは
unittest.TestCase
のサブクラスの形で用意しなくてもよい。ただし
unittest.TestCase
のサブクラスからはテストを無条件にロードする。テスト関数はモジュールの先頭から出現順に走らせる。
TestCase
サブクラスまたはその他のテストクラスは、名前のアルファベット順に走らせる。Fixture について
どうやら setup/teardown ペアのことを test fixture と呼ぶらしい。
Nose はパッケージレベル、モジュールレベル、クラスレベル、関数レベルで fixture をサポートしている。言い換えれば、これらの各レベルでテストの概念がある。
テストパッケージ
Nose はテストをパッケージの形に編成することを認めている。
パッケージレベルでの setup/teardown の概念が存在する。それらはいずれも
__init__.py
で関数の形で用意しておくと、 Nose がそれを適切なタイミングで拾ってくれる。setup 関数の名前は次のいずれかとなる:
setup
,setup_package
,setUp
,setUpPackage
teardown 関数の名前は次のいずれかとなる:
teardown
,teardown_package
,tearDown
,tearDownPackage
テストモジュール
モジュール名がテストっぽいものはテストモジュールである。
モジュールレベルでの setup/teardown の概念が存在する。それ用の関数名も上述のパッケージのそれから類推できる名前になっている。
モジュールのテストが起動するタイミングは、Nose がすべてのテストを集めた後になる。
テストクラス
テストモジュール内に定義されている、次のいずれかの条件を満たすクラスである:
unittest.TestCase
のサブクラスすべて - (A)Nose の
testMatch
にマッチする名前を持つクラスすべて - (B)
(B) タイプのクラスでも
setUp
とtearDown
を定義することができ、 Nose はそれらを (A) タイプのそれのように呼び出すことになる。タイプは (A) タイプよりも以下の点で優遇される:
ジェネレーターメソッドを持つことができる。
クラスレベルの setup/teardown を定義することができる。いずれもクラスメソッドである必要がある。
setup_class
,setupClass
,setUpClass
,setupAll
,setUpAll
teardown_class
,teardownClass
,tearDownClass
,teardownAll
,tearDownAll
テスト関数
テストモジュール内に定義されている、Nose の
testMatch
にマッチする名前を持つ関数がテスト関数となる。関数にも setup/teardown を適用することができる。自分で定義した関数をデコレーター
with_setup
を利用して取り付ける。これがたいへん便利だ。
そして Nose を利用するとジェネレーターをもテストできる。自分ではよく使わないので今のところはパス。
nose.tools¶
Note
ちょっと利用方法が理解できないものがあるため、後回し。
テストの発見および起動法則¶
さっきも書いたが、それ以外について。
Nose はテストに見えないディレクトリーかつパッケージでないものは検査しない。
Nose はモジュールを import する際に、そのモジュールがあるディレクトリーパスを
sys.path
変数に追加してしまう。モジュールが何かパッケージのものである場合、package.module
として import されることになる。もしあるオブジェクトが属性
__test__
を有し、かつそれがTrue
と評価しないようなものならば、そのオブジェクトはテストとして集められないし、さらにそのオブジェクトを含むどんなオブジェクトも集められない。
プラグイン¶
Nose のバージョンが上がってから勉強しに行こう。
雑多なメモ¶
Further Reading より:
Jason Pellerin という人物が作者のようだ。2005 年からコピーライトが発生している。
Nose という名前はどうして付いたのか。作者は discover の同義語を類語辞書で調べたようで、短くてマヌケな名前で、なおかつ spy の意味を含まぬものを採用したらしい。
nose は動詞だとクンカクンカするとかいう意味なのでは。
Nose は py.test というテスティングフレームワークにインスパイヤされて作ったとある。以前の py.test はインストールが難しく、unittest ベースでなかったとのこと。
Nose のライセンスは LGPL とかいうものらしい。バージョン 2 以降ならば、利用者が好きなライセンスを選択してよいとか。
nosetests の変な使い方。
他人様の作ったパッケージのテスト構成を探るのに最適なツールかもしれない。例えば Jinja2 の
testsuite
フォルダーの各ファイルからテストを全部抽出してリストを作成できたりする。bash$ cd site-packages/jinja2/ bash$ python34 -c 'import jinja2; print(jinja2.__version__)' 2.7.3 bash$ nosetests --collect-only --with-id -v testsuite/*.py #1 Failure: TypeError (find_all_tests() missing 1 required positional argument: 'suite') ... ok #2 test_autoescape_autoselect (jinja2.testsuite.api.ExtendedAPITestCase) ... ok #3 test_cycler (jinja2.testsuite.api.ExtendedAPITestCase) ... ok #4 test_expressions (jinja2.testsuite.api.ExtendedAPITestCase) ... ok #5 test_finalizer (jinja2.testsuite.api.ExtendedAPITestCase) ... ok ... 省略 ... #311 test_markup_leaks (jinja2.testsuite.utils.MarkupLeakTestCase) ... ok ---------------------------------------------------------------------- Ran 311 tests in 0.139s OK
Warning
最近の Jinja2 のインストールには
testsuite
フォルダーがない。Matplotlib の
tests
フォルダーはテストパッケージの構成になっている。 nosetests の実験場としては面白い。NumPy は Nose をうまく使いこなしているようだ。
import numpy; help(numpy.test)
してみよう。テストの単位をわかりやすく分類する努力を払っているのがわかる。例えば線形代数サブパッケージだけテストしたいのならば、Python インタープリターから次のようにタイプしてみるだけでよい。
>>> import numpy as np >>> np.linalg.test(verbose=2) Running unit tests for numpy.linalg NumPy version 1.11.1 NumPy relaxed strides checking option: False NumPy is installed in D:\Miniconda3\lib\site-packages\numpy Python version 3.5.2 |Continuum Analytics, Inc.| (default, Jul 5 2016, 11:41:13) [MSC v.1900 64 bit (AMD64)] nose version 1.3.7 test_lapack (test_build.TestF77Mismatch) ... SKIP: Skipping test: test_lapack: Skipping fortran compiler mismatch on non Linux platform Check mode='full' FutureWarning. ... ok test_linalg.TestBoolPower.test_square ... ok test_linalg.TestCond2.test_sq_cases ... ok test_linalg.TestCond2.test_stacked_arrays_explicitly ... ok test_linalg.TestCondInf.test ... ok test_linalg.TestCondSVD.test_sq_cases ... ok test_linalg.TestCondSVD.test_stacked_arrays_explicitly ... ok test_linalg.TestDet.test_sq_cases ... ok ... more results ... test_svd_build (test_regression.TestRegression) ... ok test_svd_no_uv (test_regression.TestRegression) ... ok ---------------------------------------------------------------------- Ran 134 tests in 17.999s OK (SKIP=2) <nose.result.TextTestResult run=134 errors=0 failures=0>
未調査項目
プラグイン周りを調べていない。
ログ設定周りを調べていない。
Windows 環境ゆえ、マルチプロセステストが試せないのは残念。