本篇接著上一篇繼續介紹 fixture
scope 的好處
之前我們都是透過 redis 的讀寫當成範例,而每一項單元測試中都會用到 redis_conn
來獲取連線; 然而我們並沒有必要一直重複和 redis 建立連線,甚至同頭用到尾都沒有關係,因此可以透過設定作用域 scope
來減少 fixture 建立和銷毀的次數
@pytest.fixture(scope='session')
def redis_conn():
print('create') # print 看看有無設置 scope 的差別
return create_redis()
@pytest.mark.parametrize(
('username', 'value'),
[('Nick', 1), ('foo', 2), ('bar', 3), ('baz', 4)]
)
def test_redis_get(redis_conn, username, value):
assert int(redis_conn.get(name=f'user:{username}')) == value
scope='session'
scope='function'
以下整理作用域和他的影響範圍
作用域 | session | package | module | class | function |
---|---|---|---|---|---|
影響範圍 | 整個測試 | 一個資料夾 | 一個py檔案 | 一個類 | 一個函式 |
conftest.py
在進入程式碼範例之前,我們先把一些常用的夾具放到 conftest.py
,像是我把上面範例用到的 redis_conn
放到 conftest.py
,放到這裡面的夾具會在執行pytest
進入測試時自動載入, 後續測試時就不需要在特別 import 也可以使用。
至於 conftest.py 要放在哪裏就要看你希望它影響那些範圍的測試了。
package
作用域設定為 package
會在測試中的每個資料夾的第一個測試建立並在最後一個測試銷毀,以下為檔案結構和範例
結構:
test │ conftest.py │ pytest.ini │ __init__.py ├─db │ │ test_redis.py │ └─ __init__.py │ └─db2 │ test_redis.py └─ __init__.py
conftest.py
python@pytest.fixture(scope='package') def redis_conn(request): package_name = os.path.basename(os.path.dirname(request.fspath)) print(f'Start In {package_name=}') yield create_redis() print(f'End In {package_name=}')
接著執行測試指令 pytest test/db test/db2 -sv
到這裡出現了我意料之外的事情,本來我預期會在 db
和 db2
這兩個 package 會各建立一次連線;但實際上它卻只建立一次,這不就和設定作用域為 session 一樣了嗎?
我猜想可能是那個 conftest.py
的位置是在最開始的那層,所以 pytest 把 db
和 db2
都當成是 test package
的一部分了;既然這樣我們就把 conftest 複製到 db
和 db2
內吧。
- 結構:
test │ conftest.py │ pytest.ini │ __init__.py ├─db │ │ conftest.py │ │ test_redis.py │ └─ __init__.py │ └─db2 │ conftest.py │ test_redis.py └─ __init__.py
module
作用域設定為 module
會在測試中的每個py檔案的第一個測試建立並在最後一個測試銷毀,以下為檔案結構和範例
結構:
test │ conftest.py │ pytest.ini │ __init__.py └─db │ test_redis.py │ test_redis_2.py └─__init__.py
conftest.py
python@pytest.fixture(scope='package') def redis_conn(request): module_name = os.path.basename(request.fspath) print(f'Start In {module_name=}') yield create_redis() print(f'End In {module_name=}')
class
作用域設定為 module
會在測試類的第一個測試方法建立並在最後一個測試方法銷毀,以下為範例
conftest.py
python@pytest.fixture(scope='class') def redis_conn(request): class_name = request.cls.__name__ print(f'Start In {class_name=}') yield create_redis() print(f'End In {class_name=}')
test_class.py
pythonclass TestRedisSet: @pytest.mark.parametrize( ('username', 'value'), [('Nick', 1), ('foo', 2), ('bar', 3), ('baz', 4)] ) def test_redis_set(self, redis_conn, username, value): redis_conn.set(name=f'user:{username}', value=value) class TestRedisGet: @pytest.mark.parametrize( ('username', 'value'), [('Nick', 1), ('foo', 2), ('bar', 3), ('baz', 4)] ) def test_redis_get(self, redis_conn, username, value): assert int(redis_conn.get(name=f'user:{username}')) == value
function
作用域設定為 function
會在第一個測試項建立並在最後一個測試項銷毀,只要不特別設定 scope 就是用 function 作為預設值,以下為範例
conftest.py
python@pytest.fixture def redis_conn(request): _id = request.node.callspec.id print(f'Start In {_id=}') yield create_redis() print(f'End In {_id=}')
test_redis.py
python@pytest.mark.parametrize( ('username', 'value'), [('Nick', 1), ('foo', 2), ('bar', 3), ('baz', 4)], ids=['first test', 'second test', 'third test', 'fourth test'] ) def test_redis_get(redis_conn, username, value): assert int(redis_conn.get(name=f'user:{username}')) == value