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

## はじめに
pytestはPython用のテストツールです。

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

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

```python
# 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での実行例。

```shell
$ 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での実行例。

```shell
$ 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にはプラグイン機構等のいくつかの機能があります。

その中でも特に便利な`フィクスチャ`機能について、[公式ドキュメント](https://docs.pytest.org/en/latest/contents.html#)を参考に使い方をまとめました。

## フィクスチャ（fixture）とは
テストの前処理(DBをセットアップする、モックを作成するなど)を行うためのpytestの機能です。

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

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

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

```python
# 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`と追加するだけでデーターベースを利用出来るようになります。

```python
# 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です。

```python
@pytest.fixture
def fixture1():
    pass


@pytest.fixture
def fiture2():
    pass


def test_foo(fixture1, fiture2):
    pass
```

### unittestとの比較

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

```python
# 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'
```

```shell
$ python -m unittest test_unittest_setup.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
```

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

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

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

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

```python
# 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
```

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

```shell
$ 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するので、これらを使ってスコープ毎の実行粒度を検証します。

```python
@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つのテストコードを作成します。

```python
# 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
```

```python
# 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
```

```python
# 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
```

テストを実行します。

```shell
$ 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`は全てのレコードを参照することが出来ています。

```python
# 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'
```

```shell
$ pytest -q test_fixture_cache.py
...                                                                 [100%]
3 passed in 0.02 seconds
```

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

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

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

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

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

```python
# users_db.py


class Users:

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

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

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

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

```python
# 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)
```

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

```python
$ 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 ===========================
```

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

```python
# test_teardown_sample.py


def test_three(db):
    db.insert('Tom', 15)
    raise
    db.insert('Alice', 12)
```

```shell
$ 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回だけ実行されます。

```python
# test_teardown_sample.py


@pytest.fixture(scope='module')  # <-- スコープを変更
def db():
    ...
```

```shell
$ 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メソッド`を使って終了処理を登録する事が出来ます。

```python
# 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回呼ばれている事が分かります。

```shell
$ 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フィクスチャに意図的に例外を仕込んでみます。

```python
# 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メソッドはちゃんと呼ばれている事が分かります。

```shell
$ 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オブジェクトのリスト」を返すファクトリ・フィクスチャの例です。

```python
# 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)
```

```shell
$ 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プロパティ`を持っているので、そのプロパティの動作をテストしています。

```python
# 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オブジェクトを返すので、それに合わせて３つのテストケースが実行されます。

```shell
$ 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`フィクスチャを定義しています。

```python
# 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
```

```shell
$ 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に定義します。

```python
# conftest.py

import tempfil


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

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

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

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

```python
# 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')
```

```shell
$ pytest -q test_usefixtures_class.py
..                                                                  [100%]
2 passed in 0.02 seconds
```

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

```python
# 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です。

```python
pytest.mark.usefixtures('fixture1', 'fixture2', 'fixtureN')
```
### conftest.pyの階層化とオーバーライド
テストコードが階層構造を持っている場合、前述の`conftest.py`も各階層に配置することが出来ます。

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

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

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

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

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

```python
# hier_conftest/conftest.py

import pytest


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

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

```python
# hier_conftest/sub/conftest.py

import pytest


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

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

```python
# hier_conftest/test_root.py


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

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

```python
# hier_conftest/sub/test_sub.py


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

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

```shell
$ 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 =========================
```