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

测试中Mock实例化Deck类遇属性错误,及相关Mock策略咨询

解决二十一点游戏Mock Deck类时的AttributeError及相关Mock问题

首先看你遇到的AttributeError:你用@patch("server.game_manager.deck"),但server/game_manager.py里是from .deck import Deck,这意味着game_manager模块中只导入了Deck类,并没有保留deck模块的引用,所以server.game_manager没有deck这个属性,patch路径完全错误。


你的三个问题解答:

1. 应该Mock哪个目标?

必须Mock**server.game_manager.Deck**——也就是GameManager模块中实际使用的Deck类,遵循Mock的核心原则:Mock对象被使用的位置,而非定义的位置。

2. 两种Mock方式的区别

  • Mock定义位置(server.deck.Deck):会替换所有模块中导入的原Deck类,相当于全局替换。如果其他测试也用到Deck,会被这个Mock影响,测试隔离性差,容易导致测试用例互相干扰。
  • Mock使用位置(server.game_manager.Deck):只替换GameManager模块内引用的Deck类,其他模块的Deck不受影响,每个测试的依赖都是独立的,测试结果更可靠。

3. 实例创建与Mock策略选择

绝对不要在测试套件顶部提前创建GameManager实例,也不建议全局Mock。正确的做法是在特定测试用例(或setUp方法)中,先启用Mock再创建实例,或者针对实例的依赖单独替换。两者区别:

  • 提前创建实例+全局Mock:实例在Mock生效前就完成了初始化,它的_deck属性是原Deck类的实例,Mock不会生效;而且全局Mock会污染其他测试,导致结果不可控。
  • 特定测试中Mock+按需创建实例:每个测试用例都是独立的,Mock仅在当前测试周期内生效,实例会使用Mock后的Deck类初始化,测试隔离性强,结果稳定。

修正后的代码示例

先修复game_manager.py的语法错误:

# server/game_manager.py
from .deck import Deck

class GameManager:
    def __init__(self):
        self._deck = Deck()
        # 补充_game_state初始化逻辑(假设启动时调用fetch_cards)
        self._game_state = {
            "player_one": {
                "cards": self._deck.fetch_cards()
            }
        }

正确的测试代码:

# server/tests/game_manager_test.py
import unittest
from unittest.mock import patch, Mock
from ..game_manager import GameManager

class TestCardDraw(unittest.TestCase):
    @patch("server.game_manager.Deck")
    def test_draw_game_start_cards(self, mock_deck_cls):
        # 配置Mock Deck实例的返回值
        mock_deck_instance = Mock()
        mock_deck_instance.fetch_cards.return_value = [7, 2]
        # 让Mock类返回这个实例
        mock_deck_cls.return_value = mock_deck_instance

        # 此时创建GameManager,会使用Mock的Deck类
        gm = GameManager()
        self.assertEqual(len(gm._game_state["player_one"]["cards"]), 2)

if __name__ == "__main__":
    unittest.main()

另一种:针对已存在的实例替换依赖

如果需要复用GameManager实例,可直接替换其_deck属性:

class TestCardDraw(unittest.TestCase):
    def setUp(self):
        self.gm = GameManager()

    def test_draw_game_start_cards(self):
        # 创建Mock Deck实例并配置返回值
        mock_deck = Mock()
        mock_deck.fetch_cards.return_value = [7, 2]
        
        # 替换GameManager实例的_deck属性
        self.gm._deck = mock_deck
        # 重新触发卡牌获取逻辑(根据你的实际业务调整)
        self.gm._game_state["player_one"]["cards"] = self.gm._deck.fetch_cards()
        
        self.assertEqual(len(self.gm._game_state["player_one"]["cards"]), 2)

你需要补充的知识点

  1. Mock的**"使用位置优先"**原则:Python的Mock是通过替换模块属性生效的,必须替换被测试代码实际引用的那个对象。
  2. 测试实例的生命周期:测试用例中的Mock只在当前测试方法执行期间生效,提前创建的实例不会感知到后续的Mock。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:30:19