如何测试含while循环的类方法?解决跨模块属性测试难题
测试跨模块属性依赖的while循环方法的可行方案
问题背景
需要测试类中的_check_for_pause方法,该方法包含依赖跨模块属性self.ui_options.paused的while循环,测试时会陷入无限循环。此前通过Mock类属性并设置side_effect=[True, False]的方案仅适用于类内属性,无法直接复用在跨模块属性上,现需其他可行测试方案。
待测试方法代码:
def _check_for_pause(self): """Check if the game is paused and update pause time.""" if self.ui_options.paused: pause_start_time = pygame.time.get_ticks() while self.ui_options.paused: self.check_events() if not self.ui_options.paused: pause_end_time = pygame.time.get_ticks() self.pause_time += pause_end_time - pause_start_time break
此前适用类内属性的测试方案:
@mock.patch.object(Class, "class_attribute", new_callable=mock.PropertyMock) def test_example(self, mock_class_attribute): mock_class_attribute.side_effect = [True, False]
可行测试方案
方案1:Mock self.ui_options实例的paused属性(PropertyMock)
直接针对被测实例的ui_options对象的paused属性做Mock,用PropertyMock模拟属性的动态返回值,让第一次返回True进入循环,第二次返回False跳出循环。
示例代码:
def test_check_for_pause(self): # 初始化被测类实例 test_obj = YourGameClass() # Mock ui_options的paused属性,使用PropertyMock with mock.patch.object(test_obj.ui_options, 'paused', new_callable=mock.PropertyMock) as mock_paused: # 设置side_effect,第一次True进入循环,第二次False跳出 mock_paused.side_effect = [True, False] # Mock pygame.time.get_ticks() 返回固定时间,方便断言 with mock.patch('pygame.time.get_ticks') as mock_ticks: mock_ticks.side_effect = [1000, 2000] # 调用待测试方法 test_obj._check_for_pause() # 断言暂停时间是否正确累加 self.assertEqual(test_obj.pause_time, 1000)
方案2:Mock check_events方法触发状态变更
循环内会调用self.check_events(),该方法通常用于处理用户事件(比如按暂停键)切换paused状态。可以Mock这个方法,让它在第一次执行后把paused改成False,从而跳出循环。
示例代码:
def test_check_for_pause_with_event(self): test_obj = YourGameClass() # 先设置初始paused为True with mock.patch.object(test_obj.ui_options, 'paused', new_callable=mock.PropertyMock) as mock_paused: mock_paused.return_value = True # Mock check_events方法,第一次调用后把paused改成False def toggle_pause(): mock_paused.return_value = False test_obj.check_events = mock.Mock(side_effect=toggle_pause) # Mock时间戳 with mock.patch('pygame.time.get_ticks') as mock_ticks: mock_ticks.side_effect = [1000, 2000] test_obj._check_for_pause() # 断言check_events被调用一次,暂停时间正确 test_obj.check_events.assert_called_once() self.assertEqual(test_obj.pause_time, 1000)
方案3:替换整个ui_options对象为Mock
直接给被测实例的ui_options赋值一个Mock对象,手动控制其paused属性的返回值变化,这种方式更灵活,尤其适合ui_options来自外部模块的场景。
示例代码:
def test_check_for_pause_mock_ui_options(self): test_obj = YourGameClass() # 创建Mock的ui_options对象 mock_ui = mock.Mock() # 设置paused属性的side_effect mock_ui.paused = mock.PropertyMock(side_effect=[True, False]) # 替换被测实例的ui_options test_obj.ui_options = mock_ui # Mock时间戳 with mock.patch('pygame.time.get_ticks') as mock_ticks: mock_ticks.side_effect = [1000, 2000] test_obj._check_for_pause() self.assertEqual(test_obj.pause_time, 1000)
方案4:超时限制(兜底方案)
如果上述Mock方案都无法生效,可以给测试用例设置超时时间,防止无限循环卡住测试。这只是临时兜底方案,无法验证逻辑正确性,仅用于避免测试挂起。
示例代码(以pytest为例):
import pytest @pytest.mark.timeout(1) # 1秒超时 def test_check_for_pause_timeout(self): test_obj = YourGameClass() # 尝试基础Mock,即使未成功,超时也会终止测试 test_obj._check_for_pause()
内容的提问来源于stack exchange,提问作者Khada Ake
相关产品推荐
相关产品推荐

