Python新手求助:Pytest测试时间转换函数时出现ValueError异常
问题描述
作为Python新手,我编写了一个12小时制转24小时制的时间格式转换函数,尝试用try-except捕获ValueError,同时通过条件语句处理错误场景。函数代码如下:
import re def main(): print(convert(input("Hours: "))) def convert(s): try: # matches := re.search(r"^(\d+)\:?(.+)? to (\d+)\:?(.+)?$", s) if (matches := re.search(r"^(\d+)\:?(.+)? to (\d+)\:?(.+)?$", s)) and s.__contains__("AM"): matchesGroup1 = matches.group(1) matchesGroup2 = matches.group(2).split(" ") matchesGroup3 = matches.group(3) matchesGroup4 = matches.group(4).split(" ") if 0 < int(matches.group(1)) <= 12 and 0 < int(matches.group(3)) <= 12: if matchesGroup2[1] == "AM" and matchesGroup4[1] == "PM": if matchesGroup1 == "12" and matchesGroup3 == "12": matchesGroup1 = "00" matchesGroup3 = "12" else: matchesGroup3 = int(matchesGroup3) + 12 matchesGroup1 = "0" + matchesGroup1 else: if matchesGroup1 == "12" and matchesGroup3 == "12": matchesGroup3 = "12" matchesGroup1 = "00" else: matchesGroup1 = int(matchesGroup1) + 12 matchesGroup3 = "0" + matchesGroup3 if len(matchesGroup2[0]) == 0 and len(matchesGroup4[0]) == 0: matchesGroup2[0] += "00" matchesGroup4[0] += "00" elif "00" <= matchesGroup2[0] <= "59" and "00" <= matchesGroup4[0] <= "59": pass else: raise Exception return f"{matchesGroup1}:{matchesGroup2[0]} to {matchesGroup3}:{matchesGroup4[0]}" else: raise Exception except Exception: raise ValueError except ValueError: raise ValueError if __name__ == "__main__": main()
我用Pytest编写了测试用例:
import pytest from working import convert def test_convert(): assert convert("9 AM to 5 PM") == "09:00 to 17:00" assert convert("9:00 AM to 5:00 PM") == "09:00 to 17:00" assert convert("10 PM to 8 AM") == "22:00 to 08:00" assert convert("10:30 PM to 8:50 AM") == "22:30 to 08:50" assert convert("9:00 AM 5:00 PM") == ValueError
但测试最后一个用例时抛出异常,提示working.py:45: Exception、test_working.py:9:、working.py:48: ValueError,需要分析异常原因并给出修复方案。
异常原因分析
- 正则匹配失败触发异常:最后一个测试用例
"9:00 AM 5:00 PM"中没有关键字to,不符合函数里正则表达式^(\d+)\:?(.+)? to (\d+)\:?(.+)?$的匹配规则,导致matches为None,进入else分支抛出Exception,随后被except Exception捕获并转抛ValueError。 - 测试用例写法错误:断言函数抛出异常不能用
== ValueError,这种写法是在判断函数返回值是否等于ValueError类对象,但实际函数会直接抛出异常而非返回该对象,因此pytest会捕获到这个未预期的异常,判定测试失败。 - 原函数逻辑存在其他漏洞:
- 仅判断输入包含
AM,如果输入是全PM的格式(如"1 PM to 2 PM")会直接触发异常。 - 用字符串比较分钟范围(
"00" <= matchesGroup2[0] <= "59"),虽然部分场景有效,但逻辑不严谨,比如"60"作为字符串会被误判为合法。 - 12小时转24小时的逻辑仅覆盖了前AM后PM、前PM后AM的同时12点场景,其他边界情况(如
"12 AM to 1 PM")会生成错误的时间格式(如"012:00")。
- 仅判断输入包含
修复方案
1. 修复测试用例
使用pytest的pytest.raises上下文管理器来断言函数抛出异常,拆分测试用例为合法格式和非法格式两类:
import pytest from working import convert def test_convert_valid(): assert convert("9 AM to 5 PM") == "09:00 to 17:00" assert convert("9:00 AM to 5:00 PM") == "09:00 to 17:00" assert convert("10 PM to 8 AM") == "22:00 to 08:00" assert convert("10:30 PM to 8:50 AM") == "22:30 to 08:50" assert convert("12 AM to 12 PM") == "00:00 to 12:00" assert convert("12:30 PM to 12:30 AM") == "12:30 to 00:30" def test_convert_invalid(): # 缺少to的情况 with pytest.raises(ValueError): convert("9:00 AM 5:00 PM") # 小时超出范围 with pytest.raises(ValueError): convert("13 AM to 5 PM") # 分钟超出范围 with pytest.raises(ValueError): convert("9:60 AM to 5 PM") # 格式错误 with pytest.raises(ValueError): convert("9 AM 5 PM")
2. 重构转换函数
优化正则表达式、拆分转换逻辑、简化异常处理,修复所有逻辑漏洞:
import re def main(): print(convert(input("Hours: "))) def convert_12_to_24(hour: str, minute: str | None, period: str) -> str: """将12小时制的小时、分钟、时段转换为24小时制的时间字符串""" hour_int = int(hour) # 处理分钟,默认补00 minute_str = minute if minute is not None else "00" minute_int = int(minute_str) # 验证分钟合法性 if not 0 <= minute_int <= 59: raise ValueError # 转换小时 if period == "AM": hour_str = "00" if hour_int == 12 else f"{hour_int:02d}" else: # PM hour_str = "12" if hour_int == 12 else f"{hour_int + 12:02d}" return f"{hour_str}:{minute_str}" def convert(s: str) -> str: # 精准匹配12小时制时间格式:小时(1-2位) + 可选分钟(两位) + AM/PM + to + 结束时间 pattern = r"^(\d{1,2})(?:\:(\d{2}))? (AM|PM) to (\d{1,2})(?:\:(\d{2}))? (AM|PM)$" matches = re.search(pattern, s) if not matches: raise ValueError start_hour = matches.group(1) start_minute = matches.group(2) start_period = matches.group(3) end_hour = matches.group(4) end_minute = matches.group(5) end_period = matches.group(6) # 验证小时合法性 start_hour_int = int(start_hour) end_hour_int = int(end_hour) if not (1 <= start_hour_int <= 12) or not (1 <= end_hour_int <= 12): raise ValueError # 转换开始和结束时间 start_24 = convert_12_to_24(start_hour, start_minute, start_period) end_24 = convert_12_to_24(end_hour, end_minute, end_period) return f"{start_24} to {end_24}" if __name__ == "__main__": main()
修复要点说明
- 正则表达式优化:使用
(\d{1,2})匹配1-2位小时,(?:\:(\d{2}))?匹配可选的两位分钟(非捕获组),确保输入格式严格符合要求。 - 拆分转换逻辑:将12小时转24小时的逻辑抽离为辅助函数,代码更清晰,便于维护和测试。
- 数值验证:所有时间数值(小时、分钟)转成整数后再做范围判断,避免字符串比较的逻辑漏洞。
- 简化异常处理:直接在不符合条件时抛出
ValueError,无需捕获Exception再转抛,逻辑更直接。
内容的提问来源于stack exchange,提问作者Navfalbek
相关产品推荐
相关产品推荐

