Python + pytestにて、pytestに独自のコマンドラインオプションを追加する

Python + pytestにて、コマンドラインオプションを追加して、コマンドラインから値を受け取った時のメモを残します。

 
目次

 

環境

 

pytestのコマンドラインオプションの追加方法

公式ドキュメントに情報がありました。
Basic patterns and examples — pytest documentation

とすれば良いようです。

 

conftest.pyの実装

公式ドキュメントによると、コマンドラインオプションを実装するための引数は標準モジュールの argparse と似ていました。

 
そこで今回は、

の2つを実装してみます。

まずは、pytest_addoption()関数に、追加するオプションの内容を記述します。 action='store'なものが前者、action='store_true'なものが後者となります。

def pytest_addoption(parser):
    """add commandline options"""
    parser.addoption('--fruit', action='store', default='ham',
                     help='fruit name: apple, grape, banana, etc')
    parser.addoption('--season', action='store_true',
                     help='fruit season now')

 
続いて、コマンドラインオプションから渡された値を返す、@pytest.fixtureデコレータの付いた関数を用意します。

request.config.getoption()メソッドの引数に、コマンドラインオプション名(parser.addoption()の第一引数と同じ名前)を指定します。

@pytest.fixture
def favorite_fruit(request):
    return request.config.getoption('--fruit')


@pytest.fixture
def is_season(request):
    return request.config.getoption('--season')

 

テストコードの実装

テストコードの引数に、conftext.pyで指定したfixtureデコレータのある関数の名前を指定します。

favorite_fruitis_seasonが該当します。

def test_fruit(favorite_fruit, is_season):
    print(f'\nfruit:{favorite_fruit}, is_season:{is_season}')
    assert True

 

テストの実行

ヘルプを見ると、オプションが追加されていました。

$ python -m pytest e.g._add_commandline_option/ --help
...
custom options:
  --fruit=FRUIT         fruit name: apple, grape, banana, etc
  --season              fruit season now
...

 
実行してみます。

なお、テストをパスした時はprint()の結果が出力されないため、 -sオプションを付けて実行します。

# 独自オプション無し:fruit・is_seasonともデフォルト値
$ python -m pytest e.g._add_commandline_option/ -s
...
fruit:ham, is_season:False

# fruitオプションあり:渡したbananaが表示
$ python -m pytest e.g._add_commandline_option/ -s --fruit banana
...
fruit:banana, is_season:False

# seasonオプションあり:is_seasonがTrueになった
$ python -m pytest e.g._add_commandline_option/ -s --fruit banana --season
...
fruit:banana, is_season:True

テストコードに、コマンドラインオプションの値が引き渡されていました。

 

応用:コマンドラインオプションで指定したテストコード以外はスキップする

pytestでは

  • pytest.mark.skipでテストをスキップ
  • pytest.mark.skipifで、条件に一致した場合にテストをスキップ

などの制御ができます。

ただ、上記の方法では、「コマンドラインオプションで指定したテストコード以外はスキップする」を実現できませんでした。コマンドラインオプションの値をマーカーに渡すことができなかったためです。

そこで公式ドキュメントを見たところ、

を組み合わせれば良さそうでした。
Working with custom markers — pytest documentation

独自コマンドラインオプションを追加したときと同様、conftest.pyとテストコードに実装します。

 

conftest.pyの実装

conftest.pyには

  • pytest_addoption()関数にて、独自コマンドラインオプションを追加 (今回は、targetオプション)
  • pytest_configure()関数にて、独自マーカーを追加 (今回は、pytest.mark.test_number)
  • @pytest.fixtureデコレータの付いた関数にて、独自コマンドラインオプションの値を取得
  • pytest_runtest_setup()関数にて、スキップするかどうかを判定

の4つを実装しました。

 

def pytest_addoption(parser):
    """add commandline options"""
    parser.addoption('--target', action='store',
                     help='if commandline args match test decorator, run test. if not, skip it')


def pytest_configure(config):
    """add custom marker"""
    config.addinivalue_line('markers', 'test_number(number): test case number')


@pytest.fixture
def my_target(request):
    return request.config.getoption('--target')


def pytest_runtest_setup(item):
    """decide skip or run testcase"""
    marker = item.get_marker('test_number')
    if marker is None:
        return

    opt = item.config.getoption('target')
    if opt is None:
        return

    targets = opt.split(',')
    test_number = str(marker.args[0])
    if test_number not in targets:
        pytest.skip('it is non-target testcase. skip it.')

 

テストコードの実装

今回はテストコードとして、

の3パターンを用意しました。

def test_no_marker():
    print('apple')
    assert True


@pytest.mark.test_number(1)
def test_with_marker():
    print('grape')
    assert True


@pytest.mark.parametrize('kind, code', [
    pytest.param('banana', '123', marks=pytest.mark.test_number(2)),
    pytest.param('pear', '456', marks=pytest.mark.test_number(3)),
])
def test_parameterize_with_marker(kind, code):
    print(f'\n{kind}: {code}')
    assert True

 

テストの実行

独自コマンドラインオプションのtargetを指定しない場合、すべてのテストが実行されました。

$ python -m pytest e.g._add_commandline_option/ -s
...
.apple
.grape
.
banana: 123
.
pear: 456
.

 
target=1とした場合は、独自マーカーの引数に1を渡しているテストケースが実行されました。

また、独自マーカーがないテストも実行されています。

$ python -m pytest e.g._add_commandline_option/ -s --target 1
...
.apple
.grape
.ss

 
@pytest.mark.parametrizeなテストコードもうまく制御されています。

# parametrizeなテストのうち、前者のもの
$ python -m pytest e.g._add_commandline_option/ -s --target 2
...
.apple
.s
banana: 123
.s


# parametrizeなテストのうち、後者のもの
$ python -m pytest e.g._add_commandline_option/ -s --target 3
...
.apple
.ss
pear: 456
.

 

conftest.pyについて

そもそも conftest.py は何だろうと思い調べたところ、以下の解説が詳しかったです。ディレクトリレベルのプラグインと考えれば良さそうです。
python - In py.test, what is the use of conftest.py files? - Stack Overflow

 

ソースコード

GitHubにあげました。e.g._add_commandline_option ディレクトリが今回のものです。
thinkAmi-sandbox/python_pytest-sample