pytest如何为含KeyboardInterrupt块的函数编写100%覆盖率用例
pytest覆盖KeyboardInterrupt异常分支实现100%覆盖率方案
问题说明
- 被测函数
loadingBar会捕获KeyboardInterrupt(用户按Ctrl+C触发的中断)执行进度保存逻辑,需要编写pytest用例覆盖该异常分支,满足coverage工具100%代码覆盖率要求 - 原被测代码
main_game.py存在几处运行时错误,先给出修正后的可运行版本:
# main_game.py import json class Cache: other_details = dict() def save_progress_for_future(progress): file = open('progress.json', 'w') content = {'progress_percent':progress, **Cache.other_details} json.dump(content, file) file.close() # 对齐except块的函数调用命名 save_progress_in_file = save_progress_for_future def get_progress_percent(): return 0 def loadingBar(): progress = 0 while True: try: ... # 其他业务逻辑 progress = get_progress_percent() print('\r Loading' + str('.' * progress//10) + '\r', end='') except KeyboardInterrupt: save_progress_in_file(progress) # 补充中断后的退出逻辑,避免死循环 return
实现思路
不需要实际向测试进程发送系统中断信号,直接通过mock在try块逻辑中主动抛出KeyboardInterrupt异常,即可稳定模拟Ctrl+C场景,避免把测试进程本身杀了导致用例失败
- 用mock替换
get_progress_percent函数,指定调用时抛出KeyboardInterrupt,精准控制中断触发时机 - mock掉进度保存函数,验证中断触发后保存逻辑是否被正确调用、入参是否符合预期
- 确保异常触发后循环能正常退出,避免测试卡死
测试用例编写
新建测试文件test_main_game.py,代码如下:
# test_main_game.py from unittest.mock import patch, MagicMock import main_game def test_keyboard_interrupt_triggers_save(): # 模拟第一次获取进度就触发Ctrl+C中断 mock_progress = MagicMock(side_effect=KeyboardInterrupt) mock_save_func = MagicMock() # patch替换被测模块中的依赖 with patch("main_game.get_progress_percent", mock_progress), \ patch("main_game.save_progress_in_file", mock_save_func): main_game.loadingBar() # 断言中断后确实调用了保存逻辑,初始进度值为0 mock_save_func.assert_called_once_with(0) def test_interrupt_at_specific_progress(): target_progress = 70 call_times = 0 def progress_mock_logic(): nonlocal call_times call_times += 1 # 第4次获取进度时触发中断,此时进度为70 if call_times == 4: raise KeyboardInterrupt return target_progress mock_save_func = MagicMock() with patch("main_game.get_progress_percent", side_effect=progress_mock_logic), \ patch("main_game.save_progress_in_file", mock_save_func): main_game.loadingBar() # 断言保存的进度值和触发中断时的进度一致 mock_save_func.assert_called_once_with(target_progress)
覆盖率验证步骤
- 安装依赖:
pip install pytest coverage pytest-mock - 执行测试并统计覆盖率:
coverage run -m pytest test_main_game.py -v - 生成终端覆盖率报告:
coverage report -m - 此时报告中
main_game.py的所有代码行都会被标记为已覆盖,包括KeyboardInterrupt对应的except分支
注意事项
- patch路径必须写对:要patch被测模块
main_game下导入的依赖函数,不要patch错模块路径导致mock不生效 - 原死循环逻辑必须在异常处理后加退出逻辑(return/break),否则测试会无限卡住
- 不要用发送系统信号的方式模拟中断,信号会作用于整个测试进程,容易导致测试框架本身异常退出
内容的提问来源于stack exchange,提问作者Aman Ahmed Siddiqui
相关产品推荐
相关产品推荐

