Stub: nó là gì, các loại và ứng dụng trong kiểm thử

Tác giả: IT Sectr Đã đăng: 2026-04-10 Thời gian đọc: 9 phút

Stub (stub, vật thay thế) — đối tượng kiểm thử trả về các phản hồi định sẵn cho các lời gọi phương thức thay vì triển khai thực tế. Trong phát triển di động, stub cô lập module đang kiểm thử khỏi các yêu cầu mạng, cơ sở dữ liệu và hệ thống tệp, cho phép kiểm tra logic mà không cần cấu hình môi trường. Không giống như mock, stub không xác minh hành vi — nó chỉ cung cấp dữ liệu. Chi tiết hơn — xem bài viết của Martin Fowler về test doubles.

Điểm chính

  • Stub — stub trả về các giá trị định sẵn cho các lời gọi phương thức mà không có logic
  • Cô lập — stub tắt các phụ thuộc thực tế: API, DB, tệp, cảm biến
  • Khác biệt với Mock — stub không xác minh lời gọi, nó chỉ thay thế phản hồi
  • Android — MockWebServer (OkHttp) làm stub cho HTTP, MockK.constantAnswer cho Kotlin
  • iOS — OCMock và các protocol Swift với các triển khai kiểm thử làm stub

Stub là gì và khác gì so với các test doubles khác?

Stub — là đối tượng giả lập thay thế phụ thuộc thực tế trong kiểm thử và trả về các giá trị định sẵn cho các lời gọi xác định. Thuật ngữ được đưa ra trong phân loại của Gerard Meszaros (2007) trong cuốn sách “xUnit Test Patterns”. Stub thuộc danh mục test doubles — các đối tượng thay thế các thành phần thực tế trong quá trình kiểm thử. Mục đích chính của stub là cung cấp cho khối đang kiểm thử dữ liệu có thể dự đoán, loại bỏ sự không chắc chắn của các hệ thống bên ngoài.

Nguyên lý hoạt động — bài kiểm thử cấu hình stub trước khi thực thi: “khi phương thức getUsers() được gọi, hãy trả về danh sách người dùng này”. Stub không chứa logic nghiệp vụ, không kiểm tra trình tự lời gọi và không ghi lại lịch sử truy cập. Nó chỉ đứng ở vị trí của thành phần thực tế và trả về những gì được yêu cầu. Trong bối cảnh kiểm thử Android, điều này có nghĩa: client OkHttp không thực hiện yêu cầu thực đến máy chủ mà nhận phản hồi từ MockWebServer được cấu hình như stub.

  • Stub — trả về dữ liệu, không kiểm tra lời gọi
  • Mock — trả về dữ liệu và kiểm tra hành vi (verify)
  • Fake — triển khai đơn giản hoạt động được với logic thực
  • Spy — bao bọc đối tượng thực, ghi lại các lời gọi
  • Dummy — được truyền vào nhưng không được sử dụng (null, đối tượng rỗng)

Khi nào nên dùng — stub tối ưu cho việc kiểm thử tầng UI (ViewModel, Presenter) và logic nghiệp vụ (UseCase, Interactor), nơi cần kiểm tra phản ứng với các dữ liệu cụ thể: danh sách rỗng, máy chủ trả về lỗi 500, token hết hạn. Bất kỳ trường hợp nào mà bài kiểm thử yêu cầu một trạng thái đầu vào xác định — đó là việc của stub. Với mỗi kịch bản kiểm thử sẽ tạo cấu hình stub riêng, giúp các bài kiểm thử dễ đọc và có thể dự đoán.

Phân loại test doubles theo Meszaros

Gerard Meszaros (2007) trong cuốn sách “xUnit Test Patterns” đã phân ra năm loại test doubles: dummy, stub, spy, mock, fake. Mỗi loại giải quyết một nhiệm vụ riêng. Dummy — được truyền vào nhưng không được sử dụng. Stub — trả về dữ liệu. Spy — ghi lại các lời gọi. Mock — kiểm tra hành vi. Fake — chứa logic đơn giản hóa. Hiểu được phân loại này giúp lập trình viên chọn đúng công cụ cho từng kịch bản kiểm thử.

Nơi sử dụng stub trong kiểm thử ứng dụng di động

Stub cho các yêu cầu mạng

Yêu cầu mạng — kịch bản sử dụng stub phổ biến nhất. Ứng dụng thực hiện các lời gọi HTTP đến API, và trong bài kiểm thử cần kiểm tra phản ứng với các phản hồi khác nhau: JSON thành công, lỗi 401 (chưa xác thực), hết thời gian, mảng rỗng. MockWebServer (OkHttp) trên Android và URLProtocol (iOS) đóng vai trò stub, trả về các phản hồi HTTP định sẵn mà không cần kết nối thực với máy chủ. Điều này rút ngắn thời gian kiểm thử từ vài giây xuống còn vài mili giây.

Cơ sở dữ liệu — Room (Android) và CoreData (iOS) có các biến thể in-memory, nhưng việc cấu hình chúng vẫn mất thời gian. Stub thay cho repository trả về các danh sách Entity đã chuẩn bị sẵn mà không đụng đến DB. Điều này đặc biệt hiệu quả khi kiểm thử ViewModel, nơi cần kiểm tra việc sắp xếp, lọc hoặc biến đổi dữ liệu. Bài kiểm thử chạy trong vài mili giây bất kể khối lượng dữ liệu.

Dịch vụ hệ thống — LocationManager, SensorManager, SharedPreferences yêu cầu thiết bị thực hoặc trình giả lập. Stub cho LocationProvider trả về tọa độ định sẵn, cho SensorManager — các giá trị gia tốc kế cố định. Trên iOS, tương tự là CLLocationManager với triển khai delegate cho kiểm thử. Không có stub, các bài kiểm thử như vậy cần thiết bị vật lý với các điều kiện nhất định.

Hệ thống tệp và bộ nhớ đệm — tải hình ảnh, lưu phản hồi vào cache, làm việc với các tệp cấu hình — tất cả các thao tác này phụ thuộc vào trạng thái của ổ đĩa. Stub cho FileManager hoặc ImageCache trả về thành công/lỗi mà không đọc các tệp thực. Điều này loại bỏ các lỗi giả của bài kiểm thử do không khớp đường dẫn hoặc quyền truy cập trên các máy phát triển khác nhau.

Stub vs Mock vs Fake: những khác biệt chính

Phân chia trách nhiệm — ba loại test doubles giải quyết các nhiệm vụ khác nhau. Stub: “hãy đưa tôi dữ liệu”. Mock: “hãy kiểm tra rằng tôi đã được gọi”. Fake: “tôi hoạt động như thật, chỉ đơn giản hơn”. Sự khác biệt cực kỳ quan trọng cho tính dễ đọc của bài kiểm thử: nếu bài kiểm thử dùng mock ở nơi cần stub, nó sẽ bị quá tải bởi các lời gọi verify không liên quan đến kịch bản đang kiểm tra.

Đặc điểmStubMockFake
Mục đíchCung cấp dữ liệuKiểm tra tương tácTriển khai đơn giản hóa
LogicKhôngKhôngCó (nhưng đơn giản hóa)
Xác minhKhôngCó (verify)Gián tiếp (qua trạng thái)
Linh hoạtThấp — phản hồi cứng nhắcTrung bìnhCao — logic tự thích ứng
Tốc độTối đaCaoTrung bình
Ví dụMockWebServer trả về JSONMockito.verify(repository).save()InMemoryRepository với HashMap

Quy tắc thực hành — nếu bài kiểm thử kiểm tra thành phần đang được kiểm thử nhận được dữ liệu nào — hãy dùng stub. Nếu bài kiểm thử kiểm tra liệu thành phần có gọi phương thức của phụ thuộc với đối số đúng không — hãy dùng mock. Nếu bạn chỉ muốn thay DB bằng bảng băm — đó là fake. Trộn các loại trong cùng một bài kiểm thử khiến nó dễ vỡ: khi thay đổi triển khai sẽ phải viết lại cả stub lẫn logic verify.

Phản mẫu: Stub với verify

Stub với verify — một lỗi phổ biến khi lập trình viên cấu hình stub rồi sau đó thêm verify(stub).method(). Theo định nghĩa, stub không nên được xác minh — để xác minh có mock. Nếu bạn cần kiểm tra rằng phương thức đã được gọi với các đối số cụ thể, hãy dùng Mockito.mock() thay vì Mockito.stub(). Sự tách biệt này giữ cho ý định của bài kiểm thử rõ ràng đối với các lập trình viên khác.

Triển khai stub trên Android với MockWebServer và MockK

MockWebServer — thư viện OkHttp để tạo HTTP-stub trên Android và JVM. Nó khởi chạy một máy chủ HTTP cục bộ trên cổng được chỉ định, chặn các yêu cầu của OkHttp-client và trả về các phản hồi định sẵn. Việc cấu hình chỉ mất ba dòng: tạo máy chủ, enqueue phản hồi, khởi chạy. Bài kiểm thử có thể enqueue tuần tự nhiều phản hồi cho các kịch bản phân trang hoặc thử lại.

kotlin
class UserRepositoryTest {

    private val server = MockWebServer()

    fun setup() {
        server.start(8080)
        val client = OkHttpClient.Builder()
            .readTimeout(1, TimeUnit.SECONDS)
            .build()
    }

    fun test_user_list_success() {
        val json = "[{\"id\":1,\"name\":\"Alice\"}]"
        server.enqueue(MockResponse()
            .setBody(json)
            .setResponseCode(200)
        )
        val result = repository.getUsers()
        assertEquals(1, result.size)
    }

    fun teardown() {
        server.shutdown()
    }
}

MockK — giải pháp thay thế Mockito cho Kotlin với hỗ trợ hạng nhất cho coroutine, extension-function và sealed-class. Stub trong MockK được tạo qua coEvery (cho suspend-function) và every (cho hàm thông thường). Không giống như MockWebServer, MockK tạo stub cho từng phương thức phụ thuộc riêng lẻ, chứ không phải toàn bộ tầng HTTP. Điều này tiện cho unit-test UseCase hoặc Interactor, nơi các phụ thuộc là các abstraction của repository.

kotlin
interface UserRepository {
    suspend fun getUsers(): List<User>
}

class GetUsersUseCaseTest {

    private val repo = mockk<UserRepository>()

    private val useCase = GetUsersUseCase(repo)

    fun test_empty_list() = runTest {
        coEvery { repo.getUsers() } returns emptyList()

        val result = useCase.invoke()

        assertTrue(result.isEmpty())
        coVerify(exactly = 1) { repo.getUsers() }
    }
}

Best practice — cho các bài kiểm thử tích hợp hãy dùng MockWebServer (chặn HTTP thực), cho unit-test — MockK (tạo stub cho interface). Đừng tạo stub cho thứ bạn không kiểm thử: nếu bài kiểm thử kiểm tra Repository, đừng tạo stub cho OkHttp-client bên trong nó — hãy dùng MockWebServer thực ở mức HTTP. Quy tắc này giữ cho các bài kiểm thử phù hợp và giảm độ dễ vỡ khi refactoring.

Triển khai stub trên iOS với OCMock và các protocol

Protocol Swift làm stub — trong cách tiếp cận iOS-native, stub được triển khai bằng cách thay thế một cấu trúc kiểm thử tuân theo protocol của phụ thuộc. Thay vì NetworkService thực, bài kiểm thử nhận StubNetworkService, trả về dữ liệu cố định. Swift là ngôn ngữ có kiểu tĩnh, do đó stub phải tuân theo cùng protocol như dịch vụ thực. Trình biên dịch đảm bảo stub triển khai mọi phương thức được yêu cầu.

swift
protocol NetworkServiceProtocol {
    func fetchUsers() async throws -> [User]
}

struct StubNetworkService: NetworkServiceProtocol {
    let result: Result<[User], Error>

    func fetchUsers() async throws -> [User] {
        try result.get()
    }
}

final class UsersViewModelTests: XCTestCase {
    func test_success_state() async {
        let stub = StubNetworkService(
            result: .success([User(name: "Alice")])
        )
        let vm = UsersViewModel(service: stub)
        await vm.load()
        XCTAssertEqual(vm.users.count, 1)
    }
}

OCMock cho Objective-C — thư viện tạo stub và mock trong các dự án iOS legacy. OCMock hỗ trợ stub-method với đối số và giá trị trả về. Các dự án Swift hiện đại ưa thích cách tiếp cận dựa trên protocol với stub viết tay — điều này cho kiểm soát từng phương thức và không cần phụ thuộc bên ngoài. OCMock vẫn là lựa chọn cho các dự án nơi việc protocol hóa mọi phụ thuộc không hiệu quả về chi phí.

URLProtocol cho HTTP-stub — cơ chế hệ thống của iOS để chặn các yêu cầu mạng thông qua lớp con của URLProtocol. Bài kiểm thử đăng ký một URLProtocol tùy chỉnh, chặn URLSession và trả về stub-response. Ưu điểm so với stub viết tay: không cần thay đổi kiến trúc ứng dụng — URLSession vẫn thực, nhưng dữ liệu được thay thế ở mức protocol. Nhược điểm: khó gỡ lỗi hơn một stub-service tường minh.

Câu hỏi thường gặp

Stub khác gì so với Mock?

Stub trả về dữ liệu định sẵn và không kiểm tra việc gọi. Mock bổ sung xác minh rằng phương thức đã được gọi với các đối số đúng (verify). Stub trả lời câu hỏi “trả về gì”, Mock — câu hỏi “có lời gọi không”. Hãy dùng stub để kiểm tra trạng thái, mock — để kiểm tra tương tác.

Khi nào dùng Fake thay vì Stub?

Fake cần thiết khi bài kiểm thử yêu cầu một triển khai hoạt động được (dù đơn giản hóa) — ví dụ, cơ sở dữ liệu in-memory thay vì Room. Stub phù hợp cho các kịch bản đơn lẻ với dữ liệu định sẵn. Nếu bạn lặp lại cùng một stub trong 10 bài kiểm thử — rất có thể bạn cần Fake. Fake giảm trùng lặp vì logic nằm trong một lớp duy nhất.

Có thể tạo stub cho phương thức tĩnh không?

Trên Android — MockK cho các đối tượng Kotlin (object) hỗ trợ mockkObject(), bao gồm cả phương thức tĩnh của lớp Java qua mockkStatic(). Trên iOS — phương thức tĩnh Swift không thể tạo stub trực tiếp; hãy dùng protocol và DI để thay lời gọi static bằng phương thức instance của protocol. Stub tĩnh là nợ kỹ thuật, nên tránh trong mã mới.

Làm sao để tạo stub cho yêu cầu mạng trên Android?

Hãy dùng MockWebServer (OkHttp) — nó hoạt động như một máy chủ HTTP cục bộ, enqueue các phản hồi. Với Retrofit chỉ cần thay base URL bằng localhost:8080. Với Ktor hãy dùng MockEngine — cơ chế tích hợp để thay thế HttpStatement. Cả hai cách đều hoạt động không cần internet thực và cho kiểm soát hoàn toàn mã trạng thái, phần thân và header của phản hồi.

Stub vs Spy — khác nhau ở điểm nào?

Spy bao bọc đối tượng thực và ghi lại các lời gọi, còn Stub thay thế hoàn toàn đối tượng bằng các phản hồi cố định. Spy cho phép sử dụng một phần triển khai thực (các phương thức còn lại hoạt động nguyên vẹn), còn stub thì không. Nếu bạn cần kiểm tra rằng phương thức đã được gọi nhưng vẫn phải thực thi một phần logic — hãy dùng spy, không phải stub.

Tóm tắt

  • Stub — đối tượng giả lập trả về các phản hồi định sẵn cho các lời gọi phương thức trong quá trình kiểm thử
  • Cô lập phụ thuộc — stub thay thế các yêu cầu mạng, DB, dịch vụ hệ thống và hệ thống tệp
  • Khác biệt với Mock — stub không xác minh lời gọi, nó chỉ trả về dữ liệu mà không kiểm tra hành vi
  • Công cụ Android — MockWebServer cho HTTP, MockK cho interface Kotlin với hỗ trợ coroutine
  • Công cụ iOS — stub dựa trên protocol trên Swift, URLProtocol cho HTTP, OCMock cho Objective-C
  • Đừng trộn vai trò — đừng thêm verify vào stub, hãy dùng mock để xác minh lời gọi
  • Stub + MockWebServer — cách tiếp cận chuẩn cho các bài kiểm thử tích hợp không cần máy chủ thực

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm