Kiểm thử Unit và UI trong iOS với XCTest và XCUITest: Đảm Bảo Chất Lượng Ứng Dụng của Bạn

Xin chào các bạn đang đồng hành cùng chúng tôi trên Lộ trình học Lập trình viên iOS 2025!

Sau khi đã trang bị cho mình những kiến thức nền tảng về ngôn ngữ Swift, hiểu về OOP và Lập trình Hàm, quản lý bộ nhớ, làm quen với UIKit hay SwiftUI, và cách xử lý lỗi, đến lúc chúng ta cần nói về một khía cạnh cực kỳ quan trọng để đảm bảo chất lượng cho những gì chúng ta xây dựng: Kiểm thử (Testing). Phát triển ứng dụng không chỉ là viết code cho nó hoạt động, mà còn là viết code một cách đáng tin cậy.

Trong hệ sinh thái Apple, XCTest là framework mặc định để viết các bài kiểm thử cho ứng dụng và framework của bạn. XCTest cung cấp nền tảng cho cả kiểm thử đơn vị (Unit Testing) và kiểm thử giao diện người dùng (UI Testing). Bài viết này sẽ đưa bạn đi sâu vào hai loại kiểm thử này, cách thiết lập và viết chúng trong Xcode.

Tại sao Kiểm thử Lại Quan Trọng Đến Thế?

Bạn có thể tự hỏi: “Tại sao phải dành thời gian viết thêm code kiểm thử khi ứng dụng của tôi đã chạy được?”. Đó là một câu hỏi phổ biến, đặc biệt với các lập trình viên mới. Tuy nhiên, bỏ qua kiểm thử là bỏ qua một bước cực kỳ quan trọng để trở thành một lập trình viên chuyên nghiệp và tạo ra các sản phẩm chất lượng.

  • Bắt lỗi sớm: Kiểm thử giúp phát hiện lỗi ngay trong quá trình phát triển thay vì chờ đến khi người dùng cuối tìm ra. Sửa lỗi sớm luôn dễ dàng và rẻ hơn sửa lỗi muộn.
  • Tự tin khi Refactor: Khi code base lớn dần, việc thay đổi hoặc tối ưu hóa (refactoring) là không thể tránh khỏi. Các bài kiểm thử đáng tin cậy giúp bạn tự tin thực hiện những thay đổi này mà không lo phá vỡ chức năng hiện có. Điều này liên quan đến việc bạn xây dựng code với các kiến trúc dễ kiểm thử hơn.
  • Tài liệu sống: Các bài kiểm thử (đặc biệt là unit test) có thể đóng vai trò như tài liệu, minh họa cách sử dụng hoặc hành vi mong đợi của một đoạn code hoặc một lớp.
  • Cải thiện thiết kế code: Việc viết code có thể kiểm thử được thường đòi hỏi code phải được tách nhỏ, tuân thủ nguyên tắc đóng gói, và có sự phụ thuộc rõ ràng. Điều này tự động dẫn đến thiết kế code tốt hơn.
  • Hỗ trợ Tích hợp Liên tục (CI): Kiểm thử tự động là nền tảng của CI/CD. Việc chạy kiểm thử sau mỗi lần commit giúp đảm bảo rằng các thay đổi mới không gây ra lỗi hồi quy (regression).

Nói tóm lại, kiểm thử không phải là gánh nặng mà là khoản đầu tư giúp bạn tiết kiệm thời gian và công sức về lâu dài, đồng thời nâng cao chất lượng và tính ổn định của ứng dụng.

XCTest: Nền tảng Kiểm thử trong Xcode

XCTest là framework được Apple cung cấp để viết kiểm thử cho ứng dụng iOS, macOS, watchOS và tvOS. Nó là nền tảng cho cả Unit Testing và UI Testing.

Thêm Test Target trong Xcode

Khi tạo một dự án iOS mới trong Xcode, bạn thường có tùy chọn tích hợp sẵn các test target. Nếu bỏ qua bước này hoặc muốn thêm test target vào dự án có sẵn, bạn có thể làm như sau:

  1. Chọn File > New > Target…
  2. Trong template chooser, chọn tab “iOS” (hoặc nền tảng tương ứng).
  3. Chọn “Unit Testing Bundle” cho Unit Test hoặc “UI Testing Bundle” cho UI Test.
  4. Nhấn Next, đặt tên cho target (ví dụ: `YourAppNameTests` cho Unit Test và `YourAppNameUITests` cho UI Test), chọn target ứng dụng tương ứng để kiểm thử, và nhấn Finish.

Xcode sẽ tạo một thư mục mới trong dự án của bạn chứa file test bundle và một class test mẫu.

Kiểm thử Đơn vị (Unit Testing) với XCTest

Unit Testing tập trung vào việc kiểm thử các đơn vị code nhỏ nhất và độc lập nhất của bạn – thường là một hàm, một phương thức, hoặc một lớp/struct cụ thể. Mục tiêu là xác minh rằng mỗi “đơn vị” code hoạt động đúng theo mong đợi trong cô lập.

Anatomy của một Unit Test Class

Khi bạn thêm một Unit Testing Bundle, Xcode tạo một file với nội dung tương tự:

import XCTest
@testable import YourAppName // Import module ứng dụng của bạn

final class YourAppNameTests: XCTestCase {

    // Được gọi trước mỗi test method
    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    // Được gọi sau mỗi test method
    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    // Test method
    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    // Test hiệu năng
    func testPerformanceExample() throws {
        // This is an example of a performance test case.
        measure {
            // Put the code you want to measure the time of here.
        }
    }
}
  • import XCTest: Bắt buộc để sử dụng framework XCTest.
  • @testable import YourAppName: Cho phép truy cập (internal access) vào các thành phần của module ứng dụng mà không cần khai báo public.
  • Class phải kế thừa từ XCTestCase.
  • Các phương thức bắt đầu bằng tiền tố test (ví dụ: testLoginSuccess, testEmptyInputValidation) sẽ được Xcode nhận diện và chạy như một bài kiểm thử riêng biệt.
  • setUpWithError(): Chạy trước *mỗi* test method. Thường dùng để khởi tạo các đối tượng hoặc thiết lập môi trường cần thiết cho các bài kiểm thử trong class này.
  • tearDownWithError(): Chạy sau *mỗi* test method. Thường dùng để dọn dẹp (ví dụ: giải phóng tài nguyên, reset trạng thái).

Viết Bài Kiểm thử Đơn giản với XCTAssert

Trái tim của Unit Testing là các hàm XCTAssert. Chúng được sử dụng để kiểm tra một điều kiện và ghi nhận kết quả kiểm thử (pass/fail). Nếu một XCTAssert thất bại, bài kiểm thử sẽ dừng lại và được đánh dấu là failed.

import XCTest
@testable import YourAppName

// Giả sử bạn có một struct đơn giản
struct Calculator {
    func add(_ a: Int, _ b: Int) -> Int {
        return a + b
    }

    func divide(_ a: Int, _ b: Int) -> Double? {
        guard b != 0 else { return nil }
        return Double(a) / Double(b)
    }
}

final class CalculatorTests: XCTestCase {

    var calculator: Calculator! // Khai báo instance

    override func setUpWithError() throws {
        // Khởi tạo trước mỗi test
        calculator = Calculator()
    }

    override func tearDownWithError() throws {
        // Dọn dẹp sau mỗi test
        calculator = nil
    }

    func testAdd_returnsCorrectSum() {
        let result = calculator.add(2, 3)
        // Kiểm tra xem kết quả phép cộng có bằng 5 không
        XCTAssertEqual(result, 5, "Hàm add() trả về kết quả sai")
    }

    func testDivide_byZero_returnsNil() {
        let result = calculator.divide(10, 0)
        // Kiểm tra xem phép chia cho 0 có trả về nil không
        XCTAssertNil(result, "Chia cho 0 lẽ ra phải trả về nil")
    }

    func testDivide_validDivision_returnsCorrectResult() {
        let result = calculator.divide(10, 2)
        // Kiểm tra kết quả phép chia hợp lệ
        XCTAssertEqual(result, 5.0, accuracy: 0.001, "Hàm divide() trả về kết quả sai cho phép chia hợp lệ")
    }

    func testBooleanCondition() {
        let isTrue = true
        XCTAssertTrue(isTrue, "Điều kiện lẽ ra phải là true")
    }

    func testErrorHandling() throws {
        enum MyError: Error {
            case somethingWentWrong
        }
        
        func potentiallyFailingFunction() throws {
            throw MyError.somethingWentWrong
        }
        
        // Kiểm tra xem hàm có throw ra lỗi mong đợi không
        XCTAssertThrowsError(try potentiallyFailingFunction()) { error in
            XCTAssertEqual(error as? MyError, MyError.somethingWentWrong, "Lỗi throw ra không đúng loại")
        }
         
        func nonFailingFunction() throws -> Int {
             return 1
        }
        
        // Kiểm tra xem hàm không throw lỗi
        XCTAssertNoThrow(try nonFailingFunction(), "Hàm này không được throw lỗi")
    }
}

Các hàm XCTAssert phổ biến:

  • XCTAssert(expression, message): Kiểm tra một biểu thức boolean.
  • XCTAssertTrue(expression, message): Kiểm tra biểu thức là true.
  • XCTAssertFalse(expression, message): Kiểm tra biểu thức là false.
  • XCTAssertEqual(expression1, expression2, message): Kiểm tra hai biểu thức có bằng nhau không. (Có nhiều overload cho các kiểu dữ liệu khác nhau, bao gồm cả optional và kiểu leeg điểm động với độ chính xác).
  • XCTAssertNil(expression, message): Kiểm tra biểu thức có là nil không.
  • XCTAssertNotNil(expression, message): Kiểm tra biểu thức không là nil.
  • XCTAssertThrowsError(expression, message): Kiểm tra xem một biểu thức (dạng try expression) có throw lỗi không. (Có overload để kiểm tra loại lỗi cụ thể). Liên quan đến bài viết về Xử lý Lỗi.
  • XCTAssertNoThrow(expression, message): Kiểm tra xem một biểu thức có throw lỗi không.

Kiểm thử Code Bất đồng bộ (Asynchronous)

Ứng dụng iOS thường xuyên làm việc với các tác vụ bất đồng bộ như gọi API, tải dữ liệu, hay xử lý trên background queue (Đa luồng trong Swift). Kiểm thử code bất đồng bộ đòi hỏi một kỹ thuật khác vì test method sẽ kết thúc trước khi tác vụ bất đồng bộ hoàn thành.

XCTest cung cấp XCTestExpectation để chờ đợi một điều kiện bất đồng bộ xảy ra. Bạn tạo một hoặc nhiều expectation, thực hiện tác vụ bất đồng bộ, và sau đó fulfill (hoặc invert) expectation khi tác vụ hoàn thành hoặc đạt trạng thái mong đợi. Cuối cùng, bạn gọi wait(for:timeout:) để tạm dừng test cho đến khi expectation được fulfill hoặc hết thời gian chờ.

import XCTest
@testable import YourAppName

// Giả sử bạn có một lớp xử lý dữ liệu bất đồng bộ
class DataFetcher {
    func fetchData(completion: @escaping (Result<String, Error>) -> Void) {
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { // Giả lập độ trễ 1 giây
            // Giả lập thành công
            completion(.success("Data fetched successfully"))
            
            // Hoặc giả lập thất bại
            // completion(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Network error"])))
        }
    }
}

final class DataFetcherTests: XCTestCase {

    var fetcher: DataFetcher!

    override func setUpWithError() throws {
        fetcher = DataFetcher()
    }

    func testFetchData_success_returnsData() {
        // 1. Tạo một expectation
        let expectation = self.expectation(description: "Fetch data completes with success")

        fetcher.fetchData { result in
            switch result {
            case .success(let data):
                // 2. Xác nhận kết quả
                XCTAssertEqual(data, "Data fetched successfully")
                // 3. Fulfill expectation
                expectation.fulfill()
            case .failure(let error):
                XCTFail("Fetch data failed with error: \(error.localizedDescription)")
            }
        }

        // 4. Chờ expectation được fulfill hoặc hết thời gian chờ
        waitForExpectations(timeout: 2, handler: nil) // Đặt timeout lớn hơn độ trễ giả lập
    }
    
    // Ví dụ kiểm thử thất bại (nếu fetchData có thể fail)
    // func testFetchData_failure_returnsError() {
    //     let expectation = self.expectation(description: "Fetch data completes with failure")
    //     // Cần logic trong DataFetcher để giả lập lỗi, ví dụ truyền một cờ (flag) vào hàm
    //     fetcher.fetchData(shouldFail: true) { result in
    //         switch result {
    //         case .success(_):
    //             XCTFail("Fetch data succeeded unexpectedly")
    //         case .failure(let error):
    //             // Xác nhận lỗi
    //             XCTAssertNotNil(error)
    //             expectation.fulfill()
    //         }
    //     }
    //     waitForExpectations(timeout: 2, handler: nil)
    // }
}

Kỹ thuật này cũng áp dụng cho các tác vụ bất đồng bộ sử dụng Combine, RxSwift, Delegates hoặc Closures hoàn thành bất đồng bộ.

Kiểm thử Giao diện Người dùng (UI Testing) với XCUITest

UI Testing (sử dụng framework XCUITest, được xây dựng trên XCTest) khác với Unit Testing ở chỗ nó kiểm thử ứng dụng từ góc nhìn của người dùng. Nó tương tác với các phần tử giao diện (button, label, text field, v.v.), mô phỏng các hành động của người dùng (tap, swipe, type), và xác nhận trạng thái của giao diện hoặc kết quả mong đợi trên màn hình.

Mục tiêu của UI Testing là xác minh các luồng người dùng quan trọng (ví dụ: đăng nhập, thêm sản phẩm vào giỏ hàng, hoàn thành một form) hoạt động đúng trên giao diện thực của ứng dụng.

Thêm UI Test Target

Tương tự Unit Test, bạn có thể thêm UI Test target khi tạo dự án hoặc thêm sau bằng cách chọn File > New > Target… > UI Testing Bundle.

Anatomy của một UI Test Class

UI Test class cũng kế thừa từ XCTestCase và có các phương thức setUpWithError(), tearDownWithError(), và các phương thức test bắt đầu bằng test.

import XCTest

final class YourAppNameUITests: XCTestCase {

    // Khởi tạo trước mỗi UI test method
    override func setUpWithError() throws {
        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false // Nếu một assertion fail, dừng ngay test case đó

        // In UI tests it’s important to set up the initial state - such as interface orientation - required for your tests before they run. The setUpWithError method is a good place to do this.
        
        // Khởi chạy ứng dụng cho mỗi test case
        let app = XCUIApplication()
        app.launch()
        
        // Có thể thiết lập trạng thái ban đầu của ứng dụng tại đây
        // Ví dụ: Xóa dữ liệu user defaults, đăng xuất nếu cần
    }

    // Dọn dẹp sau mỗi UI test method
    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    // Test UI
    func testExampleUIFlow() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        // Mọi tương tác UI sẽ diễn ra trên instance 'app' này
        
        // ... code tương tác UI ...
    }
    
    // Test UI hiệu năng
    func testLaunchingPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
            // This measures how long it takes to launch your application.
            measure(metrics: [XCTApplicationLaunchMetric()]) {
                XCUIApplication().launch()
            }
        }
    }
}
  • import XCTest: Cần thiết.
  • continueAfterFailure = false: Thiết lập phổ biến trong UI test để một lỗi nhỏ không làm hỏng hoàn toàn test suite.
  • XCUIApplication().launch(): Bắt buộc phải gọi trong setUpWithError() hoặc đầu mỗi test method để khởi chạy ứng dụng.

Ghi lại (Recording) UI Tests

Một tính năng hữu ích của XCUITest là khả năng ghi lại các tương tác của bạn với ứng dụng và tự động tạo code UI test tương ứng. Điều này rất tiện cho việc bắt đầu hoặc ghi lại các luồng phức tạp.

  1. Mở file UI test class của bạn.
  2. Đặt con trỏ vào bên trong một test method (hoặc tạo một test method mới).
  3. Nhấn nút ghi màu đỏ ở cuối vùng soạn thảo code trong Xcode.
  4. Xcode sẽ chạy ứng dụng trên simulator/thiết bị và bắt đầu ghi lại.
  5. Tương tác với ứng dụng như một người dùng bình thường (chạm nút, nhập text, cuộn, v.v.).
  6. Mỗi hành động của bạn sẽ được dịch thành một dòng code XCUITest và chèn vào test method tại vị trí con trỏ.
  7. Nhấn nút ghi lại lần nữa để dừng.

Code được tạo ra từ việc ghi lại là điểm khởi đầu tốt nhưng thường cần được chỉnh sửa và làm cho mạnh mẽ hơn (ví dụ: sử dụng accessibility identifiers thay vì hardcoded index).

Viết UI Tests Thủ công

Mặc dù ghi lại rất tiện, viết UI tests thủ công giúp bạn kiểm soát tốt hơn và tạo ra các bài kiểm thử ổn định, dễ bảo trì hơn.

Để tương tác với UI element, bạn cần truy cập vào instance của ứng dụng (XCUIApplication) và sử dụng các query để tìm kiếm các phần tử trên màn hình.

func testLoginFlow() throws {
    let app = XCUIApplication()
    app.launch() // Đảm bảo ứng dụng được khởi chạy

    // Tìm các phần tử UI
    let usernameTextField = app.textFields["usernameTextFieldIdentifier"] // Tìm theo Accessibility Identifier
    let passwordSecureTextField = app.secureTextFields["passwordSecureTextFieldIdentifier"] // Tìm theo Identifier
    let loginButton = app.buttons["loginButtonIdentifier"] // Tìm theo Identifier
    let welcomeLabel = app.staticTexts["welcomeLabelIdentifier"] // Tìm theo Identifier cho Label

    // Tương tác với các phần tử
    XCTAssertTrue(usernameTextField.exists, "Trường username không tồn tại") // Kiểm tra tồn tại trước khi tương tác
    usernameTextField.tap()
    usernameTextField.typeText("testuser")

    XCTAssertTrue(passwordSecureTextField.exists, "Trường password không tồn tại")
    passwordSecureTextField.tap()
    passwordSecureTextField.typeText("password123")

    XCTAssertTrue(loginButton.exists, "Nút Login không tồn tại")
    loginButton.tap()

    // Xác nhận kết quả sau tương tác
    let exists = NSPredicate(format: "exists == true") // Sử dụng Predicate cho assertion chờ đợi
    expectation(for: exists, evaluatedWith: welcomeLabel, handler: nil)
    waitForExpectations(timeout: 5, handler: nil) // Chờ label xuất hiện trong 5 giây

    XCTAssertTrue(welcomeLabel.exists, "Label chào mừng không xuất hiện sau khi đăng nhập")
    XCTAssertEqual(welcomeLabel.label, "Chào mừng, testuser!") // Kiểm tra nội dung label
}

Các điểm quan trọng:

  • Tìm kiếm phần tử: Sử dụng các property của XCUIApplication như textFields, buttons, staticTexts, tables, cells, v.v. Bạn có thể tìm kiếm phần tử dựa trên accessibilityIdentifier (khuyến khích!), label, value, placeholder, v.v. Việc gán accessibilityIdentifier cho các UI element trong Interface Builder hoặc code giúp UI test mạnh mẽ hơn. Điều này liên quan đến chủ đề Khả Năng Truy Cập.
  • Tương tác: Sử dụng các phương thức như tap(), typeText(_:), swipeUp(), scroll(to:).
  • Xác nhận: Sử dụng XCTAssert để kiểm tra các thuộc tính của phần tử UI (ví dụ: exists, isEnabled, label, value). Đối với các trạng thái UI thay đổi sau một hành động (ví dụ: một view xuất hiện sau khi chạm nút), bạn nên sử dụng expectation(for:evaluatedWith:handler:)waitForExpectations(timeout:handler:) để chờ đợi sự thay đổi đó xảy định trong một khoảng thời gian nhất định, tránh làm test bị “flaky” (lúc pass lúc fail không rõ nguyên nhân).

Unit Testing vs. UI Testing: Khi nào dùng loại nào?

Unit test và UI test phục vụ các mục đích khác nhau và thường được sử dụng kết hợp để có độ bao phủ kiểm thử tốt nhất.

Đặc điểm Unit Testing (XCTest) UI Testing (XCUITest)
Phạm vi Kiểm thử các đơn vị code nhỏ nhất (hàm, lớp, struct). Kiểm thử toàn bộ ứng dụng từ góc nhìn người dùng, tương tác qua giao diện.
Tốc độ Rất nhanh. Chạy trong cô lập, không cần chạy UI. Chậm hơn đáng kể. Cần khởi chạy ứng dụng, mô phỏng tương tác người dùng.
Mục đích chính Xác minh logic nghiệp vụ, các hàm xử lý dữ liệu, các lớp tiện ích hoạt động đúng. Xác minh các luồng người dùng (user flows), các màn hình hiển thị đúng, tương tác hoạt động.
Thiết lập Tương đối đơn giản. Cần mocking/stubbing cho các dependency (như network layer, database) để cô lập đơn vị code. (Chủ đề nâng cao hơn). Liên quan đến việc làm chủ kết nối mạng hoặc lưu trữ dữ liệu. Cần thiết lập môi trường ứng dụng (ví dụ: trạng thái đăng nhập, dữ liệu test). Đôi khi cần backend hỗ trợ cho môi trường test.
Tính ổn định Thường rất ổn định (deterministic). Có thể “flaky” (không ổn định) do yếu tố thời gian, trạng thái thiết bị, hoặc thay đổi nhỏ trên UI. Cần kỹ thuật viết test bền vững.
Chi phí viết/duy trì Thường ít tốn kém hơn mỗi bài test. Độ bao phủ (coverage) cao hơn dễ đạt được. Tốn kém hơn mỗi bài test. Độ bao phủ toàn bộ UI là khó và không cần thiết.
Trường hợp sử dụng lý tưởng Kiểm thử logic tính toán, xử lý chuỗi, thuật toán, validation form, các model, view models/presenters (trong kiến trúc MVVM, MVP, VIPER), các lớp xử lý JSON, v.v. Kiểm thử luồng đăng ký/đăng nhập, quy trình thanh toán, điều hướng giữa các màn hình (Điều hướng trong iOS), kiểm tra hiển thị các view (Xây dựng Giao diện Người dùng Cơ bản với UIKit) trên các kích thước màn hình khác nhau.

Nguyên tắc chung là viết nhiều Unit Test để kiểm tra chi tiết logic bên trong và một số lượng ít hơn UI Test để kiểm tra các luồng người dùng quan trọng và sự tương tác giữa các thành phần UI. “Kim tự tháp kiểm thử” (Test Pyramid) minh họa điều này: đáy là Unit Test (số lượng nhiều, nhanh), giữa là Integration Test (kiểm thử sự tương tác giữa các đơn vị, ít hơn Unit Test), và đỉnh là UI Test (số lượng ít nhất, chậm nhất).

Chạy Kiểm thử trong Xcode

Xcode cung cấp nhiều cách để chạy kiểm thử:

  • Chạy tất cả test trong một test class: Click vào biểu tượng mũi tên (play button) bên cạnh tên class.
  • Chạy một test method cụ thể: Click vào biểu tượng mũi tên bên cạnh tên test method.
  • Chạy tất cả tests trong một target: Chọn Product > Test (hoặc nhấn Cmd + U).
  • Chạy tất cả tests trong dự án: Chọn Product > Perform Action > Test.

Kết quả kiểm thử sẽ hiển thị trong Test Navigator (Khám Phá Giao Diện Xcode) và trong Debug Console. Khi một test fail, Xcode sẽ chỉ rõ dòng code XCTAssert nào gây ra lỗi, giúp bạn dễ dàng điều tra và sửa lỗi.

Đưa Kiểm thử vào Quy trình Phát triển

Kiểm thử không nên là công việc cuối cùng sau khi code xong. Hãy tích hợp nó vào quy trình làm việc hàng ngày của bạn.

  • Test-Driven Development (TDD): Một phương pháp phát triển phần mềm mà bạn viết bài kiểm thử *trước* khi viết code chức năng. Quy trình lặp đi lặp lại là: Viết test (fail) -> Viết code (pass test) -> Refactor (đảm bảo test vẫn pass). TDD có thể khó làm quen nhưng mang lại lợi ích lớn về thiết kế và chất lượng code.
  • Viết test khi sửa bug: Khi bạn tìm thấy một bug, hãy viết một bài kiểm thử để tái hiện bug đó trước khi sửa. Sau khi sửa bug, chạy lại test để đảm bảo bug đã được khắc phục và bài test này sẽ ngăn bug đó quay trở lại trong tương lai (regression test).
  • Tích hợp vào CI/CD: Cấu hình hệ thống CI (Continuous Integration) để tự động chạy tất cả Unit và UI test sau mỗi lần code được push lên repository (ví dụ: sử dụng Git và GitHub như đã nói trong bài trước Bắt Đầu Với Git và GitHub). Điều này đảm bảo rằng mọi thay đổi mới không gây ra lỗi cho các chức năng hiện có.

Kết luận

Kiểm thử Unit và UI với XCTest và XCUITest là những kỹ năng không thể thiếu trên Lộ trình học Lập trình viên iOS 2025. Chúng là công cụ mạnh mẽ giúp bạn viết code tốt hơn, phát hiện lỗi sớm hơn, và tự tin hơn khi phát triển và bảo trì ứng dụng phức tạp.

Bắt đầu với Unit Test cho các thành phần logic cốt lõi và thêm UI Test cho các luồng người dùng quan trọng. Thực hành viết test thường xuyên, coi nó là một phần không thể tách rời của quá trình phát triển. Chắc chắn bạn sẽ thấy sự khác biệt lớn về chất lượng và sự ổn định của ứng dụng mình tạo ra.

Tiếp theo trong loạt bài Lộ trình học Lập trình viên iOS 2025, chúng ta sẽ khám phá các khía cạnh khác của quá trình phát triển ứng dụng iOS chuyên nghiệp. Hãy tiếp tục theo dõi!

Chỉ mục