测试中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)
你需要补充的知识点
- Mock的**"使用位置优先"**原则:Python的Mock是通过替换模块属性生效的,必须替换被测试代码实际引用的那个对象。
- 测试实例的生命周期:测试用例中的Mock只在当前测试方法执行期间生效,提前创建的实例不会感知到后续的Mock。
内容的提问来源于stack exchange,提问作者wavesinaroom
相关产品推荐
相关产品推荐

