2016-06-20

[Python] え、まだtox使ってないんですか?

今回はtoxです。さんざん記事書くって言ってあったので書きます (本当は4月の予定だったけどね

この記事は神々(aodag, shimzukawa)の知識をほとんどそのまま拝借したものです。 ほぼ乗っ取られてます。

準備

今回テストの対象とするのは私が公開してる tesdat というライブラリを使います。

もちろん試したいコードがある方はそちらでよいです。

複数のバージョンのpython がインストールされている VM内で git clone します。

[vagrant@localhost ~]$ cd projects/ # projects配下はホスト間と共有されてる [vagrant@localhost projects]$ git clone https://github.com/righ/tesdat [vagrant@localhost projects]$ cd tesdat/

準備はこれで終了。

導入

ではインストール。このときvenvを有効にしてもしなくてもOKです。私はやりますが。

$ pip install tox

残念ながらまだ tox は使えません。

(venv3.5) [vagrant@localhost tesdat]$ tox ERROR: toxini file 'tox.ini' not found

tox を使うには tox.ini という設定ファイルが必要です。 ひとまずこんな風にしましょう。

[tox] envlist = py27, py33, py34, py35, flake8 [testenv] commands = py.test deps = pytest [testenv:flake8] deps = flake8 flake8-blind-except flake8-docstrings flake8-import-order commands = flake8 . [flake8] exclude = tests/*

簡単に説明すると

  • テストランナーは py.test を使う
  • python2.7, python3.3, python3.4, python3.5 でテストを実施する。
    • python3.2(というかpip3.2?)がtoxでうまく動作しなかったので3.3からにしました。しょうがないね。
  • tests/ フォルダを除いてflake8を実施する。
  • flake8 での構文を確認するためにライブラリをインストールする

って感じですね。

複数のコマンドを実行したい場合は deps と同じように commands を複数行にすればOKです。

tox.ini で特に重要なのは toxtestenv セクションです。

「tox」セクションの「envlist」に記述したコマンドが実際に何をするかやオプションを testenv:~ に記述します。

info
  • デフォルトenvは testenv セクションに属します。
  • 簡単に言うと py*(python*), jython, pypy です。
  • 詳しくは こちら を参照。

特定の環境だけでテストを実行したい場合は -e オプションを使います。

例えば上記設定を用いてflake8だけを実行したい場合は tox-e flake8 のようにします。

deps にはテストに必要なライブラリを requirements.txt 形式で指定できます。

すでにプロジェクトで requirements.txt がある場合 -rrequirements.txt のように指定できます。 テスト用の requirements.txt を作っておくのがよいでしょう。

とりあえず実行してみましょう。

(venv3.5) [vagrant@localhost tesdat]$ tox GLOB sdist-make: /home/vagrant/tesdat/setup.py py27 inst-nodeps: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py27 installed: tesdat==2.1.1 py27 runtests: PYTHONHASHSEED='1127406230' py27 runtests: commands[0] | py.test WARNING:test command found but not installed in testenv cmd: /home/vagrant/venv3.5/bin/py.test env: /home/vagrant/tesdat/.tox/py27 Maybe you forgot to specify a dependency? See also the whitelist_externals envconfig setting. WARNING: Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv. WARNING: IPython History requires SQLite, your history will not be saved ============================================== test session starts =============================================== platform linux -- Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: ipdb-0.1.dev2, django-2.9.1 collected 57 items tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py F tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py F. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ==================================================== FAILURES ==================================================== ________________________________________ TestJsonFormatter.test_stringify ________________________________________ self = def test_stringify(self): sf = self._getClass({'a': time(23, 59), 'b': ('2',)}, indent=None) self.assertEqual( sf.stringify(), > '{"a": "23:59:00", "b": ["2"]}' ) E AssertionError: '{"b": ["2"], "a": "23:59:00"}' != '{"a": "23:59:00", "b": ["2"]}' E - {"b": ["2"], "a": "23:59:00"} E + {"a": "23:59:00", "b": ["2"]} tesdat/tests/formatters/test_json.py:15: AssertionError ________________________________________ TestStringFormatter.test_string _________________________________________ self = def test_string(self): sf = self._getClass({'a': 1, 'b': ('2',)}) self.assertEqual( sf.stringify(), > "{'a': 1, 'b': ('2',)}" ) E AssertionError: "{'b': ('2',), 'a': 1}" != "{'a': 1, 'b': ('2',)}" E - {'b': ('2',), 'a': 1} E + {'a': 1, 'b': ('2',)} tesdat/tests/formatters/test_string.py:23: AssertionError ====================================== 2 failed, 55 passed in 0.95 seconds ======================================= ERROR: InvocationError: '/home/vagrant/venv3.5/bin/py.test' py33 inst-nodeps: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py33 installed: tesdat==2.1.1 y33 runtests: PYTHONHASHSEED='1127406230' py33 runtests: commands[0] | py.test WARNING:test command found but not installed in testenv cmd: /home/vagrant/venv3.5/bin/py.test env: /home/vagrant/tesdat/.tox/py33 Maybe you forgot to specify a dependency? See also the whitelist_externals envconfig setting. WARNING: Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv. WARNING: IPython History requires SQLite, your history will not be saved ============================================== test session starts =============================================== platform linux -- Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: ipdb-0.1.dev2, django-2.9.1 collected 57 items tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py F tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py F. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ==================================================== FAILURES ==================================================== ________________________________________ TestJsonFormatter.test_stringify ________________________________________ self = def test_stringify(self): sf = self._getClass({'a': time(23, 59), 'b': ('2',)}, indent=None) self.assertEqual( sf.stringify(), > '{"a": "23:59:00", "b": ["2"]}' ) E AssertionError: '{"b": ["2"], "a": "23:59:00"}' != '{"a": "23:59:00", "b": ["2"]}' E - {"b": ["2"], "a": "23:59:00"} E + {"a": "23:59:00", "b": ["2"]} tesdat/tests/formatters/test_json.py:15: AssertionError ________________________________________ TestStringFormatter.test_string _________________________________________ self = def test_string(self): sf = self._getClass({'a': 1, 'b': ('2',)}) self.assertEqual( sf.stringify(), > "{'a': 1, 'b': ('2',)}" ) E AssertionError: "{'b': ('2',), 'a': 1}" != "{'a': 1, 'b': ('2',)}" E - {'b': ('2',), 'a': 1} E + {'a': 1, 'b': ('2',)} tesdat/tests/formatters/test_string.py:23: AssertionError ====================================== 2 failed, 55 passed in 0.93 seconds ======================================= ERROR: InvocationError: '/home/vagrant/venv3.5/bin/py.test' py34 inst-nodeps: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py34 installed: tesdat==2.1.1 py34 runtests: PYTHONHASHSEED='1127406230' py34 runtests: commands[0] | py.test WARNING:test command found but not installed in testenv cmd: /home/vagrant/venv3.5/bin/py.test env: /home/vagrant/tesdat/.tox/py34 Maybe you forgot to specify a dependency? See also the whitelist_externals envconfig setting. WARNING: Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv. WARNING: IPython History requires SQLite, your history will not be saved ============================================== test session starts =============================================== platform linux -- Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: ipdb-0.1.dev2, django-2.9.1 collected 57 items tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py F tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py F. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ==================================================== FAILURES ==================================================== ________________________________________ TestJsonFormatter.test_stringify ________________________________________ self = def test_stringify(self): sf = self._getClass({'a': time(23, 59), 'b': ('2',)}, indent=None) self.assertEqual( sf.stringify(), > '{"a": "23:59:00", "b": ["2"]}' ) E AssertionError: '{"b": ["2"], "a": "23:59:00"}' != '{"a": "23:59:00", "b": ["2"]}' E - {"b": ["2"], "a": "23:59:00"} E + {"a": "23:59:00", "b": ["2"]} tesdat/tests/formatters/test_json.py:15: AssertionError ________________________________________ TestStringFormatter.test_string _________________________________________ self = def test_string(self): sf = self._getClass({'a': 1, 'b': ('2',)}) self.assertEqual( sf.stringify(), > "{'a': 1, 'b': ('2',)}" ) E AssertionError: "{'b': ('2',), 'a': 1}" != "{'a': 1, 'b': ('2',)}" E - {'b': ('2',), 'a': 1} E + {'a': 1, 'b': ('2',)} tesdat/tests/formatters/test_string.py:23: AssertionError ====================================== 2 failed, 55 passed in 0.92 seconds ======================================= ERROR: InvocationError: '/home/vagrant/venv3.5/bin/py.test' py35 inst-nodeps: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py35 installed: tesdat==2.1.1 py35 runtests: PYTHONHASHSEED='1127406230' py35 runtests: commands[0] | py.test WARNING:test command found but not installed in testenv cmd: /home/vagrant/venv3.5/bin/py.test env: /home/vagrant/tesdat/.tox/py35 Maybe you forgot to specify a dependency? See also the whitelist_externals envconfig setting. WARNING: Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv. WARNING: IPython History requires SQLite, your history will not be saved ============================================== test session starts =============================================== platform linux -- Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: ipdb-0.1.dev2, django-2.9.1 collected 57 items tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py F tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py F. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ==================================================== FAILURES ==================================================== ________________________________________ TestJsonFormatter.test_stringify ________________________________________ self = def test_stringify(self): sf = self._getClass({'a': time(23, 59), 'b': ('2',)}, indent=None) self.assertEqual( sf.stringify(), > '{"a": "23:59:00", "b": ["2"]}' ) E AssertionError: '{"b": ["2"], "a": "23:59:00"}' != '{"a": "23:59:00", "b": ["2"]}' E - {"b": ["2"], "a": "23:59:00"} E + {"a": "23:59:00", "b": ["2"]} tesdat/tests/formatters/test_json.py:15: AssertionError ________________________________________ TestStringFormatter.test_string _________________________________________ self = def test_string(self): sf = self._getClass({'a': 1, 'b': ('2',)}) self.assertEqual( sf.stringify(), > "{'a': 1, 'b': ('2',)}" ) E AssertionError: "{'b': ('2',), 'a': 1}" != "{'a': 1, 'b': ('2',)}" E - {'b': ('2',), 'a': 1} E + {'a': 1, 'b': ('2',)} tesdat/tests/formatters/test_string.py:23: AssertionError ====================================== 2 failed, 55 passed in 0.99 seconds ======================================= ERROR: InvocationError: '/home/vagrant/venv3.5/bin/py.test' flake8 recreate: /home/vagrant/tesdat/.tox/flake8 flake8 installdeps: flake8, flake8-blind-except, flake8-docstrings, flake8-import-order, mccabe, radon flake8 inst: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip flake8 installed: colorama==0.3.7,flake8==2.5.4,flake8-blind-except==0.1.0,flake8-docstrings==0.2.5,flake8-import-order==0.7,mando==0.3.3,mccabe==0.4.0,pep257==0.7.0,pep8==1.7.0,pyflakes==1.0.0,radon==1.3.2,tesdat==2.1.1 flake8 runtests: PYTHONHASHSEED='1127406230' flake8 runtests: commands[0] | flake8 . ./setup.py:1:1: D100 Missing docstring in public module ./setup.py:4:1: I101 Imported names are in the wrong order. Should be find_packages, setup ./setup.py:5:1: I101 Imported names are in the wrong order. Should be __author__, __author_email__, __license__, __version__ ./setup.py:5:1: I201 Missing newline before sections or imports. ./tesdat/__init__.py:1:1: D200 One-line docstring should fit on one line with quotes ./tesdat/__init__.py:11:1: E402 module level import not at top of file ./tesdat/__init__.py:12:1: E402 module level import not at top of file ./tesdat/__init__.py:12:1: I100 Import statements are in the wrong order. from .containers.dict should be before from .containers.list ./tesdat/__init__.py:13:1: E402 module level import not at top of file ./tesdat/__init__.py:15:1: E402 module level import not at top of file ./tesdat/__init__.py:16:1: E402 module level import not at top of file ./tesdat/__init__.py:16:1: I100 Import statements are in the wrong order. from .models.dict should be before from .models.list ./tesdat/__init__.py:18:1: E402 module level import not at top of file ./tesdat/__init__.py:19:1: E402 module level import not at top of file ./tesdat/__init__.py:20:1: E402 module level import not at top of file ./tesdat/__init__.py:20:1: I100 Import statements are in the wrong order. from .patterns.cycle should be before from .patterns.pickout ./tesdat/__init__.py:21:1: E402 module level import not at top of file ./tesdat/__init__.py:22:1: E402 module level import not at top of file ./tesdat/__init__.py:23:1: E402 module level import not at top of file ./tesdat/__init__.py:26:1: E402 module level import not at top of file ./tesdat/__init__.py:26:1: I100 Import statements are in the wrong order. from .formatters.string should be before from .patterns.sequence ./tesdat/__init__.py:27:1: E402 module level import not at top of file ./tesdat/__init__.py:27:1: I100 Import statements are in the wrong order. from .formatters.json should be before from .formatters.string ./tesdat/__init__.py:28:1: E402 module level import not at top of file ./tesdat/__init__.py:29:1: E402 module level import not at top of file ./tesdat/__init__.py:29:1: I100 Import statements are in the wrong order. from .formatters.csv should be before from .formatters.pickle ./tesdat/__init__.py:31:1: E402 module level import not at top of file ./tesdat/__init__.py:31:1: F401 'BLANK' imported but unused ./tesdat/__init__.py:31:1: F401 'ESCAPE' imported but unused ./tesdat/__init__.py:31:1: I100 Import statements are in the wrong order. from .api.special should be before from .formatters.csv ./tesdat/__init__.py:76:1: D205 1 blank line required between summary line and description ./tesdat/__init__.py:76:1: D400 First line should end with a period ./tesdat/__init__.py:88:9: F821 undefined name 'containers' ./tesdat/__init__.py:88:21: F821 undefined name 'models' ./tesdat/__init__.py:88:29: F821 undefined name 'patterns' ./tesdat/__init__.py:88:39: F821 undefined name 'formatters' ./tesdat/__init__.py:88:51: F821 undefined name 'exceptions' ./tesdat/__init__.py:88:63: F821 undefined name 'api' ./tesdat/compat.py:1:1: D100 Missing docstring in public module ./tesdat/compat.py:30:9: F401 'StringIO' imported but unused ./tesdat/exceptions.py:1:1: D100 Missing docstring in public module ./tesdat/exceptions.py:3:1: D101 Missing docstring in public class ./tesdat/exceptions.py:3:1: E302 expected 2 blank lines, found 1 ./tesdat/exceptions.py:7:1: D101 Missing docstring in public class ./tesdat/exceptions.py:11:1: D101 Missing docstring in public class ./tesdat/exceptions.py:15:1: D101 Missing docstring in public class ./tesdat/api/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/api/display.py:1:1: D100 Missing docstring in public module ./tesdat/api/display.py:5:1: D103 Missing docstring in public function ./tesdat/api/display.py:12:1: D103 Missing docstring in public function ./tesdat/api/display.py:22:1: W391 blank line at end of file ./tesdat/api/render.py:1:1: D100 Missing docstring in public module ./tesdat/api/render.py:7:1: I100 Import statements are in the wrong order. from .. should be before from ..patterns.base ./tesdat/api/render.py:9:1: E302 expected 2 blank lines, found 1 ./tesdat/api/render.py:15:1: D205 1 blank line required between summary line and description ./tesdat/api/render.py:15:1: D400 First line should end with a period ./tesdat/api/render.py:37:80: E501 line too long (80 > 79 characters) ./tesdat/api/special.py:1:1: D100 Missing docstring in public module ./tesdat/api/special.py:4:1: D204 1 blank line required after class docstring ./tesdat/api/special.py:4:1: D400 First line should end with a period ./tesdat/api/special.py:6:1: D102 Missing docstring in public method ./tesdat/api/special.py:9:1: D105 Missing docstring in magic method ./tesdat/containers/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/containers/base.py:1:1: D100 Missing docstring in public module ./tesdat/containers/base.py:9:1: D205 1 blank line required between summary line and description ./tesdat/containers/base.py:9:1: D400 First line should end with a period ./tesdat/containers/base.py:23:1: D200 One-line docstring should fit on one line with quotes ./tesdat/containers/base.py:23:1: D204 1 blank line required after class docstring ./tesdat/containers/base.py:26:1: D205 1 blank line required between summary line and description ./tesdat/containers/base.py:26:1: D400 First line should end with a period ./tesdat/containers/base.py:26:80: E501 line too long (90 > 79 characters) ./tesdat/containers/base.py:28:80: E501 line too long (87 > 79 characters) ./tesdat/containers/base.py:41:1: D102 Missing docstring in public method ./tesdat/containers/base.py:62:80: E501 line too long (81 > 79 characters) ./tesdat/containers/base.py:65:1: D102 Missing docstring in public method ./tesdat/containers/dict.py:1:1: D100 Missing docstring in public module ./tesdat/containers/dict.py:8:1: D101 Missing docstring in public class ./tesdat/containers/iter.py:1:1: D100 Missing docstring in public module ./tesdat/containers/iter.py:8:1: D200 One-line docstring should fit on one line with quotes ./tesdat/containers/iter.py:8:1: D204 1 blank line required after class docstring ./tesdat/containers/iter.py:8:1: D400 First line should end with a period ./tesdat/containers/iter.py:14:1: D105 Missing docstring in magic method ./tesdat/containers/iter.py:27:1: D102 Missing docstring in public method ./tesdat/containers/iter.py:30:1: D102 Missing docstring in public method ./tesdat/containers/iter.py:41:1: D105 Missing docstring in magic method ./tesdat/containers/list.py:1:1: D100 Missing docstring in public module ./tesdat/containers/list.py:8:1: D101 Missing docstring in public class ./tesdat/containers/list.py:12:80: E501 line too long (125 > 79 characters) ./tesdat/containers/list.py:28:1: D200 One-line docstring should fit on one line with quotes ./tesdat/containers/list.py:28:1: D400 First line should end with a period ./tesdat/containers/ordereddict.py:1:1: D100 Missing docstring in public module ./tesdat/containers/ordereddict.py:4:1: I100 Import statements are in the wrong order. from collections should be before from .dict ./tesdat/containers/ordereddict.py:4:1: I201 Missing newline before sections or imports. ./tesdat/containers/ordereddict.py:7:1: D101 Missing docstring in public class ./tesdat/containers/ordereddict.py:8:1: D102 Missing docstring in public method ./tesdat/formatters/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/formatters/base.py:1:1: D100 Missing docstring in public module ./tesdat/formatters/base.py:3:1: I100 Import statements are in the wrong order. import codecs should be before import os ./tesdat/formatters/base.py:8:1: D101 Missing docstring in public class ./tesdat/formatters/base.py:22:1: D102 Missing docstring in public method ./tesdat/formatters/csv.py:1:1: D100 Missing docstring in public module ./tesdat/formatters/csv.py:4:1: F401 'io' imported but unused ./tesdat/formatters/csv.py:5:1: F401 'os' imported but unused ./tesdat/formatters/csv.py:6:1: I100 Import statements are in the wrong order. import csv should be before import os ./tesdat/formatters/csv.py:9:1: F401 'OutputFileAlreadyExists' imported but unused ./tesdat/formatters/csv.py:9:1: I101 Imported names are in the wrong order. Should be CsvFormatterFieldsTypeInvalid, OutputFileAlreadyExists ./tesdat/formatters/csv.py:14:1: I100 Import statements are in the wrong order. from .. should be before from ..exceptions ./tesdat/formatters/csv.py:17:1: D101 Missing docstring in public class ./tesdat/formatters/csv.py:18:1: D102 Missing docstring in public method ./tesdat/formatters/csv.py:57:1: D102 Missing docstring in public method ./tesdat/formatters/json.py:1:1: D100 Missing docstring in public module ./tesdat/formatters/json.py:3:1: I201 Missing newline before sections or imports. ./tesdat/formatters/json.py:4:1: I101 Imported names are in the wrong order. Should be date, datetime, time ./tesdat/formatters/json.py:5:1: I201 Missing newline before sections or imports. ./tesdat/formatters/json.py:8:1: D200 One-line docstring should fit on one line with quotes ./tesdat/formatters/json.py:8:1: D204 1 blank line required after class docstring ./tesdat/formatters/json.py:18:1: D102 Missing docstring in public method ./tesdat/formatters/json.py:26:1: D101 Missing docstring in public class ./tesdat/formatters/json.py:27:1: D102 Missing docstring in public method ./tesdat/formatters/json.py:33:1: D102 Missing docstring in public method ./tesdat/formatters/pickle.py:1:1: D100 Missing docstring in public module ./tesdat/formatters/pickle.py:3:1: I201 Missing newline before sections or imports. ./tesdat/formatters/pickle.py:4:1: I201 Missing newline before sections or imports. ./tesdat/formatters/pickle.py:7:1: D101 Missing docstring in public class ./tesdat/formatters/pickle.py:8:1: D102 Missing docstring in public method ./tesdat/formatters/pickle.py:12:1: D102 Missing docstring in public method ./tesdat/formatters/string.py:1:1: D100 Missing docstring in public module ./tesdat/formatters/string.py:3:1: I201 Missing newline before sections or imports. ./tesdat/formatters/string.py:6:1: D101 Missing docstring in public class ./tesdat/formatters/string.py:7:1: D102 Missing docstring in public method ./tesdat/formatters/string.py:10:1: D102 Missing docstring in public method ./tesdat/models/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/models/base.py:1:1: D100 Missing docstring in public module ./tesdat/models/base.py:4:1: D101 Missing docstring in public class ./tesdat/models/dict.py:1:1: D100 Missing docstring in public module ./tesdat/models/dict.py:6:1: D200 One-line docstring should fit on one line with quotes ./tesdat/models/dict.py:6:1: D204 1 blank line required after class docstring ./tesdat/models/dict.py:26:80: E501 line too long (83 > 79 characters) ./tesdat/models/dict.py:39:80: E501 line too long (88 > 79 characters) ./tesdat/models/dict.py:48:13: E265 block comment should start with '# ' ./tesdat/models/list.py:1:1: D100 Missing docstring in public module ./tesdat/models/list.py:6:1: D200 One-line docstring should fit on one line with quotes ./tesdat/models/list.py:6:1: D204 1 blank line required after class docstring ./tesdat/models/list.py:40:1: D205 1 blank line required between summary line and description ./tesdat/models/list.py:44:80: E501 line too long (88 > 79 characters) ./tesdat/models/ordereddict.py:1:1: D100 Missing docstring in public module ./tesdat/models/ordereddict.py:3:1: I201 Missing newline before sections or imports. ./tesdat/models/ordereddict.py:6:1: D200 One-line docstring should fit on one line with quotes ./tesdat/models/ordereddict.py:6:1: D204 1 blank line required after class docstring ./tesdat/models/ordereddict.py:11:1: D102 Missing docstring in public method ./tesdat/patterns/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/patterns/__init__.py:2:1: W391 blank line at end of file ./tesdat/patterns/base.py:1:1: D100 Missing docstring in public module ./tesdat/patterns/base.py:4:1: D101 Missing docstring in public class ./tesdat/patterns/choice.py:1:1: D100 Missing docstring in public module ./tesdat/patterns/choice.py:3:1: I201 Missing newline before sections or imports. ./tesdat/patterns/choice.py:7:1: D200 One-line docstring should fit on one line with quotes ./tesdat/patterns/choice.py:7:1: D204 1 blank line required after class docstring ./tesdat/patterns/choice.py:10:1: D205 1 blank line required between summary line and description ./tesdat/patterns/choice.py:10:1: D400 First line should end with a period ./tesdat/patterns/choice.py:24:1: D105 Missing docstring in magic method ./tesdat/patterns/choice.py:28:1: D400 First line should end with a period ./tesdat/patterns/cycle.py:1:1: D100 Missing docstring in public module ./tesdat/patterns/cycle.py:6:1: D200 One-line docstring should fit on one line with quotes ./tesdat/patterns/cycle.py:6:1: D204 1 blank line required after class docstring ./tesdat/patterns/cycle.py:9:1: D205 1 blank line required between summary line and description ./tesdat/patterns/cycle.py:9:1: D400 First line should end with a period ./tesdat/patterns/cycle.py:11:80: E501 line too long (95 > 79 characters) ./tesdat/patterns/cycle.py:14:80: E501 line too long (80 > 79 characters) ./tesdat/patterns/cycle.py:31:1: D105 Missing docstring in magic method ./tesdat/patterns/hashof.py:1:1: D100 Missing docstring in public module ./tesdat/patterns/hashof.py:10:1: D103 Missing docstring in public function ./tesdat/patterns/hashof.py:20:1: D200 One-line docstring should fit on one line with quotes ./tesdat/patterns/hashof.py:20:1: D204 1 blank line required after class docstring ./tesdat/patterns/hashof.py:23:1: D205 1 blank line required between summary line and description ./tesdat/patterns/hashof.py:23:80: E501 line too long (84 > 79 characters) ./tesdat/patterns/hashof.py:26:80: E501 line too long (92 > 79 characters) ./tesdat/patterns/hashof.py:28:80: E501 line too long (80 > 79 characters) ./tesdat/patterns/hashof.py:37:1: D105 Missing docstring in magic method ./tesdat/patterns/increment.py:1:1: D100 Missing docstring in public module ./tesdat/patterns/increment.py:7:1: D200 One-line docstring should fit on one line with quotes ./tesdat/patterns/increment.py:7:1: D204 1 blank line required after class docstring ./tesdat/patterns/increment.py:10:1: D205 1 blank line required between summary line and description ./tesdat/patterns/increment.py:14:80: E501 line too long (80 > 79 characters) ./tesdat/patterns/increment.py:21:1: D105 Missing docstring in magic method ./tesdat/patterns/pickout.py:1:1: D100 Missing docstring in public module ./tesdat/patterns/pickout.py:8:1: D200 One-line docstring should fit on one line with quotes ./tesdat/patterns/pickout.py:8:1: D204 1 blank line required after class docstring ./tesdat/patterns/pickout.py:11:1: D205 1 blank line required between summary line and description ./tesdat/patterns/pickout.py:11:1: D400 First line should end with a period ./tesdat/patterns/pickout.py:13:80: E501 line too long (93 > 79 characters) ./tesdat/patterns/pickout.py:15:80: E501 line too long (80 > 79 characters) ./tesdat/patterns/pickout.py:27:1: D105 Missing docstring in magic method ./tesdat/patterns/pickout.py:31:1: D400 First line should end with a period ./tesdat/patterns/sequence.py:1:1: D100 Missing docstring in public module ./tesdat/patterns/sequence.py:7:1: D200 One-line docstring should fit on one line with quotes ./tesdat/patterns/sequence.py:7:1: D204 1 blank line required after class docstring ./tesdat/patterns/sequence.py:10:1: D205 1 blank line required between summary line and description ./tesdat/patterns/sequence.py:10:1: D400 First line should end with a period ./tesdat/patterns/sequence.py:12:80: E501 line too long (95 > 79 characters) ./tesdat/patterns/sequence.py:15:80: E501 line too long (80 > 79 characters) ./tesdat/patterns/sequence.py:32:1: D105 Missing docstring in magic method ./tesdat/patterns/sequence.py:36:1: D400 First line should end with a period ./tesdat/tests/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/tests/test_api.py:1:1: D100 Missing docstring in public module ./tesdat/tests/test_api.py:5:1: D101 Missing docstring in public class ./tesdat/tests/test_api.py:10:1: D102 Missing docstring in public method ./tesdat/tests/test_api.py:17:1: D102 Missing docstring in public method ./tesdat/tests/test_api.py:20:1: D102 Missing docstring in public method ./tesdat/tests/test_api.py:23:80: E501 line too long (84 > 79 characters) ./tesdat/tests/test_api.py:25:1: D102 Missing docstring in public method ./tesdat/tests/test_example.py:1:1: D100 Missing docstring in public module ./tesdat/tests/test_example.py:5:1: D101 Missing docstring in public class ./tesdat/tests/test_example.py:6:1: D102 Missing docstring in public method ./tesdat/tests/test_example.py:25:1: D102 Missing docstring in public method ./tesdat/tests/test_example.py:43:1: D102 Missing docstring in public method ./tesdat/tests/test_example.py:54:80: E501 line too long (81 > 79 characters) ./tesdat/tests/test_index.py:1:1: D100 Missing docstring in public module ./tesdat/tests/test_index.py:5:1: D101 Missing docstring in public class ./tesdat/tests/test_index.py:10:1: D102 Missing docstring in public method ./tesdat/tests/test_index.py:14:1: D102 Missing docstring in public method ./tesdat/tests/containers/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/tests/containers/test_base.py:1:1: D100 Missing docstring in public module ./tesdat/tests/containers/test_base.py:5:1: D101 Missing docstring in public class ./tesdat/tests/containers/test_base.py:10:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_base.py:15:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_base.py:23:1: D101 Missing docstring in public class ./tesdat/tests/containers/test_base.py:24:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_dict.py:1:1: D100 Missing docstring in public module ./tesdat/tests/containers/test_dict.py:5:1: D101 Missing docstring in public class ./tesdat/tests/containers/test_dict.py:10:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_dict.py:25:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_dict.py:40:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_iter.py:1:1: D100 Missing docstring in public module ./tesdat/tests/containers/test_iter.py:5:1: D101 Missing docstring in public class ./tesdat/tests/containers/test_iter.py:10:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_iter.py:14:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_iter.py:25:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_list.py:1:1: D100 Missing docstring in public module ./tesdat/tests/containers/test_list.py:5:1: D101 Missing docstring in public class ./tesdat/tests/containers/test_list.py:10:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_list.py:25:1: D102 Missing docstring in public method ./tesdat/tests/containers/test_ordereddict.py:1:1: D100 Missing docstring in public module ./tesdat/tests/containers/test_ordereddict.py:5:1: D101 Missing docstring in public class ./tesdat/tests/containers/test_ordereddict.py:10:1: D102 Missing docstring in public method ./tesdat/tests/formatters/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/tests/formatters/test_base.py:1:1: D100 Missing docstring in public module ./tesdat/tests/formatters/test_csv.py:1:1: D100 Missing docstring in public module ./tesdat/tests/formatters/test_csv.py:3:1: F401 'time' imported but unused ./tesdat/tests/formatters/test_csv.py:7:1: D101 Missing docstring in public class ./tesdat/tests/formatters/test_csv.py:10:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_csv.py:20:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_csv.py:33:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_csv.py:46:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_json.py:1:1: D100 Missing docstring in public module ./tesdat/tests/formatters/test_json.py:6:1: D101 Missing docstring in public class ./tesdat/tests/formatters/test_json.py:11:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_pickle.py:1:1: D100 Missing docstring in public module ./tesdat/tests/formatters/test_pickle.py:7:1: D101 Missing docstring in public class ./tesdat/tests/formatters/test_pickle.py:10:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_pickle.py:20:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_string.py:1:1: D100 Missing docstring in public module ./tesdat/tests/formatters/test_string.py:6:1: D101 Missing docstring in public class ./tesdat/tests/formatters/test_string.py:9:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_string.py:19:1: D102 Missing docstring in public method ./tesdat/tests/formatters/test_string.py:26:1: D102 Missing docstring in public method ./tesdat/tests/models/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/tests/models/test_dict.py:1:1: D100 Missing docstring in public module ./tesdat/tests/models/test_dict.py:5:1: D103 Missing docstring in public function ./tesdat/tests/models/test_dict.py:9:1: D101 Missing docstring in public class ./tesdat/tests/models/test_dict.py:14:1: D102 Missing docstring in public method ./tesdat/tests/models/test_dict.py:21:1: D102 Missing docstring in public method ./tesdat/tests/models/test_dict.py:42:1: D102 Missing docstring in public method ./tesdat/tests/models/test_dict.py:56:1: D102 Missing docstring in public method ./tesdat/tests/models/test_dict.py:76:1: D102 Missing docstring in public method ./tesdat/tests/models/test_list.py:1:1: D100 Missing docstring in public module ./tesdat/tests/models/test_list.py:6:1: D103 Missing docstring in public function ./tesdat/tests/models/test_list.py:10:1: D101 Missing docstring in public class ./tesdat/tests/models/test_list.py:15:1: D102 Missing docstring in public method ./tesdat/tests/models/test_list.py:20:1: D102 Missing docstring in public method ./tesdat/tests/models/test_list.py:25:1: D102 Missing docstring in public method ./tesdat/tests/models/test_list.py:46:1: D102 Missing docstring in public method ./tesdat/tests/models/test_list.py:60:1: D102 Missing docstring in public method ./tesdat/tests/models/test_list.py:77:1: D102 Missing docstring in public method ./tesdat/tests/patterns/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/tests/patterns/test_choice.py:1:1: D100 Missing docstring in public module ./tesdat/tests/patterns/test_choice.py:5:1: D101 Missing docstring in public class ./tesdat/tests/patterns/test_choice.py:10:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_choice.py:17:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_cycle.py:1:1: D100 Missing docstring in public module ./tesdat/tests/patterns/test_cycle.py:5:1: D101 Missing docstring in public class ./tesdat/tests/patterns/test_cycle.py:10:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_cycle.py:17:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_hashof.py:1:1: D100 Missing docstring in public module ./tesdat/tests/patterns/test_hashof.py:11:1: D101 Missing docstring in public class ./tesdat/tests/patterns/test_hashof.py:16:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_increment.py:1:1: D100 Missing docstring in public module ./tesdat/tests/patterns/test_increment.py:6:1: D101 Missing docstring in public class ./tesdat/tests/patterns/test_increment.py:11:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_increment.py:18:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_pickout.py:1:1: D100 Missing docstring in public module ./tesdat/tests/patterns/test_pickout.py:5:1: D101 Missing docstring in public class ./tesdat/tests/patterns/test_pickout.py:10:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_pickout.py:19:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_sequence.py:1:1: D100 Missing docstring in public module ./tesdat/tests/patterns/test_sequence.py:5:1: D101 Missing docstring in public class ./tesdat/tests/patterns/test_sequence.py:10:1: D102 Missing docstring in public method ./tesdat/tests/patterns/test_sequence.py:19:1: D102 Missing docstring in public method ./tesdat/tests/utils/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/tests/utils/test_datetime.py:1:1: D100 Missing docstring in public module ./tesdat/tests/utils/test_datetime.py:3:1: I100 Import statements are in the wrong order. from datetime should be before from unittest ./tesdat/tests/utils/test_datetime.py:9:1: D101 Missing docstring in public class ./tesdat/tests/utils/test_datetime.py:14:1: D102 Missing docstring in public method ./tesdat/tests/utils/test_datetime.py:20:1: D102 Missing docstring in public method ./tesdat/tests/utils/test_datetime.py:30:1: D102 Missing docstring in public method ./tesdat/tests/utils/test_datetime.py:43:1: D102 Missing docstring in public method ./tesdat/tests/utils/test_datetime.py:59:1: D101 Missing docstring in public class ./tesdat/tests/utils/test_datetime.py:64:1: D102 Missing docstring in public method ./tesdat/tests/utils/test_datetime.py:73:80: E501 line too long (85 > 79 characters) ./tesdat/tests/utils/test_datetime.py:76:1: D101 Missing docstring in public class ./tesdat/tests/utils/test_datetime.py:81:1: D102 Missing docstring in public method ./tesdat/tests/utils/test_datetime.py:85:1: D102 Missing docstring in public method ./tesdat/utils/__init__.py:1:1: D104 Missing docstring in public package ./tesdat/utils/datetime.py:1:1: D100 Missing docstring in public module ./tesdat/utils/datetime.py:6:1: I101 Imported names are in the wrong order. Should be date, datetime, time, timedelta ./tesdat/utils/datetime.py:20:1: E402 module level import not at top of file ./tesdat/utils/datetime.py:63:1: D400 First line should end with a period ./tesdat/utils/datetime.py:87:1: D400 First line should end with a period ERROR: InvocationError: '/home/vagrant/tesdat/.tox/flake8/bin/flake8 .' ____________________________________________________ summary _____________________________________________________ ERROR: py27: commands failed ERROR: py33: commands failed ERROR: py34: commands failed ERROR: py35: commands failed ERROR: flake8: commands failed

ってうわー、すごいエラー(警告)が。

実はエラーに関しては前から気付いていて、辞書の値が特定の順番になることを前提にテストを書いていたため、エラーになっています。

また脱線しますが、これは実行時に決まる PYTHONHASHSEED という環境変数に左右されます。

この場合は PYTHONHASHSEED='115669612' であればテストは通るのです。

(venv3.5) [vagrant@localhost tesdat]$ PYTHONHASHSEED='115669612' tox ========================================================================================== 中略 ========================================================================================== ___________________________________________________________ summary ___________________________________________________________ py27: commands succeeded py33: commands succeeded py34: commands succeeded py35: commands succeeded ERROR: flake8: commands failed

とりあえず、テストを直します。興味ないかもしれませんが、こんな感じ。

diff --git a/tesdat/tests/formatters/test_json.py b/tesdat/tests/formatters/test_json.py index 19f2542..796672b 100644 --- a/tesdat/tests/formatters/test_json.py +++ b/tesdat/tests/formatters/test_json.py @@ -1,4 +1,5 @@ # coding: utf-8 +import json from datetime import time from unittest import TestCase @@ -11,8 +12,8 @@ class TestJsonFormatter(TestCase): def test_stringify(self): sf = self._getClass({'a': time(23, 59), 'b': ('2',)}, indent=None) self.assertEqual( - sf.stringify(), - '{"a": "23:59:00", "b": ["2"]}' + json.loads(sf.stringify()), + {"a": "23:59:00", "b": ["2"]} ) sf = self._getClass({list}, indent=None) diff --git a/tesdat/tests/formatters/test_string.py b/tesdat/tests/formatters/test_string.py index 372b6c7..94c025c 100644 --- a/tesdat/tests/formatters/test_string.py +++ b/tesdat/tests/formatters/test_string.py @@ -19,8 +19,8 @@ class TestStringFormatter(TestCase): def test_string(self): sf = self._getClass({'a': 1, 'b': ('2',)}) self.assertEqual( - sf.stringify(), - "{'a': 1, 'b': ('2',)}" + eval(sf.stringify()), + {'a': 1, 'b': ('2',)} )

先ほどは失敗した PYTHONHASHSEED で通ることを確認しました。

(venv3.5) [vagrant@localhost tesdat]$ $ PYTHONHASHSEED='1127406230' tox ========================================================================================== 中略 ========================================================================================== ___________________________________________________________ summary ___________________________________________________________ py27: commands succeeded py33: commands succeeded py34: commands succeeded py35: commands succeeded ERROR: flake8: commands failed

ちなみに PYTHONHASHSEED に限らず環境変数を固定したい場合、tox.iniの testenv セクションに setenv を指定します。

[testenv] setenv = PYTHONHASHSEED = 1127406230

flake8

はい、 flake8 に手を付けます。ここは少々脱線気味なので読み飛ばしてもいいです。

この中には無視したいものもあるはずです。吟味したうえで要らないものは除外設定しましょう。除外設定は tox.ini の flake8 セクションに書きます。

それぞれ何を言われているのでしょうか。 さすがに全てを洗い出すのはしんどいので簡単にまとめました。

CODE対応説明要約
I100無視Import statements are in the wrong order.importするモジュールの順序が不正。ただどうすればいいのかはよくわからない。
I101無視Imported names are in the wrong order. Should be xxx, yyy, zzzimport順がアルファベット順になっているべき。
I201直すMissing newline before sections or imports.importする対象のモジュール種類によって空行を入れるべき
E226直すmissing whitespace around arithmetic operator演算子の前後に空行が必要。
E265直すblock comment should start with '# 'コメント記号 `#` と文字列との間に空白が1つ必要。
E302直すexpected 2 blank lines, found x関数やクラスの定義は2行空けるべき。
E402直すmodule level import not at top of fileこれは `__init__.py` で発生してる模様なので、このファイルをflake8の対象から除外。
E501直すline too long (xx > 79 characters)よく見る1行当たりの文字数オーバー。個人で開発してるのにこんなこと気にしたくないです。とはいえ長すぎるのも考え物なので倍の160文字にします。
F401直すimported but unusedimportされてるけど使われてない。本来であれば気にするんですが、このライブラリでは定義されていてユーザに使われる目的の変数もあるのです。 なので、 `__init__.py` をflake8の対象から除外することで対応する。
F821直すundefined name 'xxx'変数が定義されていない。これは `__init__.py` で発生してる模様なので、flake8の 対象から除外することで対応する。
D100無視Missing docstring in public module公開モジュールにdocstringで説明がない。ちなみにD開始はdocstring関連です ワンピースは関係ない。
D101無視Missing docstring in public class公開クラスにdocstringがない。(以下略
D103無視Missing docstring in public function公開メソッドに(以下略
D104無視Missing docstring in public package公開パッケージに(以下略
D105無視Missing docstring in magic methodマジックメソッドに(以下略
D200直すOne-line docstring should fit on one line with quotes一行のdocstringは改行なしで表示しよう。
D204直す1 blank line required after class docstringクラスレベルのdocstingの後には1行空行が必要。
D205直す1 blank line required between summary line and descriptiondocstringの前後に不要な改行がある。見やすいように改行を入れた改行がよくなかったらしい。
D210直すNo whitespaces allowed surrounding docstring textdocstringの前後には空白がいらない。
D400無視First line should end with a perioddocstringの先頭行の末尾は .(ドット) で終わってないといけない
W391直すblank line at end of fileファイルの末尾に余計な空行がある。

こんな感じで対応します。反映させた tox.ini が以下です。

[flake8] ignore = D100, D101, D102, D103, D104, D105, D400, I100, I101 exclude = tests/*, tesdat/__init__.py, tesdat/compat.py max-line-length = 160

もちろんコードも直しました。

.toxディレクトリ

一度toxを実行すると、 .tox/ 配下にenvlist毎の設定を作成し、次回以降は再利用されます。

(venv3.5) [vagrant@localhost tesdat]$ ls .tox/ dist flake8 log py27 py33 py34 py35

一旦初期化したい場合は「-r」「--recreate」オプションを指定しましょう。 たまに前の実行結果に引きずられてテストが落ちたりすることがあるので、ハマったら一度試してみましょう。

カバレッジとる

ついでなのでカバレッジを取りましょう。 tox.iniを以下のように編集します。

[testenv] commands = py.test --cov deps = pytest-cov

pytest-cov をインストールし、--covオプションを指定します。これにより、実行時にカバレッジが取得されます。

$ coverage html を実行すると htmlcov/ に HTMLファイル が作成されるのでお好みで実行してください。

で、これだと検査したくないファイルまで対象となってしまうため、 coveragerc によって除外します。

[run] omit = .tox/*, setup.py, tesdat/tests/*

あとリポジトリに入らないように .gitignore に以下を追加します。

.coverage coverage.xml htmlcov/*

実行してみましょう。

(venv3.5) [vagrant@localhost tesdat]$ tox -r GLOB sdist-make: /home/vagrant/tesdat/setup.py py27 recreate: /home/vagrant/tesdat/.tox/py27 py27 installdeps: pytest-cov py27 inst: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py27 installed: coverage==4.0.3,py==1.4.31,pytest==2.9.1,pytest-cov==2.2.1,tesdat==2.1.1 py27 runtests: PYTHONHASHSEED='2232003898' py27 runtests: commands[0] | py.test --cov ================================================= test session starts ================================================= platform linux2 -- Python 2.7.10, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: cov-2.2.1 collected 58 items tests-require.txt s tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py . tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py .. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ---------------------------------- coverage: platform linux2, python 2.7.10-final-0 ----------------------------------- Name Stmts Miss Cover ------------------------------------------------------ tesdat/__init__.py 45 8 82% tesdat/api/__init__.py 0 0 100% tesdat/api/display.py 15 1 93% tesdat/api/render.py 23 4 83% tesdat/api/special.py 7 1 86% tesdat/compat.py 37 18 51% tesdat/containers/__init__.py 0 0 100% tesdat/containers/base.py 37 3 92% tesdat/containers/dict.py 17 0 100% tesdat/containers/iter.py 31 0 100% tesdat/containers/list.py 19 1 95% tesdat/containers/ordereddict.py 6 0 100% tesdat/exceptions.py 8 0 100% tesdat/formatters/__init__.py 0 0 100% tesdat/formatters/base.py 17 1 94% tesdat/formatters/csv.py 42 5 88% tesdat/formatters/json.py 20 0 100% tesdat/formatters/pickle.py 9 0 100% tesdat/formatters/string.py 7 0 100% tesdat/models/__init__.py 0 0 100% tesdat/models/base.py 2 0 100% tesdat/models/dict.py 27 1 96% tesdat/models/list.py 34 1 97% tesdat/patterns/__init__.py 0 0 100% tesdat/patterns/base.py 3 0 100% tesdat/patterns/choice.py 18 1 94% tesdat/patterns/cycle.py 26 1 96% tesdat/patterns/hashof.py 20 3 85% tesdat/patterns/increment.py 16 2 88% tesdat/patterns/pickout.py 25 2 92% tesdat/patterns/sequence.py 30 1 97% tesdat/utils/__init__.py 0 0 100% tesdat/utils/datetime.py 48 6 88% ------------------------------------------------------ TOTAL 589 60 90% ======================================== 57 passed, 1 skipped in 0.82 seconds ========================================= py27 runtests: commands[1] | coverage html py33 recreate: /home/vagrant/tesdat/.tox/py33 py33 installdeps: pytest-cov py33 inst: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py33 installed: coverage==4.0.3,py==1.4.31,pytest==2.9.1,pytest-cov==2.2.1,tesdat==2.1.1 py33 runte sts: PYTHONHASHSEED='2232003898' py33 runtests: commands[0] | py.test --cov ================================================= test session starts ================================================= platform linux -- Python 3.3.6, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: cov-2.2.1 collected 58 items tests-require.txt s tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py . tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py .. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ----------------------------------- coverage: platform linux, python 3.3.6-final-0 ------------------------------------ Name Stmts Miss Cover ------------------------------------------------------ tesdat/__init__.py 45 8 82% tesdat/api/__init__.py 0 0 100% tesdat/api/display.py 15 1 93% tesdat/api/render.py 23 4 83% tesdat/api/special.py 7 1 86% tesdat/compat.py 37 4 89% tesdat/containers/__init__.py 0 0 100% tesdat/containers/base.py 37 3 92% tesdat/containers/dict.py 17 0 100% tesdat/containers/iter.py 31 0 100% tesdat/containers/list.py 19 1 95% tesdat/containers/ordereddict.py 6 0 100% tesdat/exceptions.py 8 0 100% tesdat/formatters/__init__.py 0 0 100% tesdat/formatters/base.py 17 1 94% tesdat/formatters/csv.py 42 5 88% tesdat/formatters/json.py 20 0 100% tesdat/formatters/pickle.py 9 0 100% tesdat/formatters/string.py 7 0 100% tesdat/models/__init__.py 0 0 100% tesdat/models/base.py 2 0 100% tesdat/models/dict.py 27 1 96% tesdat/models/list.py 34 1 97% tesdat/patterns/__init__.py 0 0 100% tesdat/patterns/base.py 3 0 100% tesdat/patterns/choice.py 18 1 94% tesdat/patterns/cycle.py 26 1 96% tesdat/patterns/hashof.py 20 3 85% tesdat/patterns/increment.py 16 2 88% tesdat/patterns/pickout.py 25 2 92% tesdat/patterns/sequence.py 30 1 97% tesdat/utils/__init__.py 0 0 100% tesdat/utils/datetime.py 48 6 88% ------------------------------------------------------ TOTAL 589 46 92% ======================================== 57 passed, 1 skipped in 1.09 seconds ========================================= py33 runtests: commands[1] | coverage html py34 recreate: /home/vagrant/tesdat/.tox/py34 py34 installdeps: pytest-cov py34 inst: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py34 installed: coverage==4.0.3,py==1.4.31,pytest==2.9.1,pytest-cov==2.2.1,tesdat==2.1.1 py34 runtests: PYTHONHASHSEED='2232003898' py34 runtests: commands[0] | py.test --cov ================================================= test session starts ================================================= platform linux -- Python 3.4.4, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: cov-2.2.1 collected 58 items tests-require.txt s tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py . tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py .. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ----------------------------------- coverage: platform linux, python 3.4.4-final-0 ------------------------------------ Name Stmts Miss Cover ------------------------------------------------------ tesdat/__init__.py 45 8 82% tesdat/api/__init__.py 0 0 100% tesdat/api/display.py 15 1 93% tesdat/api/render.py 23 4 83% tesdat/api/special.py 7 1 86% tesdat/compat.py 37 4 89% tesdat/containers/__init__.py 0 0 100% tesdat/containers/base.py 37 3 92% tesdat/containers/dict.py 17 0 100% tesdat/containers/iter.py 31 0 100% tesdat/containers/list.py 19 1 95% tesdat/containers/ordereddict.py 6 0 100% tesdat/exceptions.py 8 0 100% tesdat/formatters/__init__.py 0 0 100% tesdat/formatters/base.py 17 1 94% tesdat/formatters/csv.py 42 5 88% tesdat/formatters/json.py 20 0 100% tesdat/formatters/pickle.py 9 0 100% tesdat/formatters/string.py 7 0 100% tesdat/models/__init__.py 0 0 100% tesdat/models/base.py 2 0 100% tesdat/models/dict.py 27 1 96% tesdat/models/list.py 34 1 97% tesdat/patterns/__init__.py 0 0 100% tesdat/patterns/base.py 3 0 100% tesdat/patterns/choice.py 18 1 94% tesdat/patterns/cycle.py 26 1 96% tesdat/patterns/hashof.py 20 3 85% tesdat/patterns/increment.py 16 2 88% tesdat/patterns/pickout.py 25 2 92% tesdat/patterns/sequence.py 30 1 97% tesdat/utils/__init__.py 0 0 100% tesdat/utils/datetime.py 48 6 88% ------------------------------------------------------ TOTAL 589 46 92% ======================================== 57 passed, 1 skipped in 1.14 seconds ========================================= py34 runtests: commands[1] | coverage html py35 recreate: /home/vagrant/tesdat/.tox/py35 py35 installdeps: pytest-cov py35 inst: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip py35 installed: coverage==4.0.3,py==1.4.31,pytest==2.9.1,pytest-cov==2.2.1,tesdat==2.1.1 py35 runtests: PYTHONHASHSEED='2232003898' py35 runtests: commands[0] | py.test --cov ================================================= test session starts ================================================= platform linux -- Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: /home/vagrant/tesdat, inifile: plugins: cov-2.2.1 collected 58 items tests-require.txt s tesdat/tests/test_api.py .... tesdat/tests/test_example.py ... tesdat/tests/test_index.py .. tesdat/tests/containers/test_base.py ... tesdat/tests/containers/test_dict.py ... tesdat/tests/containers/test_iter.py ... tesdat/tests/containers/test_list.py .. tesdat/tests/containers/test_ordereddict.py . tesdat/tests/formatters/test_csv.py ... tesdat/tests/formatters/test_json.py . tesdat/tests/formatters/test_pickle.py . tesdat/tests/formatters/test_string.py .. tesdat/tests/models/test_dict.py ..... tesdat/tests/models/test_list.py ...... tesdat/tests/patterns/test_choice.py .. tesdat/tests/patterns/test_cycle.py .. tesdat/tests/patterns/test_hashof.py . tesdat/tests/patterns/test_increment.py .. tesdat/tests/patterns/test_pickout.py .. tesdat/tests/patterns/test_sequence.py .. tesdat/tests/utils/test_datetime.py ....... ----------------------------------- coverage: platform linux, python 3.5.1-final-0 ------------------------------------ Name Stmts Miss Cover ------------------------------------------------------ tesdat/__init__.py 45 8 82% tesdat/api/__init__.py 0 0 100% tesdat/api/display.py 15 1 93% tesdat/api/render.py 23 4 83% tesdat/api/special.py 7 1 86% tesdat/compat.py 37 4 89% tesdat/containers/__init__.py 0 0 100% tesdat/containers/base.py 37 3 92% tesdat/containers/dict.py 17 0 100% tesdat/containers/iter.py 31 0 100% tesdat/containers/list.py 19 1 95% tesdat/containers/ordereddict.py 6 0 100% tesdat/exceptions.py 8 0 100% tesdat/formatters/__init__.py 0 0 100% tesdat/formatters/base.py 17 1 94% tesdat/formatters/csv.py 42 5 88% tesdat/formatters/json.py 20 0 100% tesdat/formatters/pickle.py 9 0 100% tesdat/formatters/string.py 7 0 100% tesdat/models/__init__.py 0 0 100% tesdat/models/base.py 2 0 100% tesdat/models/dict.py 27 1 96% tesdat/models/list.py 34 1 97% tesdat/patterns/__init__.py 0 0 100% tesdat/patterns/base.py 3 0 100% tesdat/patterns/choice.py 18 1 94% tesdat/patterns/cycle.py 26 1 96% tesdat/patterns/hashof.py 20 3 85% tesdat/patterns/increment.py 16 2 88% tesdat/patterns/pickout.py 25 2 92% tesdat/patterns/sequence.py 30 1 97% tesdat/utils/__init__.py 0 0 100% tesdat/utils/datetime.py 48 6 88% ------------------------------------------------------ TOTAL 589 46 92% ======================================== 57 passed, 1 skipped in 1.08 seconds ========================================= py35 runtests: commands[1] | coverage html flake8 recreate: /home/vagrant/tesdat/.tox/flake8 flake8 installdeps: flake8, flake8-blind-except, flake8-docstrings, flake8-import-order, mccabe, radon flake8 inst: /home/vagrant/tesdat/.tox/dist/tesdat-2.1.1.zip flake8 installed: colorama==0.3.7,flake8==2.5.4,flake8-blind-except==0.1.0,flake8-docstrings==0.2.6,flake8-import-order==0.7,mando==0.3.3,mccabe==0.4.0,pep257==0.7.0,pep8==1.7.0,pyflakes==1.0.0,radon==1.3.2,tesdat==2.1.1 flake8 runtests: PYTHONHASHSEED='2232003898' flake8 runtests: commands[0] | flake8 . _______________________________________________________ summary _______________________________________________________ py27: commands succeeded py33: commands succeeded py34: commands succeeded py35: commands succeeded flake8: commands succeeded congratulations :)

htmlcov/index.html にアクセスすると以下のように表示されます。

埋め込める変数

上では説明しませんでしたが、 tox.ini 中には {spam} のように変数を埋め込むことができます。

重要そうな2つの変数については軽く動作を見ておきましょう。

posargs

posargsはtoxから渡された引数を参照します。 例えばtox.iniのコマンドを以下のように書き換え

[testenv] commands = py.test --cov . {posargs}

tox -e py35 tesdat/tests/test_api.py のように実行すると テストコマンドとしては py.test --cov . tesdat/tests/test_api.py のように展開されます。

pytestコマンドのテスト対象については pytestの記事 で解説しています。

-- から始まる引数はtoxのオプションとして認識され、そんなのないよってことでエラーになっちゃうので注意してください。

(venv3.5) [vagrant@localhost tesdat]$ tox --aa usage: tox [--version] [-h] [--help-ini] [-v] [--showconfig] [-l] [-c CONFIGFILE] [-e envlist] [--notest] [--sdistonly] [--installpkg PATH] [--develop] [-i URL] [--pre] [-r] [--result-json PATH] [--hashseed SEED] [--force-dep REQ] [--sitepackages] [--skip-missing-interpreters] [args [args ...]] tox: error: unrecognized arguments: --aa

また、 pytest に対してオプションを渡したい場合は -- のあとに更にオプションを指定してください。

$ tox -- -k test_target

toxinidir

この変数はtox.iniがおいてあるディレクトリのパスを示します。

どこかに移動してからテストを実行したいとかであれば以下のように書けますね。

[testenv] changedir = {toxinidir}/apps/ commands = py.test --cov .

toxについて記述がある数少ない書籍です。