のんびりSEの議事録

プログラミング系のポストからアプリに関してのポストなどをしていきます。まれにアニメ・マンガなど

Pytestを利用したPythonのユニットテスト

最近ちょっと忙しくなってきて、ブログの更新頻度が減ってきてたので、ぼちぼち更新していこうかなと。。。

ということで、テスト関連で少し記載していきます。

今まで、Pythonのテストとしては、組み込みの unittest を利用することが多かったのですが、最近 pytest を触れる機会があったので、そちらに関して記載して行こうと思います。

Pytestとは

Pythonのテスティングフレームワークで、小さくて簡単なテストから、複雑なテストまでサポートをしてくれるようです。

pytest: helps you write better programs — pytest documentation

インストール

pip install pytest

使い方

pytest コマンドで、ファイル名の先頭が test_で始まるファイル(デフォルト設定)内の、先頭がtest_で始まる関数(メソッド)をテスト対象として、テストを実行します。 assert文を利用し、結果がFAILのものを出力してくれます。

def inc(x):
    return x + 1

def test_answer():
    assert inc(1) == 2
    assert inc(2) == 2
pytest

============================================================================================================ test session starts =============================================================================================================
platform darwin -- Python 3.6.1, pytest-3.6.2, py-1.5.3, pluggy-0.6.0
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item

test_sample.py F                                                                                                                                                                                                                       [100%]

================================================================================================================== FAILURES ==================================================================================================================
________________________________________________________________________________________________________________ test_answer _________________________________________________________________________________________________________________

    def test_answer():
        assert inc(1) == 2
>       assert inc(2) == 2
E       assert 3 == 2
E        +  where 3 = inc(2)

test_sample.py:9: AssertionError
========================================================================================================== 1 failed in 0.14 seconds ==========================================================================================================

assert文について

assertの条件に満たないものについて、AssertionError を発生させます。

assert 条件, メッセージ

詳しくは、以下の記事が参考になりました。

uxmilk.jp

クラスベース

テストケースをクラスごとに、グルーピングすることが可能です。

class TestClass(object):
    def test_one(self):
        x = "this"
        assert 'h' in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, 'check')

fixture

pytestにはfixtureという機能があり、これを利用することで、繰り返し実行するケースや、テストの前後処理等、様々な場面で活用することが出来ます。

import pytest


@pytest.fixture
def user_api_mock():
    return {
            'first_name': 'Taro',
            'last_name': 'Tanaka',
            'age': 32
            }

def test_fullname(user_api_mock):
    assert user_api_mock['first_name'] + user_api_mock['last_name'] == 'TaroTanaka'

setup/teardown

unittestの setUptearDownに相当する機能です。

  • Moduleレベル
def setup_module(module):
    """ モジュールテスト実行の前処理 """

def teardown_module(module):
    """ モジュールテスト実行の後処理 """ 
  • Classレベル
class TestClass(object):

    @classmethod
    def setup_class(self):
        """ テストclass実行の前処理 """

    @classmethod
    def teardown_class(self):
        """ テストclass実行の後処理 """

設定

実行対象のディレクトリ以下に、conftest.pyファイル名で、pytestの設定を変えることが出来ます。

  • 例: pytestに引数を追加する
import pytest


# pytest_plugins = 'plugin_name'  # pluginの追加も可能

def pytest_addoption(parser):
    parser.addoption('--target', default='unit', dest='target', help='Target test type (all|e2e|unit)')

def pytest_configure(config):
    config.addinivalue_line('markers', 'target(name): Target test type name.')

def pytest_runtest_setup(item):
    target = [mark.args[0] for mark in item.iter_markers(name='target')]
    if target:
        if item.config.getoption('--target') not in target:
            pytest.skip('test require target in %r' % target)
pytest --help

...

custom options:
  --target=TARGET       Target test type (all|e2e|unit)

プラグイン

よく使うプラグインをいくつか紹介です。

Good Integration Practices

公式で紹介されている、Good Integration Practicesの解説をかいつまんでみました。

Pythonのパッケージ開発の構造。他にもいくつかありますが、とりあえず、ここでは1パッケージ単位で解説します。

setup.py
mypkg/
    __init__.py
    app.py
    view.py
tests/
    test_app.py
    test_view.py
  • setup.py

pytest-runnerプラグインを使用して、テスト実行をsetuptoolsベースのプロジェクトに統合することができます。

from setuptools import setup

setup(
    # ...,
    setup_requires=["pytest-runner", ...],
    tests_require=["pytest", ...], # ここで予めplugin入れてもok
    # ...,
)
  • setup.cfg

aliasesとしてpytestを指定します。

[aliases]
test=pytest

pytestの設定も指定することが出来ます。

[tool:pytest]
addopts = --verbose --cov cchan_web --cov-report html --cov-config .coveragerc
python_files = tests/*/test_*.py
norecursedirs =
    .git
    .tox
    .env
    venv
    dist
    build
    south_migrations
    migrations
    example

.coveragerc で pytest-covの設定が指定できます。

  • 実行
python setup.py test

参考

note.crohaco.net

www.qoosky.io