Help us understand the problem. What is going on with this article?

pytest:フィクスチャ(fixture)の使い方

はじめに

pytestはPython用のテストツールです。

標準のunittestに比べ、テストエラー時の結果が分かりやすいのが特徴です。

例として、辞書オブジェクトを比較する以下のテストコードをunittestとpytestそれぞれで実行してみます。

# test_dict.py

import unittest

dict1 = {'name': 'Tom', 'age': 20}
dict2 = {'name': 'John', 'age': 23}


class TestUnitTest(unittest.TestCase):

    def test_one(self):
        assert dict1 == dict2

unittestでの実行例。

$ python -m unittest
F
======================================================================
FAIL: test_one (test_dict.TestUnitTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/gaizaku/Develop/pytest_learning/test_dict.py", line 12, in test_one
    assert dict1 == dict2
AssertionError

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

一方、pytestでの実行例。

$ pytest -vv test_dict.py
================================ test session starts ================================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 -- /Users/gaizaku/.pyenv/versions/3.7.1/envs/pytest_learning/bin/python
cachedir: .pytest_cache
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 1 item

test_dict.py::TestUnitTest::test_one FAILED                                   [100%]

===================================== FAILURES ======================================
_______________________________ TestUnitTest.test_one _______________________________

self = <test_dict.TestUnitTest testMethod=test_one>

    def test_one(self):
>       assert dict1 == dict2
E       AssertionError: assert {'age': 20, 'name': 'Tom'} == {'age': 23, 'name': 'John'}
E         Differing items:
E         {'name': 'Tom'} != {'name': 'John'}
E         {'age': 20} != {'age': 23}
E         Full diff:
E         - {'age': 20, 'name': 'Tom'}
E         ?          ^           ^ ^
E         + {'age': 23, 'name': 'John'}
E         ?          ^           ^ ^^

test_dict.py:12: AssertionError
============================= 1 failed in 0.08 seconds ==============================

見ての通り、pytestはキー・バリューの差分まで表示してくれるので、何故テストが失敗したのか非常に分かりやすいです。

また、pytestにはプラグイン機構等のいくつかの機能があります。

その中でも特に便利なフィクスチャ機能について、公式ドキュメントを参考に使い方をまとめました。

フィクスチャ(fixture)とは

テストの前処理(DBをセットアップする、モックを作成するなど)を行うためのpytestの機能です。

個人的には、フィクスチャがあるからpytestを使っている言っても過言ではありません。

前処理はテストコードの保守性を低くする大きな原因ですが、フィクスチャ機能を上手に使うことでテストコードをクリーンに保つことが可能になります。

フィクスチャの概要

仮想的なデータベースオブジェクトUsersクラスを使ってフィクスチャの概要を紹介します。

# users_db.py


class Users:
    def __init__(self):
        self.last_insert_id = 0
        self.rows = {}

    def insert(self, name, age):
        self.last_insert_id += 1
        self.rows[self.last_insert_id] = {
            'id': self.last_insert_id,
            'name': name,
            'age': age,
        }

    def get(self, id_):
        return self.rows[id_]

上記Usersクラスをテストするコードを記述します。

以下のテストコードではdbフィクスチャ(フィクスチャ関数とも言う)を定義しています。

dbフィクスチャはダミーレコードを追加したUsersデーターベースを作成するという前処理を行っています。

テストケースの中でそのデーターベースを使いたい場合は、テストケースの引数にdbと追加するだけでデーターベースを利用出来るようになります。

# test_fixture_sample.py

import pytest

from users_db import Users


# これがフィクスチャ
@pytest.fixture
def db():
    users = Users()
    users.insert('Bob', 10)
    users.insert('Alice', 12)
    return users


# dbフィクスチャを利用するテストケース
def test_one(db):
    assert db.get(1)['name'] == 'Bob'


# フィクスチャは複数のテストケースで共有できる
def test_two(db):
    assert db.get(2)['name'] == 'Alice'


# クラスベースのテストでも利用できる
class TestUers:
    def test_one(self, db):
        assert db.get(1)['name'] == 'Bob'

複数のフィクスチャを同時に使いたい場合は、単に引数に列挙すればOKです。

@pytest.fixture
def fixture1():
    pass


@pytest.fixture
def fiture2():
    pass


def test_foo(fixture1, fiture2):
    pass

unittestとの比較

比較として、unittestで同様のテストコードを記述してみます。

# test_unittest_setup.py

import unittest

from users_db import Users


class TestUnittest(unittest.TestCase):

    def setUp(self):
        self.db = Users()
        self.db.insert('Bob', 10)
        self.db.insert('Alice', 12)

    def test_one(self):
        assert self.db.get(1)['name'] == 'Bob'

    def test_two(self):
        assert self.db.get(2)['name'] == 'Alice'
$ python -m unittest test_unittest_setup.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

conftest.pyによるフィクスチャの共有

先ほどのテストコードは規模も小さいので、unittestと比較した場合のフィクスチャのメリットが良く分からないかもしれません。

フィクスチャの真価はテスト間で共有出来る事にあります。

conftest.pyというファイルにフィクスチャを記述すると、そのフィクスチャは複数のテストコードから呼び出すことが出来るようになります。

例として、先ほどのdbフィクスチャをconftest.pyに移動します。

# conftest.py

import pytest

from users_db import Users


@pytest.fixture
def db():
    users = Users()
    users.insert('Bob', 10)
    users.insert('Alice', 12)
    return users

フィクスチャを移動した後でもテストは問題なくパスします。

$ pytest -q test_fixture_sample.py
...                                                                      [100%]
3 passed in 0.02 seconds

これで今後テストが増えたとしても、引数にdbと記述するだけでフィクスチャを利用出来ます。

unittestの場合は毎回setUpメソッドを記述しないといけないので、フィクスチャがテストコードをシンプルに保つのに便利な機能で有ることが分かります。

スコープ

フィクスチャにはスコープ(scope)という概念があります。

スコープを理解すると、フィクスチャ関数が実行される粒度を制御出来るようになります。

まず、以下にスコープの種類と実行粒度をまとめます。

スコープ名 実行粒度
function テストケースごとに1回実行される(デフォルト)
class テストクラス全体で1回実行される
module テストファイル全体で1回実行される
session テスト全体で1回だけ実行される

スコープの動作を理解するためにconftest.pyに4つのフィクスチャを追加します。

各々のフィクスチャは実行されると自身のスコープ名をprintするので、これらを使ってスコープ毎の実行粒度を検証します。

@pytest.fixture(scope='function')
def fixture_function():
    print('function')


@pytest.fixture(scope='class')
def fixture_class():
    print('class')


@pytest.fixture(scope='module')
def fixture_module():
    print('module')


@pytest.fixture(scope='session')
def fixture_session():
    print('session')

次に、3つのテストコードを作成します。

# test_scope_sample1.py


class TestScopeSample1:

    def test_one(self,
                 fixture_function,
                 fixture_class,
                 fixture_module,
                 fixture_session):
        pass

    def test_two(self,
                 fixture_function,
                 fixture_class,
                 fixture_module,
                 fixture_session):
        pass
# test_scope_sample2.py


class TestScopeSample2:

    def test_one(self,
                 fixture_function,
                 fixture_class,
                 fixture_module,
                 fixture_session):
        pass

    def test_two(self,
                 fixture_function,
                 fixture_class,
                 fixture_module,
                 fixture_session):
        pass
# test_scope_sample3.py

# 注:このテストコードはfixture_classは使わない


def test_one(fixture_function,
             fixture_module,
             fixture_session):
    pass


def test_two(fixture_function,
             fixture_module,
             fixture_session):
    pass

テストを実行します。

$ pytest -vs test_scope_sample*
============================= test session starts ==============================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 -- /Users/gaizaku/.pyenv/versions/3.7.1/envs/pytest_learning/bin/python
cachedir: .pytest_cache
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 6 items

test_scope_sample1.py::TestScopeSample1::test_one session
module
class
function
PASSED
test_scope_sample1.py::TestScopeSample1::test_two function
PASSED
test_scope_sample2.py::TestScopeSample2::test_one module
class
function
PASSED
test_scope_sample2.py::TestScopeSample2::test_two function
PASSED
test_scope_sample3.py::test_one module
function
PASSED
test_scope_sample3.py::test_two function
PASSED

=========================== 6 passed in 0.06 seconds ===========================

出力結果から、各フィクスチャの実行回数をまとめます。

functionスコープのフィクスチャが最も頻繁に実行され、逆にsessionスコープのフィクスチャはテスト全体を通して1回しか実行されていません。

つまり、下に行くほどコストが高い前処理に適している事が分かります。

スコープ名 実行回数
function 6回(テストケース数と等しい)
class 2回(クラス数と等しい)
module 3回(テストファイル数と等しい)
session 1回(テスト開始直後に1回だけ実行)

なお、function以外のスコープを使う場合、フィクスチャがpytestにキャッシュされる点に注意します。

下記のmoduled_dbフィクスチャはmoduleスコープなので、フィクスチャにより作成されたUsersデーターベースの中身はテスト全体で共有されており、test_threeは全てのレコードを参照することが出来ています。

# test_fixture_cache.py

import pytest

from users_db import Users


@pytest.fixture(scope='module')
def moduled_db():
    users = Users()
    return users


def test_one(moduled_db):
    moduled_db.insert('Bob', 10)


def test_two(moduled_db):
    moduled_db.insert('Alice', 12)


def test_three(moduled_db):
    assert moduled_db.get(1)['name'] == 'Bob'
    assert moduled_db.get(2)['name'] == 'Alice'
$ pytest -q test_fixture_cache.py
...                                                                 [100%]
3 passed in 0.02 seconds

 終了処理(teardown)

テストが終了した後に何らかの終了処理(DB接続のクローズや一時ファイルの削除等)をしたいシチュエーションは多いと思います。

フィクスチャを使う場合、2種類の方法で終了処理を記述出来ます。

yieldを使うパターン

フィクスチャからテストケースに対してデータを渡す際、通常はreturnを使用しますが、代わりにyieldを使用するとテストの終了後に任意の処理を実行出来ます。

例として、テスト終了時に全レコードを表示するという終了処理を記述してみます。

まず、データベースオブジェクトに全レコードを返させるallメソッドを追加します。

# users_db.py


class Users:

    def all(self):
        return self.rows.values()

次にフィクスチャとテストコードを作成します。

dbフィクスチャはyieldした段階でブロックされ、処理がテストケースへと移動します。

テストが終了すると処理はフィクスチャに戻り、yield以降の処理が実行されます。

# test_teardown_sample.py

import pytest

from users_db import Users


@pytest.fixture
def db():
    users = Users()
    yield users

    print()
    for v in users.all():
        print(v)


def test_one(db):
    db.insert('Bob', 10)


def test_two(db):
    db.insert('Tom', 15)
    db.insert('Alice', 12)

テストを実行すると期待通りに全レコードが出力されます。

$ pytest -vs test_teardown_sample.py
============================= test session starts ==============================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 -- /Users/gaizaku/.pyenv/versions/3.7.1/envs/pytest_learning/bin/python
cachedir: .pytest_cache
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 2 items

test_teardown_sample.py::test_one PASSED
{'id': 1, 'name': 'Bob', 'age': 10}

test_teardown_sample.py::test_two PASSED
{'id': 1, 'name': 'Tom', 'age': 15}
{'id': 2, 'name': 'Alice', 'age': 12}


=========================== 2 passed in 0.01 seconds ===========================

終了処理はテストの成否や例外の有無を問わず実行されるので、フィクスチャ内で例外キャッチ等のエラーハンドリングは必要ありません。(ただし、フィクスチャ関数内で発生するエラーは別です)

# test_teardown_sample.py


def test_three(db):
    db.insert('Tom', 15)
    raise
    db.insert('Alice', 12)
$ pytest -s test_teardown_sample.py::test_three
============================= test session starts ==============================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 1 item

test_teardown_sample.py F
{'id': 1, 'name': 'Tom', 'age': 15}


=================================== FAILURES ===================================
__________________________________ test_three __________________________________

db = <users_db.Users object at 0x104d8ecc0>

    def test_three(db):
        db.insert('Tom', 15)
>       raise
E       RuntimeError: No active exception to reraise

test_teardown_sample.py:29: RuntimeError
=========================== 1 failed in 0.10 seconds ===========================

また、functionスコープ以外のフィクスチャでは、終了処理はそのフィクスチャを利用する全テストが終わった後に1回だけ実行されます。

# test_teardown_sample.py


@pytest.fixture(scope='module')  # <-- スコープを変更
def db():
    ...
$ pytest -vs test_teardown_sample.py
============================= test session starts ==============================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 -- /Users/gaizaku/.pyenv/versions/3.7.1/envs/pytest_learning/bin/python
cachedir: .pytest_cache
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 3 items

test_teardown_sample.py::test_one PASSED
test_teardown_sample.py::test_two PASSED
test_teardown_sample.py::test_three FAILED
{'id': 1, 'name': 'Bob', 'age': 10}
{'id': 2, 'name': 'Tom', 'age': 15}
{'id': 3, 'name': 'Alice', 'age': 12}
{'id': 4, 'name': 'Tom', 'age': 15}


=================================== FAILURES ===================================
__________________________________ test_three __________________________________

db = <users_db.Users object at 0x106433cf8>

    def test_three(db):
        db.insert('Tom', 15)
>       raise
E       RuntimeError: No active exception to reraise

test_teardown_sample.py:29: RuntimeError
====================== 1 failed, 2 passed in 0.11 seconds ======================

addfinalizerを使うパターン

yield以外にはaddfinalizerを用いるパターンがあります。

yieldと比較すると、addfinalizerには3つの大きな違いがあります。

  • 終了処理をコールバック関数として登録する
  • コールバック関数は複数登録できる
  • フィクスチャ内で例外が起きたとしても必ず実行される

以下にシンプルなメールクライアントを用いたaddfinalizerの例を示します。

clientsフィクスチャが受け取っているrequestsはpytestが渡してくれる特殊なフィクスチャで、addfinalizerメソッドを使って終了処理を登録する事が出来ます。

# test_addfinalizer_sample.py

import pytest


class MailClient:
    def close(self):
        print(f'Close smtp connection:', self)


@pytest.fixture
def clients(request):
    ret = []
    for i in range(3):
        client = MailClient()
        request.addfinalizer(client.close)
        ret.append(client)
    return ret


def test_one(clients):
    pass

テストを実行すると、メールクライアントのcloseメソッドが期待通りに3回呼ばれている事が分かります。

$ pytest -sv test_addfinalizer_sample.py
============================= test session starts ==============================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 -- /Users/gaizaku/.pyenv/versions/3.7.1/envs/pytest_learning/bin/python
cachedir: .pytest_cache
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 1 item

test_addfinalizer_sample.py::test_one PASSEDClose smtp connection: <test_addfinalizer_sample.MailClient object at 0x10ef6a940>
Close smtp connection: <test_addfinalizer_sample.MailClient object at 0x10ef6a908>
Close smtp connection: <test_addfinalizer_sample.MailClient object at 0x10ef6a8d0>


=========================== 1 passed in 0.01 seconds ===========================

ここで、clientsフィクスチャに意図的に例外を仕込んでみます。

# test_addfinalizer_sample.py

@pytest.fixture
def clients(request):
    ret = []
    for i in range(3):
        client = MailClient()
        request.addfinalizer(client.close)
        raise  # <-- 例外を追加
        ret.append(client)
    yield ret

テストを実行すると、メールクライアントの生成は1つ目で終わってしまっていますが、生成されたクライアントに対するcloseメソッドはちゃんと呼ばれている事が分かります。

$ pytest -sv test_addfinalizer_sample.py
============================= test session starts ==============================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 -- /Users/gaizaku/.pyenv/versions/3.7.1/envs/pytest_learning/bin/python
cachedir: .pytest_cache
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 1 item

test_addfinalizer_sample.py::test_one ERRORClose smtp connection: <test_addfinalizer_sample.MailClient object at 0x10561b828>


==================================== ERRORS ====================================
__________________________ ERROR at setup of test_one __________________________

request = <SubRequest 'clients' for <Function test_one>>

    @pytest.fixture
    def clients(request):
        ret = []
        for i in range(3):
            client = MailClient()
            request.addfinalizer(client.close)  # 終了処理を登録
>           raise
E           RuntimeError: No active exception to reraise

test_addfinalizer_sample.py:17: RuntimeError
=========================== 1 error in 0.08 seconds ============================

ファクトリ・フィクスチャ

これまで作成してきたフィクスチャは全て何らかの固定的なデータを返す物でした。

しかし、動的なデータが欲しいシチュエーションも多いと思います。

そういう時は、データを生成するための関数(ファクトリ)を返すフィクスチャを定義すると便利です。

そういったフィクスチャの事を公式ドキュメントではファクトリ・フィクスチャと呼んでいます。

以下は「任意の名前と長さを持つPersonオブジェクトのリスト」を返すファクトリ・フィクスチャの例です。

# test_factory_fixture.py

import pytest


class Person:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return self.name


@pytest.fixture
def persons_factory():
    def factory(names):
        return [Person(v) for v in names]
    return factory


def test_one(persons_factory):
    persons1 = persons_factory(['Tom', 'Bob'])
    persons2 = persons_factory(['Alice', 'Nancy'])
    print(persons1, persons2)
$ pytest -qs test_factory_fixture.py
[Tom, Bob] [Alice, Nancy]
.
1 passed in 0.01 seconds

パラメタライズド・フィクスチャ

複数の入力値による網羅的なテストを行いたい場合はパラメタライズド・フィクスチャが便利です。

pytest.fixtureデコレータのparams引数にlist等のiterableを指定すると、含まれている各要素をパラメータとしたフィクスチャが自動で生成されます。

以下はStudentクラスをテストするコードです。

Studentクラスは生徒が小学生なのか中学生なのかを教えてくれるschool_typeプロパティを持っているので、そのプロパティの動作をテストしています。

# test_params_sample.py

import pytest


class Student:
    ELEMENTARY_SCHOOL = 1
    JUNIOR_HIGH_SCHOOL = 2

    def __init__(self, name, age):
        self.name = name
        self.age = age

    @property
    def school_type(self):
        if self.age >= 13:
            return self.JUNIOR_HIGH_SCHOOL
        else:
            return self.ELEMENTARY_SCHOOL


@pytest.fixture(params=[('Tom', 10), ('Bob', 15), ('Alice', 12)])
def student(request):
    return Student(request.param[0], request.param[1])


def test_one(student):
    if student.age <= 12:
        assert student.school_type == 1
    else:
        assert student.school_type == 2

studentフィクスチャは3つのStudentオブジェクトを返すので、それに合わせて3つのテストケースが実行されます。

$ pytest -sv test_params_sample.py
============================= test session starts ==============================
platform darwin -- Python 3.7.1, pytest-4.1.1, py-1.7.0, pluggy-0.8.1 -- /Users/gaizaku/.pyenv/versions/3.7.1/envs/pytest_learning/bin/python
cachedir: .pytest_cache
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 3 items

test_params_sample.py::test_one[student0] PASSED
test_params_sample.py::test_one[student1] PASSED
test_params_sample.py::test_one[student2] PASSED

=========================== 3 passed in 0.02 seconds ===========================

フィクスチャからフィクスチャを使う

フィクスチャには他のフィクスチャを利用できるという特性があります。

小さいメソッドが集まる事で複雑なクラスが出来上がるように、フィクスチャ同士を組み合わせる事で複雑な前処理を実現出来ます。

以下の例では、モック化されたメールサーバ接続を返すmock_connectionフィクスチャと、それを利用してモック化されたメールクライアントを返すclientフィクスチャを定義しています。

# test_fixture_in_fixture.py

import pytest


class MailClient:

    def __init__(self, connection):
        self.connection = connection

    def send(self, message):
        return self.connection.send(message)


@pytest.fixture
def mock_connection():
    class MockConnection:

        def __init__(self):
            self.sents = []

        def send(self, message):
            self.sents.append(message)
            return True

    return MockConnection()


@pytest.fixture
def client(mock_connection):
    return MailClient(mock_connection)


def test_send(client):
    msg = 'Hello, world!'
    client.send(msg)
    assert client.connection.sents[0] == msg
$ pytest test_fixture_in_fixture.py
=================================== test session starts ===================================
platform darwin -- Python 3.7.2, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 1 item

test_fixture_in_fixture.py .                                                        [100%]

================================ 1 passed in 0.02 seconds =================================

テストクラス・モジュール全体にフィクスチャを自動摘要する

pytest.mark.usefixturesを使うと、テストクラスやテストモジュール(test_*.py)全体にフィクスチャを自動で摘要出来るようになります。

例としてまず、テストケース毎に初期化された一時ディレクトリを作成してくれるフィクスチャをconftest.pyに定義します。

# conftest.py

import tempfil


@pytest.fixture
def tmpdir():
    with tempfile.TemporaryDirectory() as dirname:
        os.chdir(dirname)
        yield

上記フィクスチャを使う場合、もちろんテストケースの引数として記述しても良いのですが、このフィクスチャは何のデータも返さないので引数として受け取り意味をあまり感じません。

また、テストケースが増える度にいちいちフィクスチャ名を記述するのも冗長です。

そういった時は以下の様にテストクラスをpytest.mark.usefixturesでデコレートしてあげると、各テストケースの実行前に自動でフィクスチャが実行されるようになります。

# test_usefixtures_class.py

import pytest


@pytest.mark.usefixtures('tmpdir')
class TestUseFixtures:
    def test_one(self):
        with open('hoge.csv', 'w') as f:
            f.write('Hello, world!')

    def test_two(self):
        with pytest.raises(FileNotFoundError):
            open('hoge.csv', 'r')
$ pytest -q test_usefixtures_class.py
..                                                                  [100%]
2 passed in 0.02 seconds

また、テストモジュール(.pyファイル)で利用する場合は以下の様にします。(pytestmarkという変数に代入しないと動作しないので注意)

# test_usefixtures_module.py

import pytest

pytestmark = pytest.mark.usefixtures('tmpdir')


def test_one():
    with open('hoge.csv', 'w') as f:
        f.write('Hello, world!')

複数フィクスチャを利用する場合は列挙すればOKです。

pytest.mark.usefixtures('fixture1', 'fixture2', 'fixtureN')

conftest.pyの階層化とオーバーライド

テストコードが階層構造を持っている場合、前述のconftest.pyも各階層に配置することが出来ます。

その場合、テストケースは自身と同階層にあるconftest.py自身より上位層にあるconftest.pyに定義してあるフィクスチャを利用出来ます。

また、子階層のフィクスチャは上位層のフィクスチャをオーバーライドする事も出来ます。

以下に階層構造のテストコードの例を示します。

$ tree hier_conftest/
hier_conftest/
├── conftest.py
├── sub
│   ├── conftest.py
│   └── test_sub.py
└── test_root.py

これは上位層のconftest.pyです。

# hier_conftest/conftest.py

import pytest


@pytest.fixture
def username():
    return 'username'

これは子階層のconftest.pyで、親のusernameフィクスチャを利用しつつ、その結果をオーバーライドしています。

# hier_conftest/sub/conftest.py

import pytest


@pytest.fixture
def username(username):
    return 'sub-' + username

親階層のテストコードです。

# hier_conftest/test_root.py


def test_username(username):
    assert username == 'username'

子階層のテストコードです。

# hier_conftest/sub/test_sub.py


def test_username(username):
    assert username == 'sub-username'

テストを実行すると期待通りにオーバーライドされている事が分かります。

$ pytest hier_conftest/
=========================== test session starts ===========================
platform darwin -- Python 3.7.2, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: /Users/gaizaku/Develop/pytest_learning, inifile:
collected 2 items

hier_conftest/test_root.py .                                        [ 50%]
hier_conftest/sub/test_sub.py .                                     [100%]

======================== 2 passed in 0.03 seconds =========================
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした