Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unit Testing and Travis CI

Unit Testing and Travis CI

2018/4/21 @ Swift Taipei

nalydadad

April 21, 2018
Tweet

More Decks by nalydadad

Other Decks in Technology

Transcript

  1. • A christian at New Life Church • Working at

    KKBOX • iOS Experience: 1+ years in ObjC、Swift • iOSDC Japan 2017 Speaker (Apple Watch) • 偶爾會出沒在 CocoaHeads Taipei、Swift Taipei About Me
  2. add(a: int, b:int) input output func add(a: Int, b: Int)

    -> Int { return a + b } 1. 起⼿手式
  3. add(a: int, b:int) input output func testOneAddTwo() { let result

    = add(a: one, b: two) XCTAssert(result == 3) } func add(a: Int, b: Int) -> Int { return a + b } 1. 起⼿手式
  4. import XCTest class NewTests: XCTestCase { override func setUp() {

    super.setUp() } override func tearDown() { super.tearDown() } } 2. XCTest Framework
  5. import XCTest class NewTests: XCTestCase { override func setUp() {

    super.setUp() } override func tearDown() { super.tearDown() } } 繼承 XCTest Case,class name 需以 Tests 結尾 2. XCTest Framework
  6. import XCTest class NewTests: XCTestCase { override func setUp() {

    super.setUp() } override func tearDown() { super.tearDown() } } 繼承 XCTest Case,class name 需以 Tests 結尾 每⼀一條 test case 開始測前要做的事 2. XCTest Framework
  7. import XCTest class NewTests: XCTestCase { override func setUp() {

    super.setUp() } override func tearDown() { super.tearDown() } } 繼承 XCTest Case,class name 需以 Tests 結尾 每⼀一條 test case 測完後要做的事 每⼀一條 test case 開始測前要做的事 2. XCTest Framework
  8. import XCTest class NewTests: XCTestCase { override func setUp() {

    super.setUp() } override func tearDown() { super.tearDown() } } func testExample() { XCTAssert(1 == 1, "wrong message") XCTAssertNil(nil, "wrong message") XCTAssertNotNil("Any", "wrong message") XCTAssertTrue(true, "wrong message") XCTAssertFalse(false, "wrong message") XCTAssertEqual(1, 1, "wrong message") XCTAssertGreaterThan(1, 0, "wrong message") XCTAssertLessThan(0, 1, "wrong message") XCTAssertThrowsError(CalculatorError.unSupportedOperator, "wrong message") XCTAssertNoThrow(CalculatorError.unSupportedOperator, "wrong message") } 繼承 XCTest Case,class name 需以 Tests 結尾 每⼀一條 test case 測完後要做的事 每⼀一條 test case 開始測前要做的事 2. XCTest Framework
  9. import XCTest class NewTests: XCTestCase { override func setUp() {

    super.setUp() } override func tearDown() { super.tearDown() } } func testExample() { XCTAssert(1 == 1, "wrong message") XCTAssertNil(nil, "wrong message") XCTAssertNotNil("Any", "wrong message") XCTAssertTrue(true, "wrong message") XCTAssertFalse(false, "wrong message") XCTAssertEqual(1, 1, "wrong message") XCTAssertGreaterThan(1, 0, "wrong message") XCTAssertLessThan(0, 1, "wrong message") XCTAssertThrowsError(CalculatorError.unSupportedOperator, "wrong message") XCTAssertNoThrow(CalculatorError.unSupportedOperator, "wrong message") } 繼承 XCTest Case,class name 需以 Tests 結尾 每⼀一條 test case 測完後要做的事 test case 需以 test 前綴 每⼀一條 test case 開始測前要做的事 2. XCTest Framework
  10. import XCTest class NewTests: XCTestCase { override func setUp() {

    super.setUp() } override func tearDown() { super.tearDown() } } func testExample() { XCTAssert(1 == 1, "wrong message") XCTAssertNil(nil, "wrong message") XCTAssertNotNil("Any", "wrong message") XCTAssertTrue(true, "wrong message") XCTAssertFalse(false, "wrong message") XCTAssertEqual(1, 1, "wrong message") XCTAssertGreaterThan(1, 0, "wrong message") XCTAssertLessThan(0, 1, "wrong message") XCTAssertThrowsError(CalculatorError.unSupportedOperator, "wrong message") XCTAssertNoThrow(CalculatorError.unSupportedOperator, "wrong message") } 繼承 XCTest Case,class name 需以 Tests 結尾 每⼀一條 test case 測完後要做的事 test case 需以 test 前綴 每⼀一條 test case 開始測前要做的事 + U 2. XCTest Framework
  11. func testSomeClassDoSomething() { // Arrange let target = SomeClass() //

    Act target.doSomething() // Assert XCTAssert(...) }
  12. func testOneAddTwo() { // Arrange let calculator = Calculator() //

    Act let result = calculator.add(1, 2) // Assert XCTAssert(result == 3) } 4. 驗證⽅方法: (1) 回傳值 Let’s test “add(_: Int, _: Int )”
  13. class UsersFeedCard: UIView { var starIcon = UIImageView(image: UIImage(named: “star")!)

    private let isCelebrity: Bool init(isCelebrity: Bool) { self.isCelebrity = isCelebrity } func configureUI() { starIcon.isHidden = !self.isCelebrity } /* … */ } 4. 驗證⽅方法: (2) 狀狀態 - 1 Let’s test “configureUI( )”
  14. class UsersFeedCardTests: XCTestCase { func testCelebrityFeedCardShowsStar() { // Arrange let

    celebrityFeedCard = UsersFeedCard(isCelebrity: true) // Act celebrityFeedCard.configureUI() // Assert XCTAssertFalse(celebrityFeedCard.starIcon.isHidden) } } 4. 驗證⽅方法: (2) 狀狀態 - 1
  15. class UserListTableViewController: UITableViewController { let userInfos: Array<UserInfo>! init(with userInfos: Array<UserInfo>)

    { self.userInfos = userInfos super.init(style: .plain) } override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, 
 numberOfRowsInSection section: Int) -> Int { return userInfos.count } /* ... */ } 4. 驗證⽅方法: (2) 狀狀態 - 2
  16. class UserListTableViewControllerTests: XCTestCase { func testNumberOfRows() { // Arrange let

    userInfos = [UserInfo(name: “John”), UserInfo(name: “Mary”), UserInfo(name: “Joshua”)] let userListTableViewController = UserListTableViewController(with: userInfos) // Act userListTableViewController.tableView.reloadData() // Assert XCTAssert(userListTableViewController.tableView.numberOfRows(inSection: 0) == 3) } } 4. 驗證⽅方法: (2) 狀狀態 - 2
  17. Views API Navigation Controller Player AppDelgate Images NSUser Defualt Data

    Model Example Too Much
 Dependencies View
 Controller
  18. Signal Outer
 Module Target Module test test test test test

    test Signal Outer
 Module test ? ? ?
  19. Target Module Stub
 Module Stub Method test test test test

    test test 模擬外部⾏行行為 控制 訊號 造假
  20. Mock Example class ActionHandler: DataSourceDelegate { func dataSource(dataSource: DataSource, partialUpdateAt

    section: Index) { /*…*/ } } class MockActionHandler: MenuActionHandler { var partialUpdateWasCalled = false override func dataSource(dataSource: DataSource, partialUpdateAt section: Index) { partialUpdateWasCalled = true } } Inheritance
  21. Confirm Protocol class ActionHandler: DataSourceDelegate { func dataSource(dataSource: DataSource, partialUpdateAt

    section: Index) { /*…*/ } } class MockActionHandler: DataSourceDelegate { var partialUpdateWasCalled = false func dataSource(dataSource: DataSource, partialUpdateAt section: Index) { partialUpdateWasCalled = true } } Mock Example
  22. We have a view controller. class UserListViewController: UIViewController { var

    apiHandler: APIHandler? init() { super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
  23. class UserListViewController: UIViewController { var apiHandler: APIHandler? init() { super.init(nibName:

    nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } Dependency Find the dependencies.
  24. Injection - Constructor class UserListViewController: UIViewController { private let apiHandler:

    APIHandler init(apiHandler: APIHandler) { self.apiHandler = apiHandler super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } let userListViewController = UserListViewController(apiHandler: StubAPIHandler())
  25. class UserListViewController: UIViewController { var apiHandler: APIHandler? init() { super.init(nibName:

    nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } let userListViewController = UserListViewController() userListViewController.apiHandler = StubAPIHandler() Injection - Properties
  26. class UserListViewController: UIViewController { init() { super.init(nibName: nil, bundle: nil)

    } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func fetchData(with apiHandler: APIHandler) { /* … */ } } let userListViewController = UserListViewController() userListViewController.fetchData(with: StubAPIHandler()) Injection - Arguments
  27. 卻⼜又不影響架構 class UserTableViewController { let session: URLSession! init(session: URLSession =

    URLSession.shared) { /* ... */ } /* ... */ } let userTableViewController = UserTableViewController(session: MockURLSession)
  28. Mock id mock = OCMClassMock([SomeClass class]); /* run code under

    test */ OCMVerify([mock someMethod]); OCMock
  29. func testAsyncTask() { let promise = expectation(description: "Asnyc test complete")

    let fakeAsyncTaskManager = AsyncTaskManager() fakeAsyncTaskManager.doSomething { result, error in if let error = error { XCTFail("Error: \(error.localizedDescription)") } else { if result == .success { promise.fulfill() } else { XCTFail("Job fail, result code: \(result.rawValue)") } } } waitForExpectations(timeout: 5, handler: nil) } Asynchronous Callback
  30. func testAsyncTask() { let promise = expectation(description: "Asnyc test complete")

    let fakeAsyncTaskManager = AsyncTaskManager() fakeAsyncTaskManager.doSomething { result, error in if let error = error { XCTFail("Error: \(error.localizedDescription)") } else { if result == .success { promise.fulfill() } else { XCTFail("Job fail, result code: \(result.rawValue)") } } } waitForExpectations(timeout: 5, handler: nil) } Asynchronous Callback 1. expectation 2. wait for fulfill
  31. func testAsyncTask() { let promise = expectation(description: "Asnyc test complete")

    let fakeAsyncTaskManager = AsyncTaskManager() fakeAsyncTaskManager.doSomething { result, error in if let error = error { XCTFail("Error: \(error.localizedDescription)") } else { if result == .success { promise.fulfill() } else { XCTFail("Job fail, result code: \(result.rawValue)") } } } waitForExpectations(timeout: 5, handler: nil) } Asynchronous Callback 3. fulfill
  32. public typealias Codable = Decodable & Encodable Codable Protocol struct

    User: Codable { let name: String let userID: String let phoneNumber: Int private enum CodingKeys: String, CodingKey { case name = "name" case userID = "id" case phoneNumber } }
  33. Encode Decode open func encode<T>(_ value: T) throws -> Data

    where T : Encodable let user = User(name: "dada", userID: "12345", phoneNumber: 0955777888) let encoder = JSONEncoder() let data = try? encoder.encode(user) let decoder = JSONDecoder() let parsedUser = try? decoder.decode(User.self, from: data) open func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
  34. Unit Test - Wrap Up • Input, Module, Output •

    XCTest Framework • 3A Rule • Verifying Rule Part 1 • Stub, Mock • Dependency Injection • OCMock • Dummy Data • Asynchronous Test Part 2
  35. Let’s build an app to show playlists list. KKBOX Open

    API https://developer.kkbox.com/#/
  36. Capability query music libraries, playlists, charts, search … etc KKBOX

    Open API https://developer.kkbox.com/#/ Restful API SDKs support Python, Javascript, Android, Objective-C, Swift, .NET Framework, GO
  37. KKBOX Open API Swift SDK https://github.com/KKBOX/OpenAPI-Swift var API = KKBOXOpenAPI(clientID:

    clientID, secret: clientSecret) func doAPICallWithAccessToken(callback: @escaping (Error?) -> ()) { if let _ = self.API.accessToken { callback(nil) return } _ = try? self.API.fetchAccessTokenByClientCredential { result in switch result { case .error(let error): callback(error) case .success(_): break } } }
  38. language: swift osx_image: xcode9.2 script: - xcodebuild clean test -project

    TryTDD.xcodeproj -scheme TryTDDTests -destination 'platform=iOS Simulator,name=iPhone 8,OS=11.2’ -configuration Debug | xcpretty Create .travis.yaml
  39. language: swift osx_image: xcode9.2 script: - xcodebuild clean test -project

    TryTDD.xcodeproj -scheme TryTDDTests -destination 'platform=iOS Simulator,name=iPhone 8,OS=11.2’ -configuration Debug | xcpretty Create .travis.yaml