Fake — چیست، هدف و نحوه استفاده در تست‌نویسی

نویسنده: IT Sectr منتشر شده: 2026-04-10 زمان مطالعه: 9 دقیقه

Fake (فیِک) — یک پیاده‌سازی ساده‌شده کاربردی از وابستگی است که مانند یک کامپوننت واقعی رفتار می‌کند، اما از حافظه داخلی (in-memory) یا سایر مکانیزم‌های سبک به جای زیرساخت تولیدی استفاده می‌کند. بر خلاف stub، fake شامل منطق تجاری واقعی است — مرتب‌سازی، فیلتر کردن، تجمیع — فقط بدون اثرات جانبی خارجی. پایگاه داده In-memory به جای Room یا HashMap به جای SharedPreferences — مثال‌های کلاسیک. جزئیات بیشتر در دسته‌بندی test doubles مارتین فاولر.

نکات اصلی

  • Fake — پیاده‌سازی ساده‌شده کاربردی با منطق واقعی، اما بدون وابستگی‌های خارجی
  • ذخیره‌سازی In-memory — مخزن fake داده‌ها را در HashMap ذخیره می‌کند نه در پایگاه داده
  • تفاوت با Stub — stub داده‌های ثابت برمی‌گرداند، fake شامل منطق قابل اجرا است
  • Android — InMemoryUserRepository به عنوان Fake برای تست ViewModel و UseCase
  • iOS — FakeNetworkSession با URLProtocol و داده‌های تست به جای سرور واقعی

Fake چیست و چرا در تست‌نویسی نیاز است؟

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 است

Fake بهتر از Stub است — اگر کامپوننت تحت تست چندین عملیات روی داده‌ها انجام می‌دهد (دریافت، فیلتر، مرتب‌سازی، ذخیره)، stub نیاز به پیکربندی هر فراخوانی جداگانه دارد. Fake منطق را در خود دارد — تست به سادگی متدها را فراخوانی می‌کند و نتیجه را بررسی می‌کند. در IT Sectr ما از fake برای همه مخزن‌ها در تست‌های واحد استفاده می‌کنیم: مخزن fake با HashMap 90% سناریوها را بدون پیکربندی Mockito یا MockK پوشش می‌دهد.

Fake vs Stub vs Mock: چه زمانی چه چیزی را انتخاب کنیم

معیار انتخاب — مشخص کنید تست چه چیزی را بررسی می‌کند: وضعیت یا تعامل. اگر تست وضعیت (نتیجه کار) را بررسی می‌کند و از منطق استفاده می‌کند — fake نیاز است. اگر تست فقط به داده‌های ورودی بدون منطق نیاز دارد — stub کافی است. اگر تست فراخوانی متد را بررسی می‌کند — mock نیاز است. ترکیب انواع test doubles در یک تست درک را دشوارتر می‌کند و شکنندگی را افزایش می‌دهد.

معیارFakeStubMock
داشتن منطقبله (ساده‌شده)خیرخیر
سرعتزیادحداکثرزیاد
بررسی رفتارغیرمستقیمخیربله (verify)
نگهدارییک کلاس برای هر رابطپیکربندی برای هر تستپیکربندی برای هر تست
واقع‌گراییزیاد (کد کار می‌کند)کم (داده‌های ثابت)متوسط
ریسک هشدارهای کاذبکممتوسطزیاد (تست‌های شکننده)

ضدالگو: Fake که fake نیست — اشتباه رایج زمانی که توسعه‌دهنده شیئی را fake می‌نامد که در واقع stub یا mock است. اگر InMemoryUserRepository شما منطق (فیلتر کردن، مرتب‌سازی) ندارد — این fake نیست، بلکه stub با ذخیره‌سازی in-memory است. Fake با stub دقیقاً در داشتن منطق قابل اجرا تفاوت دارد. اگر مخزن fake به سادگی آنچه را که در آن قرار داده شده برمی‌گرداند و داده‌ها را پردازش نمی‌کند — از mock یا stub استفاده کنید.

قاعده عملی انتخاب test double

توصیه عملی — با fake برای هر مخزن یا سرویس شروع کنید. اگر fake پیچیده‌تر از 50 خط است — به چند کلاس تقسیم کنید. اگر fake اصلاً نیاز نیست (تست فقط یک سناریو با داده‌های ثابت را بررسی می‌کند) — از stub استفاده کنید. اگر تست بررسی می‌کند که متد با پارامترهای مشخصی فراخوانی شده است — از mock استفاده کنید. انتخاب را از قبل بهینه نکنید: fake را بنویسید، و اگر اضافی بود، در تست خاص آن را با stub جایگزین کنید.

ایجاد اشیاء Fake در Android برای Room و Retrofit

مخزن Fake برای Room — مثال معمولی fake در Android. پیاده‌سازی تولیدی UserRepository از Room DAO با کوئری‌های SQLite استفاده می‌کند. نسخه fake داده‌ها را در MutableList یا HashMap ذخیره می‌کند و همان متدها را پیاده‌سازی می‌کند: getUser(id)، saveUser(user)، deleteUser(id). Fake شامل منطق جستجو، فیلتر کردن و مرتب‌سازی است — مانند مخزن تولیدی، اما بدون SQL. این امکان تست ViewModel و UseCase را بدون پیکربندی پایگاه Room فراهم می‌کند.

kotlin
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 (کدهای وضعیت، هدرها) مهم نیست مناسب است.

FakeSharedPreferences برای تست‌های سریع

— سناریوی رایج دیگر. SharedPreferences تولیدی از طریق commit/apply روی دیسک می‌نویسد. نسخه fake جفت‌های کلید-مقدار را در HashMap ذخیره می‌کند و فوراً داده را برمی‌گرداند. از همان متدها پشتیبانی می‌کند: getString، putString، getInt، putInt، clear. برای Jetpack DataStore معادل آن FakeDataStore با ذخیره‌ساز in-memory است. چنین fakeهایی تست‌ها را ده‌ها برابر سریع‌تر می‌کنند، زیرا عملیات نوشتن روی دیسک وجود ندارد.

پیاده‌سازی‌های Fake در iOS با ذخیره‌سازهای in-memory

Fake در Swift — از طریق پروتکل‌ها ساخته می‌شود. کلاس تولیدی پروتکل را با منطق واقعی (CoreData، URLSession) پیاده‌سازی می‌کند. ساختار fake همان پروتکل را با ذخیره‌ساز in-memory و منطق ساده‌شده پیاده‌سازی می‌کند. Swift زبانی با معناشناسی مقدار است، بنابراین ساختارهای fake غیرقابل تغییر و در تست‌های چندنخی ایمن هستند. این مزیتی نسبت به مشابه‌های Android ایجاد می‌کند: نیازی به همگام‌سازی دسترسی به داده‌های in-memory نیست.

swift
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 در پروژه‌های موبایل

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 چیست؟

Fake شامل منطق کاربردی است — فیلتر می‌کند، مرتب می‌کند، شمارش می‌کند. Stub فقط پاسخ‌های از پیش تعیین‌شده را بدون منطق برمی‌گرداند. اگر شیء دارای انشعاب است (if/else, when) — این fake است. اگر فقط return values دارد — این stub است. Fake نگهداری گران‌تری دارد، اما تست‌های واقعی‌تری ارائه می‌دهد.

چه زمانی fake می‌تواند مضر باشد؟

زمانی که منطق fake با منطق تولیدی مطابقت ندارد. مثلاً FakeUserRepository از جستجوی case-sensitive استفاده می‌کند، در حالی که نسخه تولیدی case-insensitive است. تست عبور می‌کند، اما در واقعیت باگ وجود دارد. راه‌حل: منطق fake را جداگانه تست کنید یا از fakes فقط برای رابط‌های با منطق ساده (عملیات CRUD) استفاده کنید. برای منطق پیچیده، تست‌های یکپارچه‌سازی با پایگاه داده واقعی بنویسید.

آیا Fake همان in-memory database است؟

In-memory database — یکی از انواع fake است. Room.inMemoryDatabaseBuilder() یک SQLite in-memory ایجاد می‌کند که مانند پایگاه داده تولیدی رفتار می‌کند. این یک fake تمام‌عیار است. اما fake می‌تواند هم در سطح مخزن (بدون SQL) و هم در سطح شبکه (FakeApiService) باشد. پایگاه داده In-memory یک حالت خاص از fake است که منطق آن حداکثر به واقعی نزدیک است.

آیا می‌توان Fake و Mock را در یک تست ترکیب کرد؟

بله، اما با احتیاط. Fake برای مخزن (داده‌ها)، Mock برای AnalyticsTracker (تأیید رویدادها). تقسیم بر اساس لایه‌ها: fake برای لایه داده، mock برای لایه تحلیل/ثبت. یک شیء را همزمان fake و mock نکنید — این اصل تک مسئولیتی را نقض می‌کند و تست را گیج‌کننده می‌کند.

چگونه خود Fake را تست کنیم؟

Fake را تست کنید با همان تست‌های پیاده‌سازی تولیدی. اگر UserRepositoryTest دارید که save، get، delete را بررسی می‌کند — آن را دو بار اجرا کنید: با FakeUserRepository و با RealUserRepository. این تضمین می‌کند که fake رفتار کلاس تولیدی را تکرار می‌کند. اگر fake شروع به رفتار متفاوت کند — تست در هر دو پیاده‌سازی شکست خواهد خورد.

خلاصه

  • Fake — پیاده‌سازی ساده‌شده کاربردی از وابستگی با منطق تجاری واقعی و ذخیره‌سازی in-memory
  • تفاوت با Stub — fake شامل منطق (فیلتر، مرتب‌سازی) است، stub فقط داده برمی‌گرداند
  • سرعت — fake 100-1000 برابر سریع‌تر از پیاده‌سازی تولیدی بدون عملیات I/O کار می‌کند
  • Android — InMemoryUserRepository، FakeDataStore، in-memory Room از طریق Room.inMemoryDatabaseBuilder
  • iOS — fake مبتنی بر پروتکل، CoreData in-memory، FakeURLProtocol برای رهگیری HTTP
  • بهترین روش — fake را به ماژول تست مشترک منتقل کرده و در همه تست‌های پروژه استفاده کنید
  • Fake را تست کنید — همان تست‌ها را روی fake و پیاده‌سازی تولیدی برای بررسی سازگاری اجرا کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید