You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pytest参数化测试报错,如何实现多游戏测试用例分组执行?

解决pytest参数化中Fixture作为参数传入的报错问题

问题背景

作为单元测试新手,我需要让test_select_childnode测试用例适配不同游戏场景:每个游戏需要创建对应的node和mcts实例,搭配整数参数执行测试。尝试用@pytest.mark.parametrize实现复用,但当前写法运行报错。之前直接将Fixture传入测试函数可以正常执行,但每个游戏都要重复编写测试函数和Fixture,存在大量冗余代码。

错误原因

当前代码中,直接将Fixture函数(如mcts_ttt、node_ttt)放入@pytest.mark.parametrize的参数列表中,pytest不会自动执行这些Fixture,而是将函数对象本身传递给测试函数。因此当代码访问mcts.nodes时,mcts是一个函数对象而非MCTS实例,自然会抛出AttributeError: 'function' object has no attribute 'nodes'。

解决方案

方法1:使用pytest-lazy-fixture延迟执行Fixture

这是最直接的修改方式,通过lazy_fixture引用Fixture,让pytest在参数化时正确执行Fixture并返回实例。

步骤:

  1. 安装依赖:
pip install pytest-lazy-fixture
  1. 修改参数化代码:
    在@pytest.mark.parametrize中用lazy_fixture("fixture_name")替代直接传入Fixture函数。

修改后的代码:

import pytest
from pytest_lazyfixture import lazy_fixture

@pytest.fixture
def node_ttt():
    g = TicTacToeGame()
    node = Node(g)
    return node

@pytest.fixture
def mcts_ttt():
    g = TicTacToeGame()
    nnet = TicTacToeNNet(g, nn_args)
    model = NNetWrapper(g, nnet, nn_args)
    mcts_instance = MCTS(model, mcts_args)
    return mcts_instance

@pytest.mark.parametrize("mcts, node, action_idx", [
    (lazy_fixture("mcts_ttt"), lazy_fixture("node_ttt"), 0),
    # 后续添加其他游戏的Fixture,比如lazy_fixture("mcts_hive"), lazy_fixture("node_hive"), 1
])
def test_select_childnode(mcts, node, action_idx):
    """
    Assert that childnode is creating a new node when needed.
    Assert that childnode is not creating a new node when not needed.
    """
    mcts.nodes[node.id] = node
    child_node = mcts.select_childnode(node, action_idx)
    child_node2 = mcts.select_childnode(node, action_idx)
    assert child_node != child_node2, "child nodes are not unique when they should be"
    mcts.add_node(child_node, node.id, action_idx)

    child_node2 = mcts.select_childnode(node, action_idx)
    assert child_node == child_node2, "accessing the same child node that we previously added, should not create a new node"

方法2:抽象通用Fixture(更适合多游戏扩展)

如果需要支持多个游戏,推荐这种方式:将游戏类型作为参数,编写通用Fixture根据参数生成对应实例,避免重复编写多个类似的Fixture。

示例代码:

import pytest

# 定义测试参数:每个元素为(游戏类型, action_idx)
GAME_TEST_CASES = [
    (TicTacToeGame, 0),
    # 后续添加其他游戏,比如(HiveGame, 1)
]

@pytest.fixture
def game(request):
    # 根据参数返回对应的游戏实例
    game_class = request.param
    return game_class()

@pytest.fixture
def node(game):
    # 基于游戏实例生成node
    return Node(game)

@pytest.fixture
def mcts(game):
    # 基于游戏实例生成mcts
    nnet = game.nnet_class(game, nn_args)  # 假设每个游戏类有nnet_class属性指向对应的网络类
    model = NNetWrapper(game, nnet, nn_args)
    return MCTS(model, mcts_args)

# 参数化时传入游戏类型和action_idx,同时指定game fixture的参数
@pytest.mark.parametrize("game, action_idx", GAME_TEST_CASES, indirect=["game"])
def test_select_childnode(mcts, node, action_idx):
    """
    Assert that childnode is creating a new node when needed.
    Assert that childnode is not creating a new node when not needed.
    """
    mcts.nodes[node.id] = node
    child_node = mcts.select_childnode(node, action_idx)
    child_node2 = mcts.select_childnode(node, action_idx)
    assert child_node != child_node2, "child nodes are not unique when they should be"
    mcts.add_node(child_node, node.id, action_idx)

    child_node2 = mcts.select_childnode(node, action_idx)
    assert child_node == child_node2, "accessing the same child node that we previously added, should not create a new node"

注:需要确保每个游戏类(如TicTacToeGame)定义了对应的网络类属性nnet_class,或者根据实际情况调整通用mctsFixture的实现。

验证结果

修改后,pytest会正确执行Fixture生成对应的实例,test_select_childnode可以复用在不同游戏场景中,无需重复编写测试函数和Fixture,解决了冗余代码问题,同时避免了原有的AttributeError。

内容的提问来源于stack exchange,提问作者Tue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 10:55:50