Standing the Test of Time: The Date Provider Pattern
-
Upload
derek-lee-boire -
Category
Software
-
view
27 -
download
0
Transcript of Standing the Test of Time: The Date Provider Pattern
STANDING THE TEST OF TIMETHE DATE PROVIDER PATTERN
DEREK LEE | TOKYO IOS MEETUP APRIL 2017
!
CALENDAR
TO DO
TRANSPORTATION
SHOPPING
DRUMMING
FLIGHT DEPARTUREHANEDA ✈ OKINAWAAPR-25-2017 7:12PM
HOW MUCH TIME DO I HAVEBEFORE MY DEPARTURE?
LETS START WITH A SIMPLE STRUCTstruct Flight { let flightNumber: String let departureCityCode: String let departureDateTime: Date let arrivalCityCode: String let arrivalDateTime: Date}
WE CAN EASILY INITIALIZE WITH SOME DATAlet tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date( timeIntervalSince1970: 1493115120 // 4-25-2017 19:12 ), arrivalCityCode: "OKA", arrivalDateTime: Date( timeIntervalSince1970: 1493125920 // 4-25-2017 22:12 ))
CALCULATING THE REMAINING TIME IS EASY...?func timeRemainingUntilDeparture() -> TimeInterval { let departureTimeInterval = departureDateTime.timeIntervalSinceNow
let currentTimeInterval = Date().timeIntervalSinceNow
return departureTimeInterval - currentTimeInterval}
AND WITH SOME QUICK FORMATTING...func formatTimeInterval(timeInterval: TimeInterval) -> String { let d = Int(timeInterval / 86400) let h = Int(timeInterval / 3600) % 24 let m = Int(timeInterval / 60) % 60 let s = Int(timeInterval) % 60
return "\(d) days, \(h) hours, \(m) minutes, and \(s) seconds"}
WE CAN EASILY OUTPUT THE REMAINING TIMElet timeIntervalUntilDeparture = tripHome.timeRemainingUntilDeparture()
let formattedRemainingTime = formatTimeInterval( timeInterval: timeIntervalUntilDeparture)
print("Time until departure: " + formattedRemainingTime)
TIME UNTIL DEPARTURE: 25 DAYS, 19 HOURS,45 MINUTES, AND 50 SECONDS
!
!HMMM....
!MAYBE WE SHOULD WRITE A TEST FOR THIS
func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() {
}
func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )
}
func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )
let remainingTime = tripHome.timeRemainingUntilDeparture()
}
func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )
let remainingTime = tripHome.timeRemainingUntilDeparture()
XCTAssertEqual(Int(remainingTime), ???)}
func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )
let remainingTime = tripHome.timeRemainingUntilDeparture()
XCTAssertEqual(Int(remainingTime), 2144133)}
WE RUN THE TEST AND...
HMM.
WELL... LETS UPDATE THE TEST THEN
func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )
let remainingTime = tripHome.timeRemainingUntilDeparture()
XCTAssertEqual(Int(remainingTime), 2144113)}
!
WE NEED MORE CONTROL
HERE'S OUR CODE:func timeRemainingUntilDeparture() -> TimeInterval { let departureTimeInterval = departureDateTime.timeIntervalSinceNow
let currentTimeInterval = Date().timeIntervalSinceNow
return departureTimeInterval - currentTimeInterval}
WHAT IS IT THAT WE NEED MORE CONTROL OVER?func timeRemainingUntilDeparture() -> TimeInterval { let departureTimeInterval = departureDateTime.timeIntervalSinceNow
let currentTimeInterval = Date().timeIntervalSinceNow
return departureTimeInterval - currentTimeInterval}
THE CURRENT TIMElet currentTimeInterval = Date().timeIntervalSinceNow
THEREFORE, THE CURRENT TIME IS OUR DEPENDENCY
SO HOW CAN WE SOLVE THIS PROBLEM?
USING A DATE PROVIDER
DATE PROVIDER:AN OBJECT THAT WE CAN ASK TO GIVE US
THE CURRENT DATE & TIME
BACK TO OUR TEST THEN...
FIRST WE NEED TO PASS OUR DEPENDENCY INlet remainingTime = tripHome.timeRemainingUntilDeparture()
↓let remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: ???)
THIS CHANGES OUR METHOD'S DECLARATIONfunc timeRemainingUntilDeparture() -> TimeInterval
↓func timeRemainingUntilDeparture(dateProvider: DateProvider) -> TimeInterval
WHICH MEANS WE'LL NEED A PROTOCOLprotocol DateProvider {
}
... AND A WAY TO GET THE CURRENT DATE/TIMEprotocol DateProvider { func currentDateTime() -> Date}
WE'LL CREATE A FAKE OBJECT FOR TESTINGprotocol DateProvider { func currentDateTime() -> Date}
class FakeDateProvider: DateProvider {
func currentDateTime() -> Date {
}}
WHAT SHOULD THIS FUNCTION RETURN?protocol DateProvider { func currentDateTime() -> Date}
class FakeDateProvider: DateProvider {
func currentDateTime() -> Date { return ??? }}
METHOD NAME + "_RETURNVALUE"protocol DateProvider { func currentDateTime() -> Date}
class FakeDateProvider: DateProvider {
func currentDateTime() -> Date { return currentDateTime_returnValue }}
WHICH WE CAN DEFINE AND INITIALIZEprotocol DateProvider { func currentDateTime() -> Date}
class FakeDateProvider: DateProvider { var currentDateTime_returnValue = Date()
func currentDateTime() -> Date { return currentDateTime_returnValue }}
BACK TO OUR TEST... WE NEED TO PASS IN OUR FAKElet remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: ???)
BACK TO OUR TEST... WE NEED TO PASS IN OUR FAKElet remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: ???)
↓let fakeDateProvider = FakeDateProvider()let remainingTime = tripHome.timeRemainingUntilDeparture( dateProvider: fakeDateProvider)
NEXT IS THE MOST IMPORTANT STEP
SET THE VALUE YOU WANT THE FAKE TO RETURNfakeDateProvider.currentDateTime_returnValue = Date( timeIntervalSince1970: 1493028720 // 4-24-2017 19:12)
AND NOW WE CAN ADJUST OUR EXPECTATIONXCTAssertEqual(Int(remainingTime), 86400) // 24 Hours Prior
OUR UPDATED TEST:
func testTimeRemainingUntilDeparture_returnsCorrectTimeInterval() { let tripHome = Flight( flightNumber: "JAL925", departureCityCode: "HND", departureDateTime: Date(timeIntervalSince1970: 1493115120), arrivalCityCode: "OKA", arrivalDateTime: Date(timeIntervalSince1970: 1493125920) )
let fakeDateProvider = FakeDateProvider() fakeDateProvider.currentDateTime_returnValue = Date(timeIntervalSince1970: 1493028720)
let remainingTime = tripHome.timeRemainingUntilDeparture(dateProvider: fakeDateProvider)
XCTAssertEqual(Int(remainingTime), 86400)}
WHAT ABOUT THE ACTUAL IMPLEMENTATION?
THE DEFAULT IMPLEMENTATION IS EASYprotocol DateProvider { func currentDateTime() -> Date}
JUST RETURN THE CURRENT DATE & TIMEprotocol DateProvider { func currentDateTime() -> Date}
struct DefaultDateProvider: DateProvider { func currentDateTime() -> Date { return Date() }}
AND PASS IN THE DEFAULT TO YOUR ACTUAL CALL SITElet timeIntervalUntilDeparture = tripHome.timeRemainingUntilDeparture( dateProvider: DefaultDateProvider())
let formattedRemainingTime = formatTimeInterval( timeInterval: timeIntervalUntilDeparture)
print("Time until departure: " + formattedRemainingTime + " (" + String(timeIntervalUntilDeparture) + ")")
!
...
THIS IS ACTUALLY DEPENDENCY INJECTION
SUMMARY
▸ Identified our dependency (current date/time)▸ Created a protocol for it▸ Extracted it from our method
▸ Passed in the actual value at runtime▸ Created a fake we could use for testing
THANK YOU!@DEREKLEEROCK
GITHUB.COM/DEREKLEEROCK
Q&A: WHAT DO YOU USE FOR GETTING EPOCH DATES AND TIMES?
HTTPS://WWW.EPOCHCONVERTER.COM/MAC TERMINAL "DATE" COMMAND:
$ date -r 1 # Outputs Thu Jan 1 09:00:01 JST 1970
$ date -r 1493125920 # Outputs Tue Apr 25 22:12:00 JST 2017
$ date +%s # Outputs current date/time in # of seconds
SOURCE CODE IS AVAILABLE ON GITHUB:
https://github.com/derekleerock/StandingTheTestOfTime