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
相关产品推荐
相关产品推荐

