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

单元测试失败排查:工单修改返回None场景触发StopIteration错误

问题根源分析

1. 重复定义setUp方法导致配置失效

你的测试类里写了三个setUp方法,unittest只会保留最后一个,前两个测试实际用的是config.usertype = "manager"的配置,完全跳过了客户端权限检查逻辑,直接导致测试场景不成立。

2. Mock目标错误

代码里用的是take_input函数而非直接调用input,你mock了builtins.input,但take_input内部的input没被正确拦截,导致mock的输入序列无法匹配实际调用次数。

3. 数据库查询未处理空结果

当查询的ID不存在时,cur.fetchone()会返回None,此时直接访问[6]会抛出IndexError,代码提前崩溃,走不到预期的return None分支。

4. 输入序列不匹配(StopIteration直接原因)

由于上述问题,代码没在预期位置返回None,而是继续执行后续的take_input/input调用,但你提供的mock输入序列长度不够,触发StopIteration。


修复步骤

步骤1:合并并修正setUp方法

用单个setUp初始化基础配置,在每个测试方法里按需覆盖特定配置:

class TestAmendData(unittest.TestCase):
    def setUp(self):
        # 基础配置,每个测试可按需覆盖
        config.name = ""
        config.usertype = ""

    @patch('amend.take_input')  # 替换为take_input实际所在的模块路径
    def test_should_return_none_on_no_permission_to_edit(self, mock_take_input):
        # 设置测试专属配置
        config.name = "ZAVALC"
        config.usertype = "client"
        # Mock take_input返回ID=1
        mock_take_input.return_value = "1"
        
        # 模拟数据库查询结果:返回非当前用户的工单记录
        with patch('sqlite3.connect') as mock_connect:
            mock_cursor = mock_connect.return_value.cursor.return_value
            # 第7个元素(索引6)是其他用户,触发权限校验失败
            mock_cursor.fetchone.return_value = (1, "2024-01-01", "10:00", "Product", "Use Case", "Signed", "OTHER_USER", "Staff", False)
            
            self.assertIsNone(amend.change(database="test"))

    @patch('amend.take_input')
    def test_should_return_none_on_client_assigning_to_IT(self, mock_take_input):
        config.name = "BRAYC"
        config.usertype = "client"
        # 第一次take_input返回ID=1,第二次返回"Assigned To"
        mock_take_input.side_effect = ["1", "Assigned To"]
        
        # 模拟数据库查询结果:当前用户拥有该工单权限
        with patch('sqlite3.connect') as mock_connect:
            mock_cursor = mock_connect.return_value.cursor.return_value
            mock_cursor.fetchone.return_value = (1, "2024-01-01", "10:00", "Product", "Use Case", "Signed", "BRAYC", "Staff", False)
            
            self.assertIsNone(amend.change(database="test"))

    # 修正成功测试的mock目标
    @patch('amend.take_input')
    @patch('builtins.input')  # 直接input的地方也要mock
    def test_should_amend_value_on_correct_input_as_staff(self, mock_input, mock_take_input):
        config.usertype = "manager"
        mock_take_input.side_effect = ["1", "Pro", "y"]
        mock_input.return_value = "Amended Value"
        
        with patch('sqlite3.connect') as mock_connect:
            mock_cursor = mock_connect.return_value.cursor.return_value
            self.assertTrue(amend.change(database="test"))

步骤2:修复change函数的异常处理与资源释放

在访问查询结果前先判空,同时确保所有分支都关闭数据库连接:

def change(database="it"):
    conn: Connection = sqlite3.connect(f"{database}.db")
    config.connection_open = True
    cur = conn.cursor()
    row = take_input('Select ID to amend', 'ID: ')
    if row == "":
        change()
    if config.usertype == "client":
        # 改用参数化查询,避免SQL注入
        cur.execute("SELECT * from requests WHERE ID=?", (row,))
        row_result = cur.fetchone()
        if not row_result:
            print("Error: Request ID not found. Returning to Home.")
            conn.close()
            config.connection_open = False
            return None
        rowreq = row_result[6]
        if rowreq != config.name:
            print("Error: You do not have permission to edit this file. Returning to Home.")
            conn.close()
            config.connection_open = False
            return None
    # 后续代码...
    # 所有return None的分支都要添加数据库关闭逻辑

步骤3:统一输入处理的mock目标

确保所有用户输入(包括take_input和直接input)都被正确mock,根据take_input的实际模块路径patch对应的函数,而非底层的builtins.input。


关键注意事项

  • 测试类中只能有一个setUp方法,重复定义会被覆盖。
  • 必须mock数据库操作,避免测试依赖真实数据库状态。
  • 使用参数化SQL查询,既防止注入,也让mock更简单。
  • 所有分支路径都要关闭数据库连接,避免资源泄漏。

内容的提问来源于stack exchange,提问作者dan.robertson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 17:12:59