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

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,需要分析异常原因并给出修复方案。

异常原因分析
  1. 正则匹配失败触发异常:最后一个测试用例"9:00 AM 5:00 PM"中没有关键字to,不符合函数里正则表达式^(\d+)\:?(.+)? to (\d+)\:?(.+)?$的匹配规则,导致matches为None,进入else分支抛出Exception,随后被except Exception捕获并转抛ValueError。
  2. 测试用例写法错误:断言函数抛出异常不能用== ValueError,这种写法是在判断函数返回值是否等于ValueError类对象,但实际函数会直接抛出异常而非返回该对象,因此pytest会捕获到这个未预期的异常,判定测试失败。
  3. 原函数逻辑存在其他漏洞:
    • 仅判断输入包含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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 19:50:35