如何在pytest中使用来自两个不同fixture的不同数据参数化测试

0 投票
1 回答
42 浏览
提问于 2025-04-14 17:21

我有几个pytest的测试准备工具:

@pytest.fixture
def articles_new(category):
    article_new_1 = ArticleFactory.create(category=category["category_1"], status=ArticleStatusChoices.NEW)
    article_new_2 = ArticleFactory.create(category=category["category_2"], status=ArticleStatusChoices.NEW)
    return {"article_new_1": article_new_1, "article_new_2": article_new_2}

@pytest.fixture
def articles_published_with_readers(category):
    article_published_with_readers_1 = ArticleFactory.create(
        category=category["category_2"],
        readers=ArticleRoleTypesChoices.EMPLOYEE,
        status=ArticleStatusChoices.PUBLISHED,
    )
    return {"article_published_with_readers_1": article_published_with_readers_1}

@pytest.fixture
def category(product):
    category_1 = CategoryFactory.create(product=product["product_1"])
    category_2 = CategoryFactory.create(product=product["product_2"])
    return {"category_1": category_1, "category_2": category_2}

@pytest.fixture
def product():
    product_1 = ProductFactory.create()
    product_2 = ProductFactory.create()
    return {"product_1": product_1, "product_2": product_2}

我想把这个测试:

def test_articles(self, client, user):
    client.force_login(user)
    
    res = client.get(reverse("get_article", kwargs={"article_guid": article_new_1.guid}))
    assert res.status_code == 200

    res = client.get(reverse("get_article", kwargs={"article_guid": article_published_with_readers_1.guid}))
    assert res.status_code == 403

改成像这样的:

@pytest.mark.parametrize(
        "article, expected",
        [
            ......
        ],
    )
def test_articles(self, client, user, article, expected):
    client.force_login(user)
    
    res = client.get(reverse("get_article", kwargs={"article_guid": article.guid}))
    assert res.status_code == expected

这个问题怎么解决呢?
注意: article_new_1article_published_with_readers_1属于不同的类别。

1 个回答

1

这里有一种解决方法:

import logging

import pytest


@pytest.fixture
def client(): ...


@pytest.fixture
def user(): ...


@pytest.fixture
def category(product):
    category_1 = "category_1"
    category_2 = "category_2"
    return {"category_1": category_1, "category_2": category_2}


@pytest.fixture
def product():
    product_1 = "product_1"
    product_2 = "product_2"
    return {"product_1": product_1, "product_2": product_2}


def create_new_article(category):
    return f"article with category {category['category_1']}"


def publish_article(category):
    return f"published article with category {category['category_1']}"


@pytest.mark.parametrize(
    "article_type, expected",
    [
        pytest.param("new", 200, id="success case"),
        pytest.param("published", 403, id="failed case"),
    ],
)
def test_article(client, user, category, article_type, expected):
    logging.debug("article_type=%r", article_type)
    if article_type == "new":
        article = create_new_article(category)
    elif article_type == "published":
        article = publish_article(category)
    else:
        raise ValueError("Invalid article type")

    # Do something with article and expected
    logging.debug("article=%r", article)
    logging.debug("expected=%r", expected)
    # assert res.status_code == expected

输出结果:

test_it.py::test_article[success case]
-------------------------------------------- live log call --------------------------------------------
DEBUG    root:test_it.py:44 article_type='new'
DEBUG    root:test_it.py:53 article='article with category category_1'
DEBUG    root:test_it.py:54 expected=200
PASSED
test_it.py::test_article[failed case]
-------------------------------------------- live log call --------------------------------------------
DEBUG    root:test_it.py:44 article_type='published'
DEBUG    root:test_it.py:53 article='published article with category category_1'
DEBUG    root:test_it.py:54 expected=403
PASSED

========================================== 2 passed in 0.01s ==========================================

注意事项:

  • 我没有直接把实际的文章传入测试,而是传入了 article_type:可以是“new”(新文章)或者“published”(已发布)。在测试中,我会用这个文章类型来决定要创建哪种类型的文章。

  • create_new_article()publish_article() 不是固定的测试数据,它们只是普通的函数。

  • 我模拟了那些我无法访问的固定数据/函数/类。

  • 我用的是

      ("new", 200)
    

    而不是

      pytest.param("new", 200, id="success case")
    

    这样做的好处是可以给每个参数化的案例起个名字,比如“成功案例”或者“失败案例”。

  • 这个解决方案有点复杂,因为用了一个大的 if 语句来创建文章,而不是直接把文章传入测试,但它简单易懂。

撰写回答