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
Quick Nimble
Written by Tâm Kun on June 26, 2022

UnitTest with Quick & Nimble

iOS & Swift

Contents

  • Chuẩn bị
  • Quick & Nimble là gì?
    • Cài đặt thư viện
    • Quick
      • Khái niệm
      • Một số cú pháp có trong Quick
      • Tóm tắt
    • Nimble
      • Khái niệm
      • Hỗ trợ kiểm thử trong quá trình đồng bộ
      • Hỗ trợ kiểm thử trong quá trình bất đồng bộ
  • Example
    • Step 1 : Import
    • Step 2: describle
    • Step 3: context
    • Step 4: beforeEach
    • Step 5: test case
  • Tạm kết

Chào mừng bạn đến với Fx Studio. Chúng ta đã khám phá UnitTest hay Testing trong bài viết trước là gì rồi. Thì chủ đề lần này sẽ là việc thực hiện các UnitTest. Tuy nhiên, thay vì sử dụng các tools mặc định của Xcode, thì ta sẽ sử dụng 2 thư viện đình đám là Quick & Nimble. Nó là gì & hoạt động ra sao? … chúng ta cùng nhau tìm hiểu nhóe!

Tác giả bài viết là bạn Tâm Kun. Mọi người có thể theo dõi thêm các bài viết từ GitHub chính chủ của bạn 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

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ề mặt demo, Source code các ví dụ trong seri này các bạn có thể tham khảo ở đây. Trong đó có 3 file test chính:

  • TutorialViewModelTest: Basic test.
  • HomeViewModelTest: Test with Nimble/Quick.
  • DetailViewModelTest: Test with OHHTTPStubs liên quan đến case server.

Quick & Nimble là gì?

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 hình sau nhóe!

Quick Nimble

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.

Quick

Khái niệm

Ta sẽ bắt đầu tìm hiểu khái niệm đầu tiên. Đó là Quick.

Quick là một testing framework, là nơi cung cấp những methods thuận tiện cho công việc viết test.

Nó sử dụng function spec() để định nghĩa toàn bộ code test. Function spec() hỗ trợ cho việc chia nhiều sections.

Ví dụ: Ở ViewModel sẽ có những hàm xử lí logic và hàm xử lí api, thì ta sẽ chia nó thành 2 section khác nhau để cho nó clear và những người khác lúc vào maintain test cũng có thể dễ dàng tìm được.

Một số cú pháp có trong Quick

Ta có 3 cú pháp quan trọng, đó là it, context & describle.

  • it: được dùng để định nghĩa kết quả kì vọng cụ thể nhất.

Hiểu nôm na sử dụng it để test một case cụ thể nào đó trong một source code. Có bao nhiêu case thì có bấy nhiêu it.

enum Gender {
    case male
    case female
}

func identify(gender: Gender) -> String {
    switch gender {
    case .male { return "have bird" }
    case .female { return "dont have bird :("}
    }
}

Như bạn thấy hàm này nó trả về kiểu string và có 2 case để nó trả về giá trị khác nhau. Nên khi test sử dụng 2 câu lệnh it để có thể “bao phủ” cả hàm trên.

 

  • context: được đùng để định nghĩa các “specific context” của một tác vụ nào đó mà bạn phải test.

Các case khác nhau tạo ra một hàm xử lý, từ đó suy ra được nhiều “it” nó tạo ra một context.

Như ví dụ trên: 2 cái “it” tạo ra một context. Context này để giúp xác định giới tính của một người nào đó trong lớp học.

Tóm lại trong code, những case nào mà cùng liên quan đến một tác vụ, một chức năng nào đó thì gom lại thành 1 context.

  • describle: được dùng để định nghĩa nhưng tác vụ lớn hoặc hành vi mà bạn phải test

Cũng tương tự như vậy, nhiều context sẽ tạo ra describle.

Cũng với ví dụ trên, trong một lớp học cần xác định giới tính (context) , xác định học lực (context), kiểm tra hành kiểm (context)… Nhiều cái thì chúng ta gom chúng vào một describle.

Vậy buộc phải dùng tất cả hả ? Trả lời : Không.

Vậy dùng toàn “it” được không? Trả lời: Được.Nhưng…

Ví dụ:

    it("Test case rank bad") {
        expect(viewModel.rankStudent(point: 3)) == .bad
        expect(viewModel.rankStudent(point: 3)).to(equal(.bad))
     }
    it("Test case gender male") {
        expect(viewModel.identify(gender: .male)) == "have bird"
    }
    it("Test case rank middle") {
        expect(viewModel.rankStudent(point: 6)) == .middle
        expect(viewModel.rankStudent(point: 6)).toNot(equal(.bad))
    }
    it("Test case rank good") {
        expect(viewModel.rankStudent(point: 8.2)) == .good
        expect(viewModel.rankStudent(point: 8.2)).toNot(equal(.middle))
    }
    it("Test case rank verygood") {
        expect(viewModel.rankStudent(point: 8.6)) == .verygood
        expect(viewModel.rankStudent(point: 8.2)).toNot(equal(.middle))
    }
    it("Test case gender female") {
        expect(viewModel.identify(gender: .female)) == "dont have bird :("
    }
    it("Test case rank error") {
        expect(viewModel.rankStudent(point: 11)) == .error
    }
context("Test rank") {
    it("Test case rank bad") {
        expect(viewModel.rankStudent(point: 3)) == .bad
        expect(viewModel.rankStudent(point: 3)).to(equal(.bad))
    }
    it("Test case rank middle") {
        expect(viewModel.rankStudent(point: 6)) == .middle
        expect(viewModel.rankStudent(point: 6)).toNot(equal(.bad))
    }
    it("Test case rank good") {
        expect(viewModel.rankStudent(point: 8.2)) == .good
        expect(viewModel.rankStudent(point: 8.2)).toNot(equal(.middle))
    }
    it("Test case rank verygood") {
        expect(viewModel.rankStudent(point: 8.6)) == .verygood
        expect(viewModel.rankStudent(point: 8.2)).toNot(equal(.middle))
    }
    it("Test case rank error") {
        expect(viewModel.rankStudent(point: 11)) == .error
    }
}
context("Test gender") {
    it("Test case gender male") {
        expect(viewModel.identify(gender: .male)) == "have bird"
    }
    it("Test case gender female") {
        expect(viewModel.identify(gender: .female)) == "dont have bird :("
    }
}

Tóm tắt

Qua 2 cách viết trên ta thấy được cách nào cũng mang lại kết quả tối ưu là test pass.

Nhưng khi người sau khi vào maintain hay lỡ có bị fail,  thì dev trố mắt nhìn để tìm kiếm đến đến case nào bị lỗi, function nào bị fail và nhìn vào chả có cảm tình gì cả.

Theo bản thân mình nhận thấy, mô hình MVC, MVVM hay bất kì mô hình gì, thì chúng ta cũng chia code ra để quản lý và maintain dễ hơn mà thôi. Thì viết test case cũng vậy, viết sao cho người sau vào đọc nói “dễ chịu”, cấu trúc rõ ràng. Không phải test case function này rồi thích nhảy qua test case của function kia.

Tâm-Kun yêu cái đẹp!!!

  • beforeEach: tương tự như setup vậy, chúng ta chuẩn bị dữ liệu test ở beforeEach.
  • afterEach: context nó chạy xong thì nó sẽ vào afterEach để config dữ liệu lại theo như mong muốn.

Nimble

Khái niệm

Tiếp theo, ta sẽ tìm hiểu khái niệm Nimble nhóe!

Nimble cũng là một framework cung cấp rất nhiều các options để giúp thoả mãn được các “kì vọng” test.

Keyword “expect” trong Nimble rất quan trọng. Nó thay thế cho XCTAssertion của hàng chính hãng XCTest.

Expect là “kì vọng”.

Có nghĩa là chúng ta kì vọng trường hợp đó output ra như ta mong đợi. Thư viện Nimble hỗ trợ ta rất nhiều để có thể “expect” được những giá trị, kiểu dữ liệu hay so sánh 2 đối tượng nào đó ở nhiều trường hợp. Rất tiện lợi đúng không nào!

Chúng ta tiếp tục xem Nimble hỗ trợ cho việc test về những gì nhé!

Hỗ trợ kiểm thử trong quá trình đồng bộ

expect(1 + 1).to(equal(2)) // so sánh bằng
expect(1.2).to(beCloseTo(1.1, within: 0.1)) // xấp xỉ trong giới hạn là bao nhiêu
expect(3) > 2 // so sánh hơn, kém
expect("seahorse").to(contain("sea")) // kiểm thử có chứa phần tử hay không?
expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi")) // kiểm thử không chứa phần tử hay không?
expect(1 + 1).to(equal(3), description: "Make sure 1+1 = 2")
// Bạn muốn add thêm thông tin đến khi test case đó bị sai thì thêm argument description.

Xem qua đoạn code ví dụ trên, mình đã liệt kê ra kha khá các function mà bạn có thể dùng hoặc sẽ sử dụng khá nhiều sau này nhóe. Xoay quanh 2 kiểu chính:

  • to
  • toNot

Còn nội dung ở trong như thế nào, thì bạn sẽ theo logic mà bạn muốn test nhóe!

Hỗ trợ kiểm thử trong quá trình bất đồng bộ

Nimble cung cấp ta 2 cách để nhận biết rằng code chúng ta đang chạy là bất đồng bộ. Đó là toEventually và waitUntil.

  • toEventually: Có một điều mình rất thích ở Nimble nói chung và toEventually nói riêng là nó cho phép ta viết kì vọng test nhưng đang đọc tiếng anh.

“Expect value to eventually be this“.

Giá trị mong đợi cuối cùng là …

Có nghĩa là toEventually giúp bạn có thể dự đoán một thứ gì đó “trong tương lai”.

DispatchQueue.main.async {
    ocean.add("dolphins")
    ocean.add("whales")
}
expect(ocean).toEventually(contain("dolphins", "whales"))

Ví dụ trên có ý nghĩa như sau:

  • Đối tượng ocean nó sẽ được đánh giá liên tục.
  • Nếu nó đã từng chứa dolphins và whales, thì kì vọng sẽ pass.
  • Ngược lại, nếu nó không chứa trong bất kì khoảng thời điểm nào bất chấp ocean được đánh giá liên tục, thì kì vọng của chúng ta sẽ fail.

Tiếp theo với,

  • waitUntil: nó là một function được cung cấp bởi Nimble và hỗ trợ cho quá trình test bất đồng bộ.

Phần này, mình chỉ nói sơ qua để mọi người hiểu một phần nào đó. Bài tiếp theo, test bất đồng bộ liên quan đến server sẽ được nói rõ hơn.

Nimble lo tất cả & chúng ta chỉ cần dùng mà thôi 😀

Các bạn muốn biết nhiều hơn về cách dùng thì vào đây xem nhé. Thôi lí thuyết rứa đủ rồi, qua làm cái ví dụ là hiểu liền.

Example

Giới thiệu qua ViewModel chúng ta cần test một chút. Nó sẽ lấy data từ api về và fill data vào cái tableView, nên sẽ có 2 tác vụ chính.

  1. Các hàm xử lí api.
  2. Các hàm xử lí logic, cung cấp dữ liệu đổ vào tableView.

Step 1 : Import

import Nimble
import Quick

@testable import FinalProject

class HomeViewModelTest: QuickSpec { }

Đầu tiên, tất nhiên là phải import thư viện cần dùng. Chú ý tiếp theo, các bạn sẽ thấy được cú pháp @testable import, thì nó để làm gì?

@testable import giúp chúng ta khai báo thêm, tức là khi dùng nó ta sẽ add được file khác target vào target test, để phục vụ cho việc test.

Tiếp theo, đảm bảo class test phải kế thừa “QuickSpec”. Việc kết thừa QuickSpec, chúng ta có thể override function spec(), thì công dụng của nó thì như ta đã nói phía trên.

Cuối cùng là khởi tạo một instance để dùng và bỏ ở trong hàm spec().

var viewModel: HomeViewModel!

Step 2: describle

Ta sử dụng describle” để mô tả mục đích lớn nhất của ta trong việc test ViewModel này là gì?

Ở đây, ta đang test các chức năng của màn hình Home nên ta có thể viết như sau:

describe("Test funcs homeScreen") {...}

Và nếu file hay class của bạn muốn test có quá nhiều logic trong đó, thì hãy cứ thêm các describle vào nhóe. Mục tiêu tối thượng là: người khác đọc hiểu một cách nhanh chóng!

Step 3: context

Trong describle thì sẽ có nhiều context.

Như chúng ta đã phân tích, thì có 2 tác vụ chính thì ta sẽ có 2 “context“.

context("Test some funcs related to tableView") { ... }

context("Test func related to api") { 
  // Phần này chúng ta phân tích kĩ hơn ở bài sau
}

Tạm thời, bạn sẽ sử dụng 2 context như trên để test TableView. Chúng ta sẽ triển khai dần dần thêm các cấu hình phục vụ việc test nhóe.

Step 4: beforeEach

Trong cái context đầu tiên, chúng ta sẽ bắt đầu test các function liên quan đến TableView. Sử dụng beforeEach, để chuẩn bị dữ liệu test.

beforeEach {
    viewModel = HomeViewModel()
    viewModel.musics = DummyData.dummyMusics
}

Hiện tại, chúng ta chưa có dữ liệu, nên sẽ fake để test. Ta sẽ viết extension để tạo ra một DummyData.

extension HomeViewModelTest {
    
    struct DummyData {
        static var dummyMusics: [Music] {
            var items: [Music] = []
            // dummy
            let item1 = Music()
            item1.name = "tam"
            let item2 = Music()
            item2.name = "tien"
            items.append(item1)
            items.append(item2)
            return items
        }
    }
}

Step 5: test case

Bây giờ, đây mới chính là công việc chính của chúng ta. Bắt đầu test từng case nào!

Số lượng section trong một TableView.

  • HomeViewModel
func numberOfSections() -> Int {
    return 1
}
  • HomeViewModelTest
it("Test func numberOfSection") {
    expect(viewModel.numberOfSections()) == 1
    // cach khac
    expect(viewModel.numberOfSections()).to(equal(1))
}

 

Số lượng items trong một section.

  • HomeViewModel
func numberOfItems(inSection section: Int) -> Int {
      musics.count
}
  • HomeViewModelTest
it("Test func numberOfItem in section 0") {
    expect(viewModel.numberOfItems(inSection: 0)) == 2
}

Dừng lại khoảng chừng 2 giây và nghĩ một chút nhóe!

Chúng ta dummyData với cái mảng musics có 2 items nên chúng ta “expect” là 2.

Vậy nếu chúng ta expect là 1 thì điều gì xảy ra?

Quick Nimble

Nó sẽ test fail case này và còn gợi ý kết quả “expect” đúng.

Tiếp tục, việc test từng item cho cell

  • HomeViewModel
func viewModelForItem(at indexPath: IndexPath) -> HomeCellViewModel {
    return HomeCellViewModel(item: musics[indexPath.row], index: indexPath.row)
}
  • HomeViewModelTest
it("Test func viewModelForItem with row = 0, section = 0") {
    expect(viewModel.viewModelForItem(at: IndexPath(row: 0, section: 0))).to(beAnInstanceOf(HomeCellViewModel.self))
    
    // cach khac
    expect(viewModel.viewModelForItem(at: IndexPath(row: 0, section: 0)).item?.name) == "tam"
}

Sau khi “run” test, thì ta được kết quả như sau:

Okay! bạn đã đi dạo 1 vòng viết UnitTest với Quick & Nimble rồi đây. Và đây là toàn bộ phần test cho các bạn có cái nhìn tổng quan hơn.

override func spec() {
    var viewModel: HomeViewModel!
    
    describe("Test funcs homeScreen") {
        context("Test some funcs related to tableView") {
            beforeEach {
                viewModel = HomeViewModel()
                viewModel.musics = DummyData.dummyMusics
            }
            it("Test func numberOfSection") {
                expect(viewModel.numberOfSections()) == 1
                // cach khac
                expect(viewModel.numberOfSections()).to(equal(1))
            }
            it("Test func numberOfItem in section 0") {
                expect(viewModel.numberOfItems(inSection: 0)) == 2
            }
            it("Test func viewModelForItem with row = 0, section = 0") {
                expect(viewModel.viewModelForItem(at: IndexPath(row: 0, section: 0))).to(beAnInstanceOf(HomeCellViewModel.self))
                
                // cach khac
                expect(viewModel.viewModelForItem(at: IndexPath(row: 0, section: 0)).item?.name) == "tam"
            }
            afterEach {
                viewModel = nil
            }
        }
        context("Test func related to api") {
            
        }
    }
}

Qua trên, là một số ví dụ cơ bản khi sử dụng “expect“. Thư viện Nimble hổ trợ ta rất nhiều để giúp ta có thể đạt được mục đích test.

Cứ làm nhiều vào, khó có stackoverflow. Ahihi!

Tạm kết

  • Tìm hiểu Quick & Nimble.
  • Các thành phần cơ bản sử dụng trong test.
  • Giới thiệu các bạn cách test cơ bản sử dụng thư viện Quick & Nimble.
  • Đưa ra một ví dụ cơ bản để sử dụng.

Thực sự khi làm dự án thật, thì một ViewModel có rất nhiều functions cần được test. Và một app hoàn chỉnh, thì lại có rất nhiều ViewModel nữa.

Đối với một newbie hoặc thế thệ devs cũ … thì đây là một điều kinh khủng. Nó ăn mòn vào lối suy nghĩ của từng thế hệ.

Bài viết tiếp theo, mình sẽ hướng dẫn các bạn viết test đối với những case có liên quan đến api.

 

Okay! Tới đây, mình xin kết thúc bài viết về Quick & 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ạn có thể checkout source code tại đây.
  • Bài viết tiếp theo tại đây.

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

FacebookTweetPinYummlyLinkedInPrintEmailShares68

Related Posts:

  • API Testing (UnitTest) with OHHTTPStubs
    API Testing (UnitTest) with OHHTTPStubs
  • Nimble trong 10 phút
    Nimble trong 10 phút
  • Quick trong 10 phút
    Quick trong 10 phút
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:

  • Quick trong 10 phút
    Quick trong 10 phút
  • API Testing (UnitTest) with OHHTTPStubs
    API Testing (UnitTest) with OHHTTPStubs
  • Nimble trong 10 phút
    Nimble trong 10 phút

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!