Fake là một triển khai đơn giản hóa có thể hoạt động của một phụ thuộc, hoạt động như một thành phần thật nhưng sử dụng bộ nhớ trong thay vì cơ sở hạ tầng sản xuất. Không giống stub, fake chứa logic kinh doanh thực sự — sắp xếp, lọc, tổng hợp — nhưng không có tác dụng bên ngoài. Cơ sở dữ liệu trong bộ nhớ thay vì Room hoặc HashMap thay vì SharedPreferences là những ví dụ kinh điển. Thêm chi tiết trong phân loại test doubles của Martin Fowler.
Những điểm chính
Fake là một triển khai đầy đủ nhưng nhẹ của một giao diện, phù hợp cho kiểm thử. Thuật ngữ này được Gerard Meszaros (2007) giới thiệu trong cuốn sách “xUnit Test Patterns.” Không giống stub trả về câu trả lời được mã hóa cứng, fake chứa mã có thể thực thi: nó có thể sắp xếp danh sách, lọc theo điều kiện, đếm bản ghi. Sự khác biệt duy nhất so với triển khai sản xuất là fake làm việc với dữ liệu trong bộ nhớ và không thực hiện các thao tác I/O thực sự.
Lợi ích chính là tốc độ. Các kiểm thử với fake được thực thi trong mili giây vì không có truy cập đĩa, mạng hoặc cơ sở dữ liệu. Một HashMap trong bộ nhớ hoạt động nhanh hơn 100–1000 lần so với Room hoặc CoreData. Đồng thời, fake kiểm thử logic kinh doanh thực sự: sắp xếp, lọc, tổng hợp — tất cả những gì stub không thể kiểm thử vì stub chỉ trả về những gì được yêu cầu. Fake mang lại sự tự tin rằng mã xử lý dữ liệu đúng cách, thay vì chỉ nhận một câu trả lời định trước.
Fake được ưu tiên hơn Stub — nếu thành phần đang kiểm thử thực hiện nhiều thao tác trên dữ liệu (truy xuất, lọc, sắp xếp, lưu), stub sẽ yêu cầu cấu hình từng lời gọi riêng lẻ. Fake chứa logic bên trong — kiểm thử chỉ cần gọi phương thức và kiểm tra kết quả. Tại IT Sectr, chúng tôi sử dụng fakes cho tất cả các kho lưu trữ trong kiểm thử đơn vị: một kho lưu trữ fake với HashMap bao phủ 90% kịch bản mà không cần cấu hình Mockito hoặc MockK.
Tiêu chí lựa chọn — xác định kiểm thử xác minh điều gì: trạng thái hay tương tác. Nếu kiểm thử xác minh trạng thái (kết quả công việc) và sử dụng logic — hãy dùng fake. Nếu kiểm thử chỉ cần dữ liệu đầu vào không có logic — stub là đủ. Nếu kiểm thử xác minh rằng một phương thức đã được gọi — hãy dùng mock. Trộn lẫn các loại test doubles trong một kiểm thử làm phức tạp việc hiểu và tăng tính dễ vỡ.
| Tiêu chí | Fake | Stub | Mock |
|---|---|---|---|
| Có logic | Có (đơn giản hóa) | Không | Không |
| Tốc độ | Cao | Tối đa | Cao |
| Xác minh hành vi | Gián tiếp | Không | Có (verify) |
| Bảo trì | Một lớp cho mỗi giao diện | Cấu hình theo kiểm thử | Cấu hình theo kiểm thử |
| Tính thực tế | Cao (mã hoạt động) | Thấp (dữ liệu cố định) | Trung bình |
| Rủi ro dương tính giả | Thấp | Trung bình | Cao (kiểm thử dễ vỡ) |
Phản mẫu: Fake không phải là fake — một lỗi phổ biến khi nhà phát triển gọi một đối tượng là fake nhưng thực chất là stub hoặc mock. Nếu InMemoryUserRepository của bạn không chứa logic (lọc, sắp xếp) — nó không phải là fake, mà là stub với bộ nhớ trong. Fake khác stub ở chỗ có logic có thể thực thi. Nếu một kho lưu trữ fake chỉ trả về những gì được đưa vào và không xử lý dữ liệu — hãy dùng mock hoặc stub.
Khuyến nghị thực tế — bắt đầu với fake cho mỗi kho lưu trữ hoặc dịch vụ. Nếu fake vượt quá 50 dòng — hãy chia thành nhiều lớp. Nếu fake không cần thiết (kiểm thử chỉ xác minh một kịch bản duy nhất với dữ liệu cố định) — hãy dùng stub. Nếu kiểm thử xác minh rằng một phương thức đã được gọi với các tham số cụ thể — hãy dùng mock. Đừng tối ưu hóa lựa chọn trước: hãy viết fake, và nếu nó trở nên quá mức, hãy thay thế bằng stub trong một kiểm thử cụ thể.
Kho lưu trữ fake cho Room — một ví dụ điển hình về fake trên Android. Triển khai sản xuất của UserRepository sử dụng Room DAO với các truy vấn SQLite. Phiên bản fake lưu dữ liệu trong MutableList hoặc HashMap và triển khai các phương thức tương tự: getUser(id), saveUser(user), deleteUser(id). Fake chứa logic tìm kiếm, lọc và sắp xếp — giống như kho lưu trữ sản xuất, nhưng không có SQL. Điều này cho phép kiểm thử ViewModel và UseCase mà không cần thiết lập cơ sở dữ liệu Room.
class FakeUserRepository : UserRepository {
private val users = mutableListOf<User>()
override suspend fun getUser(id: String): User? {
return users.find { it.id == id }
}
override suspend fun saveUser(user: User) {
val index = users.indexOfFirst { it.id == user.id }
if (index >= 0) users[index] = user
else users.add(user)
}
override suspend fun search(query: String): List<User> {
return users.filter {
it.name.contains(query, ignoreCase = true)
}
}
}
Fake cho API Retrofit — thay vì MockWebServer (là stub, không phải fake), bạn có thể tạo một triển khai ApiService trả về dữ liệu từ một bộ sưu tập trong bộ nhớ. Sự khác biệt: MockWebServer chặn HTTP và trả về JSON, trong khi fake ApiService hoạt động ở cấp giao diện Kotlin mà không cần tuần tự hóa. Fake nhanh hơn (không phân tích cú pháp JSON) và dễ gỡ lỗi hơn (chạy trong cùng tiến trình, có kiểu). Phù hợp cho các kiểm thử nơi ngữ nghĩa HTTP (mã trạng thái, tiêu đề) không quan trọng.
Fake trong Swift — được xây dựng thông qua các giao thức. Lớp sản xuất triển khai giao thức với logic thực (CoreData, URLSession). Cấu trúc fake triển khai cùng giao thức với bộ nhớ trong và logic đơn giản hóa. Swift là ngôn ngữ có ngữ nghĩa giá trị, vì vậy các cấu trúc fake là bất biến và an toàn trong các kiểm thử đa luồng. Điều này mang lại lợi thế so với các sản phẩm tương tự Android: không cần đồng bộ hóa quyền truy cập vào dữ liệu trong bộ nhớ.
protocol UserRepositoryProtocol {
func getUser(id: String) async -> User?
func saveUser(user: User) async
}
final class FakeUserRepository: UserRepositoryProtocol {
private var storage: [String: User] = [:]
func getUser(id: String) async -> User? {
return storage[id]
}
func saveUser(user: User) async {
storage[user.id] = user
}
}
final class UserViewModelTests: XCTestCase {
func test_save_and_load() async {
let fake = FakeUserRepository()
let vm = UserViewModel(repository: fake)
let user = User(id: "1", name: "Alice")
await vm.saveUser(user)
let loaded = await vm.getUser(id: "1")
XCTAssertEqual(loaded?.name, "Alice")
}
}
Fake cho CoreData — trong các dự án iOS, bạn có thể tạo một NSPersistentContainer trong bộ nhớ bằng cách đặt description.type = NSInMemoryStoreType. Đây là một ngăn xếp CoreData đầy đủ, nhưng chạy trong bộ nhớ. Fake như vậy cho phép kiểm thử NSFetchRequest, các vị từ và sắp xếp mà không cần tạo tệp SQLite. Tốc độ: các kiểm thử trên CoreData trong bộ nhớ chạy nhanh hơn 5–10 lần so với sản phẩm tương tự trên đĩa. Nhược điểm: cần thiết lập NSManagedObjectModel mỗi lần.
FakeURLProtocol — một lớp con của URLProtocol để chặn các yêu cầu mạng trên iOS. Nó được đăng ký qua URLProtocol.registerClass(fakeProtocol). Bên trong, nó chứa một từ điển URL -> Data trong bộ nhớ và trả về dữ liệu mà không có yêu cầu thực sự. Sự khác biệt với stub: FakeURLProtocol có thể kiểm tra nội dung yêu cầu, tiêu đề và trả về các phản hồi khác nhau tùy theo dữ liệu đầu vào. Đây là fake vì nó chứa logic định tuyến yêu cầu.
Fake như Test Fixture — đặt các lớp fake trong một mô-đun kiểm thử dùng chung (androidTest/sharedTest hoặc TestSupport). Tất cả các kiểm thử trong dự án sử dụng cùng một InMemoryUserRepository. Điều này loại bỏ việc trùng lặp thiết lập đối tượng mock trong mỗi kiểm thử và đảm bảo hành vi thống nhất. Thay đổi logic của fake sẽ cập nhật tất cả các kiểm thử đồng thời. Tại IT Sectr, chúng tôi lưu trữ các lớp fake trong sharedTest/java/com/itSectr/fake/ và bao gồm chúng qua implementation project(:sharedTest).
Fake với dữ liệu được đặt trước — các kiểm thử thường cần một kho lưu trữ đã chứa sẵn một số bản ghi. Giải pháp: một phương thức xuởng fakeWithData(vararg items) hoặc một phương thức tích hợp addDefaultData(). Xuởng tạo một fake, điền dữ liệu điển hình vào đó và trả về một đối tượng sẵn sàng sử dụng. Điều này giảm boilerplate trong các kiểm thử: thay vì thiết lập các lời gọi mock, kiểm thử chỉ cần gọi FakeUserRepository.withUsers(alice, bob).
Fake với đếm số lần gọi — đôi khi bạn cần xác minh không chỉ trạng thái mà còn số lần gọi. Fake có thể chứa các bộ đếm: saveCallCount, getUserCallCount. Kiểm thử kiểm tra bộ đếm sau khi thực thi. Đây là sự thỏa hiệp giữa fake thuần túy (xác minh trạng thái) và mock (xác minh tương tác). Các bộ đếm không kiểm tra đối số hoặc thứ tự gọi — chỉ kiểm tra số lượng. Để xác minh đối số, hãy dùng mock.
Fake với Callback — để kiểm thử các kịch bản bất đồng bộ, fake có thể chấp nhận một callback ở mỗi lần gọi: beforeGetUser, afterSaveUser. Điều này cho phép mô phỏng độ trễ, lỗi hoặc kiểm tra các trạng thái trung gian. Cách tiếp cận này hữu ích để kiểm thử các trạng thái tải của UI: fake tạm dừng 100 ms và kiểm thử xác minh rằng màn hình hiển thị bộ tải. Callback không có trong sản xuất — đây là chức năng kiểm thử thuần túy.
Câu hỏi thường gặp
Fake chứa logic chức năng — lọc, sắp xếp, đếm. Stub chỉ trả về câu trả lời định trước mà không có logic. Nếu một đối tượng có rẽ nhánh (if/else, when) — đó là fake. Nếu nó chỉ chứa các giá trị trả về — đó là stub. Fake tốn kém hơn để bảo trì nhưng mang lại các kiểm thử thực tế hơn.
Khi logic của fake không khớp với logic sản xuất. Ví dụ, FakeUserRepository sử dụng tìm kiếm phân biệt chữ hoa chữ thường, trong khi phiên bản sản xuất không phân biệt. Kiểm thử đạt nhưng thực tế có lỗi. Giải pháp: kiểm thử logic của fake riêng biệt hoặc chỉ sử dụng fakes cho các giao diện có logic đơn giản (thao tác CRUD). Đối với logic phức tạp, hãy viết các kiểm thử tích hợp với cơ sở dữ liệu thực.
Cơ sở dữ liệu trong bộ nhớ là một loại fake. Room.inMemoryDatabaseBuilder() tạo SQLite trong bộ nhớ hoạt động như cơ sở dữ liệu sản xuất. Đây là một fake chính thống. Nhưng fake cũng có thể ở cấp kho lưu trữ (không SQL) và ở cấp mạng (FakeApiService). Cơ sở dữ liệu trong bộ nhớ là một trường hợp đặc biệt của fake nơi logic càng gần với thực tế càng tốt.
Có, nhưng hãy thận trọng. Fake cho kho lưu trữ (dữ liệu), Mock cho AnalyticsTracker (xác minh sự kiện). Phân chia theo lớp: fake cho lớp dữ liệu, mock cho lớp phân tích/ghi log. Đừng biến một đối tượng thành cả fake và mock cùng lúc — điều này vi phạm Nguyên tắc Trách nhiệm Đơn lẻ và gây nhầm lẫn cho kiểm thử.
Kiểm thử fake bằng các kiểm thử giống như triển khai sản xuất. Nếu bạn có UserRepositoryTest xác minh save, get, delete — hãy chạy nó hai lần: với FakeUserRepository và với RealUserRepository. Điều này đảm bảo rằng fake sao chép hành vi của lớp sản xuất. Nếu fake bắt đầu hoạt động khác — kiểm thử sẽ thất bại trên cả hai triển khai.
Tổng kết
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.
Đọc thêm