Skip to content

好用的 pytest 之 fixture (1)

📅 6/16/2024

fixture 的英文是夾具,也許是為了和 python 的裝飾器有個呼應所以才叫這個名字吧? 這邊整理一些比較常見的 fixture 用法,可以根據大家的測試情境去做搭配

提高重複利用率

舉個例子,我們想測試下面這段 redis 的連線可不可以正常的讀寫資料,所以我分別寫了三個測試

python
from redis import Redis


def create_redis(db: int = 0):
    return Redis(host='127.0.0.1',
                 port=6379,
                 decode_responses=True,
                 retry_on_timeout=True,
                 db=db)
python
from db import create_redis


def test_redis_set():
    r = create_redis()
    r.set(name='user:Nick', value=1)


def test_redis_get():
    r = create_redis()
    assert int(r.get(name='user:Nick')) == 1


def test_redis_keys():
    r = create_redis()
    print(r.keys())

可以看到操作 redis 的 set, get, keys 都需要先建立 redis 連線,因此我們可以透過 fixture 減少程式碼重複呼叫連線

python
import pytest

from db import create_redis


@pytest.fixture
def redis_conn():
    return create_redis()


def test_redis_set(redis_conn):
    redis_conn.set(name='user:Nick', value=1)


def test_redis_get(redis_conn):
    assert int(redis_conn.get(name='user:Nick')) == 1


def test_redis_keys(redis_conn):
    print(redis_conn.keys())

fixture + fixture

fixture 可以無限套娃

例如這邊的例子是我想測試 redis 的 pipeline,我依然可以利用剛剛建好的 fixture redis_conn 來幫忙建立新的 fixture

python
@pytest.fixture
def pipe(redis_conn):
    pipe = redis_conn.pipeline()
    return pipe
    
def test_pipeline_set(pipe):
    pipe.set(name='user:Nick', value=1)
    pipe.set(name='user:foo', value=2)
    pipe.set(name='user:bar', value=3)
    pipe.set(name='user:baz', value=4)
    pipe.execute()
    pipe.close()

fixture yield

上面 pipeline 的例子能看到我最後調用 close 去關閉 pipeline 連線,不過我們能透過 pipeline 的上下文管理更好的關閉連線

這邊就提供一個在 fixture 中 yield pipeline 出來的例子

python
@pytest.fixture
def pipe(redis_conn):
    with redis_conn.pipeline() as pipe:
        yield pipe
        pipe.execute()
        
def test_pipeline_set(pipe):
    pipe.set(name='user:Nick', value=1)
    pipe.set(name='user:foo', value=2)
    pipe.set(name='user:bar', value=3)
    pipe.set(name='user:baz', value=4)

fixture + parametrize

上一篇文章中,我們透過 parametrize 來設置多個測資

而 fixture 也可以做到同樣的事,例如我們測試中輪流切換 redis 的三個庫

python
@pytest.fixture(params=[0, 1, 2])
def redis_conn(request):
    return create_redis(db=request.param)

def test_redis_set(redis_conn):
    redis_conn.set(name='user:Nick', value=1)

pytest.mark.parametrize 的作法則是

python
@pytest.fixture
def redis_conn(request):
    return create_redis(request.param)

@pytest.mark.parametrize('redis_conn', [1, 2, 3], indirect=True)
def test_redis_set_with_difference_db(redis_conn):
    redis_conn.set(name='user:Nick', value=1)

小結

今天只講到 fixture 用法的一小部分,不過都是我認為最實用的部分

當然後續還會再補上其他 fixture 的介紹,預期下一篇應該會整理整理 scope 的部分