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

Swift DateFormatter单元测试本地正常但CI流水线失败原因排查

Swift DateFormatter扩展单元测试本地正常但CI失败的原因及修复方案

失败原因

  • 日历实例不一致:待测试函数中计算"昨天"用的是Calendar.current,但判断日期是否为同一天用的是DateFormatter自身的calendar属性。CI环境的系统默认日历/区域设置可能和本地不同,导致两者的日期计算逻辑出现偏差。
  • 环境依赖问题:Calendar.current会受CI机器的系统时区、区域设置影响,而测试用例硬编码了特定时区的日期字符串,一旦CI环境默认时区与本地(-5时区)不同,就会引发日期计算错误。

修复方案

1. 修正待测试函数的日历一致性

将函数中计算"昨天"的Calendar.current替换为self.calendar,确保整个逻辑使用同一个日历实例,消除环境依赖:

public extension DateFormatter {
    func fromYesterday(date: String?, today: String?) -> Bool {
        guard let dateString = date,
              let todayString = today,
              let date = self.date(from: dateString),
              let today = self.date(from: todayString) else {
            return false
        }

        // 使用DateFormatter自身的calendar,而非系统默认的Calendar.current
        guard let dayBefore = self.calendar.date(byAdding: .day, value: -1, to: today) else {
            return false
        }

        return self.calendar.isDate(date, inSameDayAs: dayBefore)
    }
}

2. 强化测试Stub的环境隔离

在测试用的DateFormatter Stub中明确指定日历为公历,进一步避免区域设置影响:

struct DateFormatterStubs {
    static var defaultDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: -5)
        // 明确指定日历为公历,避免系统默认日历差异
        formatter.calendar = Calendar(identifier: .gregorian)
        return formatter
    }()
}

3. 优化测试用例(可选,提升严谨性)

基于Stub的日历和时区生成测试日期,替代硬编码字符串,彻底消除环境差异影响:

class DateFormatterExtensionTests: XCTestCase {

    let dateFormatter = DateFormatterStubs.defaultDateFormatter

    func testFromYesterday_todayAsDayBeforeDayThanGivenDate_returnsTrue() {
        let calendar = dateFormatter.calendar
        let timeZone = dateFormatter.timeZone
        
        // 生成第一个测试场景的日期
        var todayComponents = DateComponents(timeZone: timeZone, year: 2021, month: 1, day: 10, hour: 0, minute: 0, second: 0)
        guard let today = calendar.date(from: todayComponents) else {
            XCTFail("Failed to create today date")
            return
        }
        let todayString = dateFormatter.string(from: today)
        
        var yesterdayComponents = DateComponents(timeZone: timeZone, year: 2021, month: 1, day: 9, hour: 23, minute: 59, second: 59)
        guard let yesterday = calendar.date(from: yesterdayComponents) else {
            XCTFail("Failed to create yesterday date")
            return
        }
        let yesterdayString = dateFormatter.string(from: yesterday)
        
        XCTAssertTrue(dateFormatter.fromYesterday(date: yesterdayString, today: todayString))
        
        // 生成第二个测试场景的日期
        yesterdayComponents.hour = 0
        yesterdayComponents.minute = 0
        yesterdayComponents.second = 0
        guard let earlyYesterday = calendar.date(from: yesterdayComponents) else {
            XCTFail("Failed to create early yesterday date")
            return
        }
        let earlyYesterdayString = dateFormatter.string(from: earlyYesterday)
        
        todayComponents.hour = 23
        todayComponents.minute = 59
        todayComponents.second = 59
        guard let lateToday = calendar.date(from: todayComponents) else {
            XCTFail("Failed to create late today date")
            return
        }
        let lateTodayString = dateFormatter.string(from: lateToday)
        
        XCTAssertTrue(dateFormatter.fromYesterday(date: earlyYesterdayString, today: lateTodayString))
    }
}

内容的提问来源于stack exchange,提问作者Andrés Mendieta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 07:22:53