Fake (فیِک) — یک پیادهسازی سادهشده کاربردی از وابستگی است که مانند یک کامپوننت واقعی رفتار میکند، اما از حافظه داخلی (in-memory) یا سایر مکانیزمهای سبک به جای زیرساخت تولیدی استفاده میکند. بر خلاف stub، fake شامل منطق تجاری واقعی است — مرتبسازی، فیلتر کردن، تجمیع — فقط بدون اثرات جانبی خارجی. پایگاه داده In-memory به جای Room یا HashMap به جای SharedPreferences — مثالهای کلاسیک. جزئیات بیشتر در دستهبندی test doubles مارتین فاولر.
نکات اصلی
Fake — یک پیادهسازی کامل اما سبک از رابط (interface) است که برای تستنویسی مناسب میباشد. این اصطلاح توسط Gerard Meszaros (2007) در کتاب «xUnit Test Patterns» معرفی شد. بر خلاف stub که پاسخهای از پیش تعیینشده را برمیگرداند، fake شامل کد قابل اجرا است: میتواند لیست را مرتب کند، بر اساس شرط فیلتر کند، تعداد رکوردها را بشمارد. تنها تفاوت با پیادهسازی تولیدی — fake با دادههای in-memory کار میکند و عملیات واقعی I/O را انجام نمیدهد.
مزیت اصلی — سرعت است. تستهای با fake در میلیثانیه اجرا میشوند، زیرا دسترسی به دیسک، شبکه یا پایگاه داده وجود ندارد. HashMap in-memory 100-1000 برابر سریعتر از Room یا CoreData کار میکند. در عین حال fake منطق تجاری واقعی را بررسی میکند: مرتبسازی، فیلتر کردن، تجمیع — همه آنچه stub نمیتواند بررسی کند، زیرا stub فقط آنچه به آن گفته شده را برمیگرداند. Fake اطمینان میدهد که کد دادهها را به درستی پردازش میکند، نه اینکه فقط یک پاسخ از پیش تعیینشده دریافت کند.
Fake بهتر از Stub است — اگر کامپوننت تحت تست چندین عملیات روی دادهها انجام میدهد (دریافت، فیلتر، مرتبسازی، ذخیره)، stub نیاز به پیکربندی هر فراخوانی جداگانه دارد. Fake منطق را در خود دارد — تست به سادگی متدها را فراخوانی میکند و نتیجه را بررسی میکند. در IT Sectr ما از fake برای همه مخزنها در تستهای واحد استفاده میکنیم: مخزن fake با HashMap 90% سناریوها را بدون پیکربندی Mockito یا MockK پوشش میدهد.
معیار انتخاب — مشخص کنید تست چه چیزی را بررسی میکند: وضعیت یا تعامل. اگر تست وضعیت (نتیجه کار) را بررسی میکند و از منطق استفاده میکند — fake نیاز است. اگر تست فقط به دادههای ورودی بدون منطق نیاز دارد — stub کافی است. اگر تست فراخوانی متد را بررسی میکند — mock نیاز است. ترکیب انواع test doubles در یک تست درک را دشوارتر میکند و شکنندگی را افزایش میدهد.
| معیار | Fake | Stub | Mock |
|---|---|---|---|
| داشتن منطق | بله (سادهشده) | خیر | خیر |
| سرعت | زیاد | حداکثر | زیاد |
| بررسی رفتار | غیرمستقیم | خیر | بله (verify) |
| نگهداری | یک کلاس برای هر رابط | پیکربندی برای هر تست | پیکربندی برای هر تست |
| واقعگرایی | زیاد (کد کار میکند) | کم (دادههای ثابت) | متوسط |
| ریسک هشدارهای کاذب | کم | متوسط | زیاد (تستهای شکننده) |
ضدالگو: Fake که fake نیست — اشتباه رایج زمانی که توسعهدهنده شیئی را fake مینامد که در واقع stub یا mock است. اگر InMemoryUserRepository شما منطق (فیلتر کردن، مرتبسازی) ندارد — این fake نیست، بلکه stub با ذخیرهسازی in-memory است. Fake با stub دقیقاً در داشتن منطق قابل اجرا تفاوت دارد. اگر مخزن fake به سادگی آنچه را که در آن قرار داده شده برمیگرداند و دادهها را پردازش نمیکند — از mock یا stub استفاده کنید.
توصیه عملی — با fake برای هر مخزن یا سرویس شروع کنید. اگر fake پیچیدهتر از 50 خط است — به چند کلاس تقسیم کنید. اگر fake اصلاً نیاز نیست (تست فقط یک سناریو با دادههای ثابت را بررسی میکند) — از stub استفاده کنید. اگر تست بررسی میکند که متد با پارامترهای مشخصی فراخوانی شده است — از mock استفاده کنید. انتخاب را از قبل بهینه نکنید: fake را بنویسید، و اگر اضافی بود، در تست خاص آن را با stub جایگزین کنید.
مخزن Fake برای Room — مثال معمولی fake در Android. پیادهسازی تولیدی UserRepository از Room DAO با کوئریهای SQLite استفاده میکند. نسخه fake دادهها را در MutableList یا HashMap ذخیره میکند و همان متدها را پیادهسازی میکند: getUser(id)، saveUser(user)، deleteUser(id). Fake شامل منطق جستجو، فیلتر کردن و مرتبسازی است — مانند مخزن تولیدی، اما بدون SQL. این امکان تست ViewModel و UseCase را بدون پیکربندی پایگاه 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 برای Retrofit API — به جای MockWebServer (که stub است نه fake) میتوان پیادهسازی ApiService ایجاد کرد که دادهها را از مجموعه in-memory برمیگرداند. تفاوت: MockWebServer HTTP را رهگیری میکند و JSON برمیگرداند، در حالی که fake-ApiService در سطح رابط Kotlin بدون سریالیزاسیون کار میکند. Fake سریعتر است (بدون تجزیه JSON) و اشکالزدایی آن سادهتر است (در همان فرآیند کار میکند، تایپشده). برای تستهایی که معناشناسی HTTP (کدهای وضعیت، هدرها) مهم نیست مناسب است.
Fake در Swift — از طریق پروتکلها ساخته میشود. کلاس تولیدی پروتکل را با منطق واقعی (CoreData، URLSession) پیادهسازی میکند. ساختار fake همان پروتکل را با ذخیرهساز in-memory و منطق سادهشده پیادهسازی میکند. Swift زبانی با معناشناسی مقدار است، بنابراین ساختارهای fake غیرقابل تغییر و در تستهای چندنخی ایمن هستند. این مزیتی نسبت به مشابههای Android ایجاد میکند: نیازی به همگامسازی دسترسی به دادههای in-memory نیست.
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 برای CoreData — در پروژههای iOS میتوان با تنظیم description.type = NSInMemoryStoreType یک NSPersistentContainer in-memory ایجاد کرد. این یک پشته CoreData کامل است که در حافظه کار میکند. چنین fake امکان تست NSFetchRequest، محمولها و مرتبسازیها را بدون ایجاد فایل SQLite فراهم میکند. سرعت: تستهای CoreData in-memory 5-10 برابر سریعتر از مشابه دیسکی اجرا میشوند. عیب: باید هر بار NSManagedObjectModel را پیکربندی کرد.
FakeURLProtocol — زیرکلاسی از URLProtocol برای رهگیری درخواستهای شبکه در iOS. از طریق URLProtocol.registerClass(fakeProtocol) ثبت میشود. داخل خود یک دیکشنری in-memory URL -> Data دارد و داده را بدون درخواست واقعی برمیگرداند. تفاوت با stub: FakeURLProtocol میتواند بدنه درخواست، هدرها را بررسی کرده و پاسخهای متفاوت بسته به دادههای ورودی برگرداند. این fake است زیرا شامل منطق مسیریابی درخواستها میباشد.
Fake به عنوان Test Fixture — کلاسهای fake را به ماژول تست مشترک (androidTest/sharedTest یا TestSupport) منتقل کنید. همه تستهای پروژه از همان InMemoryUserRepository استفاده میکنند. این کار تکرار پیکربندی اشیاء mock در هر تست را از بین میبرد و رفتار یکپارچه را تضمین میکند. تغییر منطق fake همه تستها را همزمان بهروز میکند. در IT Sectr ما کلاسهای fake را در sharedTest/java/com/itSectr/fake/ ذخیره کرده و از طریق implementation project(:sharedTest) متصل میکنیم.
Fake با دادههای از پیش تعیینشده — اغلب تستها به مخزنی نیاز دارند که از قبل حاوی برخی رکوردها باشد. راهحل: متد کارخانهای fakeWithData(vararg items) یا متد داخلی addDefaultData(). کارخانه fake را ایجاد میکند، آن را با دادههای معمولی پر میکند و شیء آماده استفاده را برمیگرداند. این کار boilerplate را در تستها کاهش میدهد: به جای پیکربندی فراخوانیهای mock، تست به سادگی FakeUserRepository.withUsers(alice, bob) را فراخوانی میکند.
Fake با شمارش فراخوانیها — گاهی نه تنها وضعیت، بلکه تعداد مراجعات نیز باید بررسی شود. Fake میتواند شمارندههایی داشته باشد: saveCallCount، getUserCallCount. تست پس از اجرا شمارنده را بررسی میکند. این سازش بین fake خالص (بررسی وضعیت) و mock (بررسی تعامل) است. شمارندهها آرگومانها و ترتیب فراخوانیها را بررسی نمیکنند — فقط تعداد. برای بررسی آرگومانها از mock استفاده کنید.
Fake با Callback — برای تست سناریوهای ناهمگام، fake میتواند در هر فراخوانی callback بپذیرد: beforeGetUser، afterSaveUser. این امکان شبیهسازی تأخیرها، خطاها یا بررسی وضعیتهای میانی را فراهم میکند. چنین رویکردی برای تست وضعیتهای بارگذاری UI مفید است: fake 100 میلیثانیه مکث میکند، و تست بررسی میکند که صفحه لودر را نشان میدهد. در محیط تولید callback وجود ندارد — این صرفاً قابلیت تست است.
سوالات متداول
Fake شامل منطق کاربردی است — فیلتر میکند، مرتب میکند، شمارش میکند. Stub فقط پاسخهای از پیش تعیینشده را بدون منطق برمیگرداند. اگر شیء دارای انشعاب است (if/else, when) — این fake است. اگر فقط return values دارد — این stub است. Fake نگهداری گرانتری دارد، اما تستهای واقعیتری ارائه میدهد.
زمانی که منطق fake با منطق تولیدی مطابقت ندارد. مثلاً FakeUserRepository از جستجوی case-sensitive استفاده میکند، در حالی که نسخه تولیدی case-insensitive است. تست عبور میکند، اما در واقعیت باگ وجود دارد. راهحل: منطق fake را جداگانه تست کنید یا از fakes فقط برای رابطهای با منطق ساده (عملیات CRUD) استفاده کنید. برای منطق پیچیده، تستهای یکپارچهسازی با پایگاه داده واقعی بنویسید.
In-memory database — یکی از انواع fake است. Room.inMemoryDatabaseBuilder() یک SQLite in-memory ایجاد میکند که مانند پایگاه داده تولیدی رفتار میکند. این یک fake تمامعیار است. اما fake میتواند هم در سطح مخزن (بدون SQL) و هم در سطح شبکه (FakeApiService) باشد. پایگاه داده In-memory یک حالت خاص از fake است که منطق آن حداکثر به واقعی نزدیک است.
بله، اما با احتیاط. Fake برای مخزن (دادهها)، Mock برای AnalyticsTracker (تأیید رویدادها). تقسیم بر اساس لایهها: fake برای لایه داده، mock برای لایه تحلیل/ثبت. یک شیء را همزمان fake و mock نکنید — این اصل تک مسئولیتی را نقض میکند و تست را گیجکننده میکند.
Fake را تست کنید با همان تستهای پیادهسازی تولیدی. اگر UserRepositoryTest دارید که save، get، delete را بررسی میکند — آن را دو بار اجرا کنید: با FakeUserRepository و با RealUserRepository. این تضمین میکند که fake رفتار کلاس تولیدی را تکرار میکند. اگر fake شروع به رفتار متفاوت کند — تست در هر دو پیادهسازی شکست خواهد خورد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید