はじめに
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 =========================