单元测试失败排查:工单修改返回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
相关产品推荐
相关产品推荐

