Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Nimble
Written by Tâm Kun on July 13, 2022

Nimble trong 10 phút

iOS & Swift

Contents

  • Chuẩn bị
  • Giới thiệu Nimble
  • Cài đặt thư viện
  • expect() – diễn tả kết quả mong đợi
  • Những phương thức thường sử dụng
    • Kiểm tra khác kiểu
    • Bất đồng bộ
    • So sánh giá trị
    • Kiểm tra kiểu
    • Kiểm tra tham chiếu
    • Xử lí lỗi
    • Custom validation
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Chủ đề bài viết lần này là về UnitTest trong iOS. Tuy nhiên, chúng ta sẽ quan tâm tới một thư viện được sử dụng khá nhiều, đó là Nimble. Nó là gì & hoạt động như thế nào, thì ta sẽ khám phá tiếp bên dưới nhóe.

Nếu mọi việc đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

Về mặt kiến thức, bạn cần hiểu qua các thuật ngữ cơ bản đầu tiền của iOS Testing trước nhóe. Nếu bạn chưa biết nó là gì, bạn có thể tham khảo bài viết sau:

  • Hello Testing iOS
  • UnitTest with Quick & Nimble
  • API Testing (UnitTest) with OHHTTPStubs

Về mặt công cụ, bạn chỉ cần có Xcode là xong nhóe. Bạn không cần quan tâm tới version Xcode, hay iOS … hầu như bạn sẽ sử dụng với các phiên bản mới nhất rồi.

Vì đã có một bài viết về cách viết UnitTest với Quick & Nimble rồi. Do đó, bài viết này chỉ tập trung những gì mà Nimble có và cách bạn sử dụng thư viện mà thôi.

Giới thiệu Nimble

Nimble là một framework giúp diễn tả kết quả mong đợi của testcase bằng cách sử dụng ngôn ngữ tự nhiên, dễ hiểu hơn so với framework XCTest mà Apple cung cấp.

Ví dụ với một dòng test bằng XCTest của Xcode.

// XCTest
XCTAssertEqual(1 + 1, 2, "One plus one should be equal to two")

Ta sẽ viết lại nó bằng Nimble như sau:

// Nimble
expect(1 + 1).to(equal(2), description: "One plus one should be equal to two")

Về cảm nhận đầu tiên, bạn sẽ thấy dễ hiểu test hơn khi được viết bằng Nimble. Các chú thích hay các tên tham số rất cụ thể.

Cài đặt thư viện

Chúng ta sẽ bắt đầu việc cài đặt thư viện trước nhóe. Vì bạn có thể vài đường Google để biết chúng nó là gì rồi. Và chúng ta sẽ không quan tâm quá nhiều về mặt lý thuyết dài dòng kia.

Để cài đặt Quick & Nimble, mình sử dụng CocoaPods. Cách này cũng khá phổ thông và nếu bạn chưa biết cách sử dụng CocoaPod thì có thể đọc vài viết này nhóe! (theo 2 cách)

  • Cài đặt CocoaPods
  • Sử dụng Bundle cho iOS Project

Tiếp theo, bạn cập nhật lại PodFile để thêm các pod cho Quick & Nimble. Xem đoạn code ở Podfile như sau nhóe!

# Podfile

use_frameworks!

target "MyApp" do
  # Normal libraries

  abstract_target 'Tests' do
    inherit! :search_paths
    target "MyAppTests"
    target "MyAppUITests"

    pod 'Quick'
    pod 'Nimble'
  end
end

Các bạn nhớ để Quick & Nimble ở trong target Tests nha. Cụ thể ở đây là “FinalProjectTests”. Nếu bỏ nhầm chổ khác, thì lúc import nó ở file test sẽ không nhận đâu.

expect() – diễn tả kết quả mong đợi

Keyword “expect” trong Nimble rất quan trọng. Nó diễn tả kết quả mong đợi bằng cách sử dụng các hàm cơ bản:

  • expect(<kết quả>).to(<mong đợi>)
  • expect(<kết quả>).toNot<mong đợi>)
  • expect(<kết quả>).notTo(<mong đợi>)

Ví dụ code nhóe!

import Nimble
...
expect(username).to(equal("A Nguyen V."))
expect(username).toNot(equal("A Nguyen V."))
expect(username).notTo(equal("A Nguyen V."))

Trong đó notTo và toNot giống nhau.

Bên cạnh đó, bạn còn có thể custom thông báo khi lỗi xảy ra:

  • expect(<kết quả>).to(<mong đợi>, description: <thông báo khi lỗi>)

Xem tiếp ví dụ code nhóe!

import Nimble
...
expect(username).to(equal("A Nguyen V."), description: "User name should be equal to `A Nguyen V.`")

Những phương thức thường sử dụng

Phần tiếp theo, mình sẽ liệt kê các phương thức expect() mà bạn thường sử dụng với Nimble.

Kiểm tra khác kiểu

Nimble không cho phép biên dịch khi ta expect kết quả với mong đợi khác kiểu dữ liệu. Ví dụ như sau:

// Does not compile:
expect(1 + 1).to(equal("Squee!"))

Trong đó:

  • Kết quả của expect là một kiểu Int
  • Mong đợi lại là một kiểu String

Đây cũng điều đầu tiên mà bạn cần lưu ý và cũng có thể lợi dụng để test. Biết đâu được nhĩ!

Bất đồng bộ

Về bất đồng bộ, bạn sẽ sử dụng nó khá nhiều. Nhất là trong các việc test với server hay các API. Chúng ta sẽ có các function liên quan như sau:

  • expect(<kết quả>).toEventually(<mong đợi>, description: <thông báo khi lỗi>)
  • expect(<kết quả>). toEventuallyNot(<mong đợi>, description: <thông báo khi lỗi>)
  • waitUntil { done in <thực thi expect> done()}
  • waitUntil(timeOut: <giá trị>) {done in <thực thi expect> done()}

Giải thích đơn giản:

  • toEventually giúp bạn có thể dự đoán một thứ gì đó “trong tương lai”.
  • waitUntil thì chờ đợi kết quả và so sánh với mong đợi

Ví dụ code nhóe!

  • toEventually
var fishes: [String] = []
api.request { fish in
    fishes.append(fish) // value is whales
}
expect(fishes).toEventually(contain("whales"))
var fishes: [String] = []
api.request { fish in // value is whales
    fishes.append(fish)
}
expect(fishes).toEventually(contain("whales"), timeout: 3)
  • waitUntil
// Call done() to finish expect() function, after timeout if done() is not executed, expect() func is fail
waitUntil(timeout: 3.0) { done in
    api.request { fish in
        expect(fish) == "whales"
        done()
    }
}

Chú ý done() & giá trị timeout mặc định sẽ là 1.0 nhóe!

So sánh giá trị

Những giá trị được so sánh phải extension từ Equatable, Comparable, hoặc là các lớp con của NSObject. Ta sẽ có những cách so sánh sau với việc sử dụng 2 kiểu viết code (dùng method & dùng toán tử).

// Equal
expect(actual).to(equal(expected))
// Or
expect(actual) == expected

// Less than
expect(actual).to(beLessThan(expected))
// Or
expect(actual) < expected

// Less than or equal
expect(actual).to(beLessThanOrEqualTo(expected))
// Or
expect(actual) <= expected

// Greater than
expect(actual).to(beGreaterThan(expected))
// Or
expect(actual) > expected

// Greater than or equal
expect(actual).to(beGreaterThanOrEqualTo(expected))
// Or
expect(actual) >= expected

Kiểm tra kiểu

  • beAKindOf(aClass): Passes khi kết quả cần kiểm tra có kiểu là aClass, hoặc khi nó là một thể hiện của aClass hay là bất kỳ lớp con nào từ aClass.
  • beAKindOf(aProtocol): Passes khi kết quả cần kiểm tra là một thể hiện thoả mãn aProtocol.
protocol SomeProtocol {}
class SomeClass {}
class TestClass: SomeClass {}
extension TestClass: SomeProtocol {}

// The following tests passes
expect(1).to(beAKindOf(Int.self))
expect("turtle").to(beAKindOf(String.self))

let testClass = TestClass()
expect(testClass).to(beAKindOf(TestClass.self))
expect(testClass).to(beAKindOf(SomeClass.self))
expect(testClass).to(beAKindOf(SomeProtocol.self))
  • beAnInstanceOf(aClass): Passes khi là thể hiện chính xác của aClass.
protocol SomeProtocol {}
class SomeClass {}
class TestClass: SomeClass {}
extension TestClass: SomeProtocol {}

// The following tests passes
expect(1).to(beAnInstanceOf(Int.self))
expect("turtle").to(beAnInstanceOf(String.self))

let testClass = TestClass()
expect(testClass).to(beAnInstanceOf(TestClass.self))
expect(testClass).toNot(beAnInstanceOf(SomeClass.self))
expect(testClass).toNot(beAnInstanceOf(SomeProtocol.self))

Và bạn có thể dùng phương thức này để kiểm tra các kiểu dữ liệu của các CellViewModel con trong một ViewModel lớn.

Kiểm tra tham chiếu

Cũng ít khi sử dụng tới, nhưng mà cũng vì đam mê nên bỏ vào thôi.

// Passes if 'actual' has the same pointer address as 'expected':
expect(actual).to(beIdenticalTo(expected))
expect(actual) === expected

// Passes if 'actual' does not have the same pointer address as 'expected':
expect(actual).toNot(beIdenticalTo(expected))
expect(actual) !== expected

Xử lí lỗi

Các function có xử lý lỗi thì đặc trưng sẽ là try catch & throw. Nên khi viết UnitTest thì ta cũng phải viết luôn việc xử lý các phần đó. Ta có ví dụ với 1 function như sau:

enum SomethingError: Error {
    case less
    case greater
}

func checkZeroNumber(_ number: Int) throws -> Int {
    if number < 0 {
        throw SomethingError.less
    }
    if number > 0 {
        throw SomethingError.greater
    }
    return number
}

Bạn sẽ sử dụng expect tới try & có thể kèm với closure để xử lý throw. Xem tiếp ví dụ nhóe!

// Passes if 'checkZeroNumber(:_)' throws an 'Error'
expect { try checkZeroNumber(-1)}.to(throwError())
expect { try checkZeroNumber(1)}.to(throwError())

// Passes if 'checkZeroNumber(:_)' is an expected value
expect { try checkZeroNumber(0)}.to(equal(0))

// Passes if 'checkZeroNumber(:_)' throws an 'less' error
expect { try checkZeroNumber(-1)}.to(throwError(SomethingError.less))

// Passes if 'checkZeroNumber(:_)' throws an 'greater' error
expect { try checkZeroNumber(1)}.to(throwError(SomethingError.greater))

// Passes if 'checkZeroNumber(:_)' throws an error type
expect { try checkZeroNumber(-1)}.to(throwError(errorType: SomethingError.self))

// Expected error by closure
expect { try checkZeroNumber(-1)}.to(throwError { error in
    expect(error) == SomethingError.less
})

Custom validation

Tiếp theo, với expect bạn có thể tùy chỉnh các mong đợi tương ứng với các kiểu Result. Ví dụ nhóe

enum Result {
    case success
    case failure(String)
}

// passes if .succeeded is returned from the closure
let actual: Result = .success
expect({
    guard case Result.success = actual else {
        return .failed(reason: "Wrong enum case")
    }
    return .succeeded
}).to(succeed())

// passes if .succeeded is returned from the closure
let actual2: Result = .failure("a")
expect({
    guard case Result.failure("a") = actual2 else {
        return .failed(reason: "Wrong enum case")
    }
    return .succeeded
}).to(succeed())

// passes if .failed is returned from the closure
let actual3: Result = .failure("a")
expect({
    guard case Result.failure("b") = actual3 else {
        return .failed(reason: "Wrong enum case")
    }
    return .succeeded
}).notTo(succeed())

Trong đó:

  • Tại expect, bạn sẽ cung cấp 1 closure để kiểm tra các điều kiện
  • Bạn sẽ return về các case phù hợp
  • Cuối cùng, bạn sẽ so sánh với mong đời .to(succeed())

Để có thể đơn giản hóa đi việc test như trên. Bạn sẽ thêm các extension của Result với các protocol có thể so sánh được, như là: Equatable & Comparable. Ví dụ:

enum Result {
    case success
    case failure(String)
}

extension Result: Equatable {

    public static func == (lhs: Result, rhs: Result) -> Bool {
        switch (lhs, rhs) {
        case (.success, .success):
            return true
        case let (.failure(lhsValue), .failure(rhsValue)):
            return lhsValue == rhsValue
        default:
            return false
        }
    }
}

let actual: Result = .success
let actual2: Result = .faillure("a")

// Equal
expect(actual) == Result.success
expect(actual).to(equal(Result.success))

// Equal
expect(actual2) == Result.failure("a")
expect(actual2).to(equal(Result.failure("a")))

Tạm kết

  • Tìm hiểu về thư viện Nimble
  • Diễn tả kết quả mong đợi với expect
  • Những phương thức thường sử dụng trong các trường hợp cần test.

 

Okay! Tới đây, mình xin kết thúc bài viết về Nimble trong UnitTest . Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

  • Bài viết tiếp theo tại đây.

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares14

Related Posts:

  • Generics trong 10 phút - Swift
    Generics trong 10 phút - Swift
  • Race Condition và giải pháp trong 10 phút - Swift
    Race Condition và giải pháp trong 10 phút - Swift
  • Sendable Protocol & @Sendable trong 10 phút - Swift 5.5
    Sendable Protocol & @Sendable trong 10 phút - Swift 5.5
  • Text View trong 10 phút - SwiftUI Notes #25
    Text View trong 10 phút - SwiftUI Notes #25
Tags: iOS, testing, unittest
Written by Tâm Kun

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • UnitTest with Quick & Nimble
    UnitTest with Quick & Nimble
  • Cơ bản về async/await trong 10 phút - Swift 5.5
    Cơ bản về async/await trong 10 phút - Swift 5.5
  • Image View trong 10 phút - SwiftUI Notes #26
    Image View trong 10 phút - SwiftUI Notes #26
  • Property Wrapper trong 10 phút
    Property Wrapper trong 10 phút
  • Cơ bản về Actor trong 10 phút - Swift 5.5
    Cơ bản về Actor trong 10 phút - Swift 5.5

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!