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

今回は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 での構文を確認するためにライブラリをインストールする

って感じですね。

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

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

Note

  • デフォルト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, zzz

import順がアルファベット順になっているべき。

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 unused

importされてるけど使われてない。

本来であれば気にするんですが、このライブラリでは定義されていてユーザに使われる目的の変数もあるのです。 なので、 __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 description

docstringの前後に不要な改行がある。見やすいように改行を入れた改行がよくなかったらしい。

D210

直す

No whitespaces allowed surrounding docstring text

docstringの前後には空白がいらない。

D400

無視

First line should end with a period

docstringの先頭行の末尾は .(ドット) で終わってないといけない

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 にアクセスすると以下のように表示されます。

coverage

埋め込める変数

上では説明しませんでしたが、 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 .

image1

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

image2

次は tox x vagrantでハマったこと について書きます。