Stub (استاب، جایگزین) — یک شیء تستی است که به جای پیادهسازی واقعی، پاسخهای از پیش تعیینشده به فراخوانیهای متد برمیگرداند. در توسعه موبایل، استابها ماژول تحت测试 را از درخواستهای شبکه، پایگاه داده و سیستم فایل جدا میکنند و امکان بررسی منطق بدون پیکربندی محیط را فراهم میکنند. بر خلاف mock، استاب رفتار را بررسی نمیکند — فقط داده ارائه میدهد. جزئیات بیشتر در مقاله مارتین فاولر درباره test doubles.
نکات اصلی
Stub — یک شیء جایگزین است که وابستگی واقعی را در تست جایگزین میکند و مقادیر از پیش تعیینشده را به فراخوانیهای مشخص برمیگرداند. این اصطلاح در طبقهبندی Gerard Meszaros (2007) در کتاب «xUnit Test Patterns» معرفی شد. Stub به دسته test doubles تعلق دارد — اشیایی که در طول تست اجزای واقعی را جایگزین میکنند. هدف اصلی استاب تأمین دادههای قابل پیشبینی برای بلوک مورد آزمایش و حذف عدم قطعیت سیستمهای خارجی است.
اصل کار — تست قبل از اجرا استاب را پیکربندی میکند: «وقتی متد getUsers() فراخوانی شد، این لیست کاربران را برگردان». Stub شامل منطق تجاری نیست، توالی فراخوانیها را بررسی نمیکند و تاریخچه مراجعات را ثبت نمیکند. آن فقط به جای مؤلفه واقعی میایستد و آنچه به آن گفته شده را برمیگرداند. در زمینه تست Android این به این معناست که کلاینت OkHttp درخواست واقعی به سرور نمیفرستد، بلکه پاسخ را از MockWebServer که به عنوان استاب پیکربندی شده دریافت میکند.
زمان استفاده — استابها برای تست لایه UI (ViewModel, Presenter) و منطق تجاری (UseCase, Interactor) بهینه هستند، جایی که باید واکنش به دادههای خاص را بررسی کرد: لیست خالی، سرور خطای 500 برگرداند، توکن منقضی شده باشد. هر موردی که تست به حالت ورودی مشخصی نیاز دارد — وظیفهای برای stub است. برای هر سناریوی تستی پیکربندی مخصوص استاب ایجاد میشود که تستها را خوانا و قابل پیشبینی میکند.
Gerard Meszaros (2007) در کتاب «xUnit Test Patterns» پنج نوع test doubles را مشخص کرد: dummy, stub, spy, mock, fake. هر نوع وظیفه خود را حل میکند. Dummy — منتقل میشود اما استفاده نمیشود. Stub — داده برمیگرداند. Spy — فراخوانیها را ثبت میکند. Mock — رفتار را بررسی میکند. Fake — شامل منطق سادهشده است. درک این طبقهبندی به برنامهنویس کمک میکند ابزار مناسب را برای هر سناریوی تستی انتخاب کند.
درخواستهای شبکه — رایجترین سناریوی استفاده از استابها. برنامه تماسهای HTTP به API انجام میدهد و در تست باید واکنش به پاسخهای مختلف را بررسی کرد: JSON موفق، خطای 401 (غیرمجاز)، تایماوت، آرایه خالی. MockWebServer (OkHttp) در Android و URLProtocol (iOS) به عنوان استاب عمل میکنند و پاسخهای HTTP از پیش تعیینشده را بدون اتصال واقعی به سرور برمیگردانند. این کار تستها را از ثانیه به میلیثانیه افزایش میدهد.
پایگاه داده — Room (Android) و CoreData (iOS) نسخههای in-memory دارند، اما پیکربندی آنها همچنان زمانبر است. Stub به جای مخزن، لیستهای از پیش آماده Entity را بدون دست زدن به پایگاه داده برمیگرداند. این به ویژه برای تست ViewModel مؤثر است، جایی که باید مرتبسازی، فیلتر یا تبدیل دادهها را بررسی کرد. تست صرفنظر از حجم داده در میلیثانیه اجرا میشود.
سرویسهای سیستمی — LocationManager, SensorManager, SharedPreferences به دستگاه واقعی یا شبیهساز نیاز دارند. Stub برای LocationProvider مختصات مشخصی و برای SensorManager مقادیر ثابت شتابسنج را برمیگرداند. در iOS مشابه آن CLLocationManager با پیادهسازی تستی delegate است. بدون استابها چنین تستهایی به دستگاه فیزیکی با شرایط خاص نیاز دارند.
سیستم فایل و کش — بارگذاری تصاویر، ذخیره پاسخها، کار با فایلهای پیکربندی — همه این عملیات به وضعیت دیسک وابسته هستند. Stub برای FileManager یا ImageCache بدون خواندن فایلهای واقعی موفقیت/خطا برمیگرداند. این کار سقوطهای کاذب تستها را به دلیل عدم تطابق مسیرها یا مجوزها در ماشینهای مختلف توسعهدهندگان حذف میکند.
تقسیم مسئولیت — سه نوع test doubles وظایف مختلفی را حل میکنند. Stub: «به من داده بده». Mock: «بررسی کن که مرا صدا زدند». Fake: «من مثل واقعی کار میکنم، فقط سادهتر». این تفاوت برای خوانایی تستها حیاتی است: اگر تست از mock در جایی که stub نیاز است استفاده کند، با فراخوانیهای verify نامرتبط با سناریوی مورد آزمایش بارگذاری میشود.
| ویژگی | Stub | Mock | Fake |
|---|---|---|---|
| هدف | تأمین داده | بررسی تعامل | پیادهسازی سادهشده |
| منطق | ندارد | ندارد | دارد (اما ساده) |
| تأیید | ندارد | دارد (verify) | غیرمستقیم (از طریق وضعیت) |
| انعطافپذیری | کم — پاسخهای ثابت | متوسط | زیاد — منطق تطبیق مییابد |
| سرعت | حداکثر | زیاد | متوسط |
| مثال | MockWebServer JSON برمیگرداند | Mockito.verify(repository).save() | InMemoryRepository با HashMap |
قاعده عملی — اگر تست بررسی میکند که مؤلفه مورد آزمایش چه دادهای دریافت کرده — از stub استفاده کنید. اگر تست بررسی میکند که آیا مؤلفه متد وابستگی را با آرگومانهای صحیح فراخوانی کرده — از mock استفاده کنید. اگر فقط میخواهید پایگاه داده را با یک جدول هش جایگزین کنید — این fake است. مخلوط کردن انواع در یک تست آن را شکننده میکند: با تغییر پیادهسازی باید هم stub و هم منطق verify را بازنویسی کنید.
Stub با verify — یک اشتباه رایج که توسعهدهنده استاب را پیکربندی میکند و سپس verify(stub).method() را اضافه میکند. Stub بنا بر تعریف نباید تأیید شود — برای تأیید از mock استفاده میشود. اگر نیاز به بررسی اینکه متد با آرگومانهای خاص فراخوانی شده دارید، از Mockito.mock() به جای Mockito.stub() استفاده کنید. این جداسازی هدف تست را برای سایر توسعهدهندگان روشن نگه میدارد.
MockWebServer — کتابخانه OkHttp برای ایجاد استابهای HTTP در Android و JVM. این کتابخانه یک سرور HTTP محلی در پورت مشخص راهاندازی میکند که درخواستهای کلاینت OkHttp را رهگیری کرده و پاسخهای از پیش تعیینشده برمیگرداند. راهاندازی سه خط طول میکشد: ایجاد سرور، قرار دادن پاسخ در صف (enqueue)، اجرا. تست میتواند برای سناریوهای صفحهبندی یا تلاش مجدد چندین پاسخ را پشت سر هم در صف قرار دهد.
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 — جایگزین Mockito برای Kotlin با پشتیبانی درجه یک از کوروتینها، توابع توسعهدهنده و کلاسهای sealed. استابها در MockK از طریق coEvery (برای توابع suspend) و every (برای توابع معمولی) ایجاد میشوند. بر خلاف MockWebServer، MockK متدهای وابستگی جداگانه را جایگزین میکند نه کل لایه HTTP را. این برای تستهای واحد UseCase یا Interactor که وابستگیها انتزاعات مخزن هستند مفید است.
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 — برای تستهای یکپارچهسازی از MockWebServer استفاده کنید (HTTP واقعی را رهگیری میکند)، برای تستهای واحد — MockK (اینترفیسها را جایگزین میکند). چیزی را که تست نمیکنید جایگزین نکنید: اگر تست Repository را بررسی میکند، کلاینت OkHttp درون آن را جایگزین نکنید — از MockWebServer واقعی در سطح HTTP استفاده کنید. این قانون تستها را مرتبط نگه میدارد و شکنندگی در بازسازی را کاهش میدهد.
پروتکلهای Swift به عنوان استاب — در رویکرد بومی iOS، استاب از طریق جایگزینی ساختار تستی مطابق با پروتکل وابستگی پیادهسازی میشود. به جای NetworkService واقعی، تست StubNetworkService را دریافت میکند که دادههای ثابت را برمیگرداند. 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 برای Objective-C — کتابخانهای برای ایجاد استاب و mock در پروژههای قدیمی iOS. OCMock از متدهای stub با آرگومانها و مقادیر برگشتی پشتیبانی میکند. پروژههای مدرن Swift رویکرد مبتنی بر پروتکل با استابهای دستی را ترجیح میدهند — این کنترل بر هر متد را فراهم میکند و به وابستگیهای خارجی نیاز ندارد. OCMock گزینهای برای پروژههایی باقی میماند که پروتکلیزه کردن همه وابستگیها از نظر اقتصادی مناسب نیست.
URLProtocol برای استابهای HTTP — مکانیسم سیستمی iOS برای رهگیری درخواستهای شبکه از طریق زیرکلاس URLProtocol. تست یک URLProtocol سفارشی ثبت میکند که URLSession را رهگیری کرده و پاسخهای استاب را برمیگرداند. مزیت نسبت به استابهای دستی: نیازی به تغییر معماری برنامه نیست — URLSession واقعی باقی میماند اما دادهها در سطح پروتکل جایگزین میشوند. نقطه ضعف: دیباگ کردن آن نسبت به سرویس استاب صریح دشوارتر است.
سوالات متداول
Stub دادههای از پیش تعیینشده را برمیگرداند و واقعیت فراخوانی را بررسی نمیکند. Mock علاوه بر این تأیید میکند که متد با آرگومانهای صحیح فراخوانی شده (verify). Stub به سوال «چه چیزی برگردانم» و Mock به سوال «آیا فراخوانی شد» پاسخ میدهد. برای بررسی وضعیت از stub و برای بررسی تعامل از mock استفاده کنید.
Fake زمانی نیاز است که تست به یک پیادهسازی کارآمد (هرچند سادهشده) نیاز دارد — مثلاً پایگاه داده in-memory به جای Room. Stub برای سناریوهای منفرد با دادههای از پیش تعیینشده مناسب است. اگر همان stub را در 10 تست تکرار میکنید — به احتمال زیاد به Fake نیاز دارید. Fake باعث کاهش تکرار میشود زیرا منطق در یک کلاس زندگی میکند.
در Android — MockK برای اشیاء Kotlin (object) از mockkObject() از جمله متدهای استاتیک کلاسهای Java از طریق mockkStatic() پشتیبانی میکند. در iOS — متدهای استاتیک Swift مستقیماً استاب نمیشوند؛ از پروتکلها و DI استفاده کنید تا فراخوانی static را با متد نمونه پروتکل جایگزین کنید. استابهای استاتیک بدهی فنی هستند و باید در کد جدید اجتناب شوند.
از MockWebServer (OkHttp) استفاده کنید — آن به عنوان یک سرور HTTP محلی کار میکند که پاسخها را در صف (enqueue) قرار میدهد. برای Retrofit کافی است URL پایه را به localhost:8080 تغییر دهید. برای Ktor از MockEngine استفاده کنید — مکانیزم داخلی برای جایگزینی HttpStatement. هر دو رویکرد بدون اینترنت واقعی کار میکنند و کنترل کامل بر کد وضعیت، بدنه و هدرهای پاسخ میدهند.
Spy شیء واقعی را میپیچد و فراخوانیها را ثبت میکند، در حالی که Stub شیء را کاملاً با پاسخهای ثابت جایگزین میکند. Spy امکان استفاده جزئی از پیادهسازی واقعی را میدهد (سایر متدها همانطور کار میکنند)، اما stub نمیدهد. اگر نیاز به بررسی فراخوانی متد دارید اما بخشی از منطق باید اجرا شود — از spy استفاده کنید نه stub.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید