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

如何测试含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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 16:05:29