TDD: چیست، اصول تست و روش‌شناسی

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

Test-Driven Development (TDD) — یک روش‌شناسی توسعه است که در آن تست‌ها قبل از پیاده‌سازی کد نوشته می‌شوند. توسعه‌دهنده ابتدا رفتار مورد انتظار را در قالب یک تست ناموفق فرموله می‌کند، سپس حداقل کد را برای عبور از آن می‌نویسد و پس از آن نتیجه را بازآفرینی (رفاکتور) می‌کند. به گفته Martin Fowler (2023)، TDD یک تکنیک تست نیست — بلکه یک تکنیک طراحی است که معماری را منظم می‌کند و تعداد نقص‌ها را در مرحله نوشتن کد کاهش می‌دهد.

نکات کلیدی

  • TDD — روش‌شناسی که در آن تست قبل از پیاده‌سازی نوشته می‌شود، نه بعد از آن
  • چرخه Red-Green-Refactor — اساس TDD: تست قرمز، تست سبز، بازآفرینی
  • JUnit و Mockito — ابزارهای اصلی برای TDD در توسعه Android
  • پوشش کد در پروژه‌های TDD اغلب به لطف انضباط «تست قبل از همه چیز» از ۹۰٪ فراتر می‌رود
  • بازآفرینی (رفاکتورینگ) بدون ترس از شکستن عملکرد — مزیت کلیدی رویکرد TDD

TDD چیست؟

Test-Driven Development — یک تمرین توسعه نرم‌افزار است که در آن تست‌های خودکار نوشتن کد تولیدی را تعیین می‌کنند. بر خلاف رویکرد سنتی که در آن کد نوشته می‌شود و سپس تست می‌شود، TDD توالی را معکوس می‌کند: ابتدا تست نوشته می‌شود، سپس کدی که این تست را پاس می‌کند.

بنیانگذار TDD Kent Beck در نظر گرفته می‌شود که این تمرین را در اواخر دهه ۱۹۹۰ در چارچوب روش‌شناسی Extreme Programming (XP) فرموله کرد. در کتاب «Test-Driven Development: By Example» (۲۰۰۲) بک پنج قانون TDD را توصیف کرد که به قوانین متعارف تبدیل شدند: تست را قبل از کد تولیدی بنویس، دقیقاً به اندازه‌ای کد بنویس که برای عبور تست لازم است و پس از هر چرخه بازآفرینی کن.

اصول کلیدی TDD

اولین اصل — تست رابط را تعیین می‌کند. توسعه‌دهنده مجبور است قبل از اینکه به نحوه پیاده‌سازی کامپوننت فکر کند، به نحوه استفاده از آن فکر کند. این از همان ابتدا یک API تمیز را شکل می‌دهد.

TDD به عنوان تکنیک طراحی

دومین اصل — حداقل پیاده‌سازی. وقتی تست نوشته شد، توسعه‌دهنده دقیقاً به اندازه‌ای کد تولیدی می‌نویسد که برای عبور تست لازم است — نه یک خط بیشتر. این از انتزاع زودهنگام و پیچیدگی بیش از حد جلوگیری می‌کند که Martin Fowler آن را Speculative Generality می‌نامد.

تفاوت TDD با تست معمولی

تفاوت کلیدی بین TDD و تست «پس از واقع» — انضباط توالی است. در TDD تست نه تنها کد را بررسی می‌کند — بلکه ساختار آن را هدایت می‌کند. طبق تحقیقات Microsoft Research (Nagappan et al., 2008)، تیم‌هایی که TDD را اعمال می‌کنند، کاهش تراکم نقص را به میزان ۴۰–۹۰٪ در مقایسه با تیم‌هایی که از رویکرد سنتی استفاده می‌کنند نشان می‌دهند.

چرخه Red-Green-Refactor

چرخه Red-Green-Refactor — یک توالی سه مرحله‌ای است که برای هر تست جدید تکرار می‌شود. Red: تستی بنویس که عبور نمی‌کند. Green: حداقل کد را بنویس تا تست عبور کند. Refactor: کد را بدون تغییر رفتارش بهبود بده.

مرحله Red: نوشتن تست ناموفق

توسعه‌دهنده تستی می‌نویسد که عملکرد هنوز پیاده‌سازی نشده را بررسی می‌کند. در این مرحله تست باید ناموفق باشد — این تأیید می‌کند که تست واقعاً چیزی را بررسی می‌کند. در محیط توسعه Android فریم‌ورک JUnit 5 نشانگر قرمز را برای تست‌های ناموفق نمایش می‌دهد که نام این مرحله را داده است.

kotlin
class CalculatorTest {
    fun testAddition() {
        val result = Calculator().add(2, 3)
        Assertions.assertEquals(5, result)
    }
}

مرحله Green: حداقل پیاده‌سازی

در این مرحله حداقل کد تولیدی نوشته می‌شود که برای عبور تست کافی است. هیچ افزونگی — فقط آنچه برای نشانگر سبز نیاز است. اگر پیاده‌سازی می‌تواند ثابت باشد — بگذار ثابت باشد. بازآفرینی در مرحله بعدی انجام خواهد شد وقتی تست‌های جدید ظاهر شوند.

kotlin
class Calculator {
    fun add(a: Int, b: Int): Int {
        return a + b
    }
}

مرحله Refactor: بهبود بدون ریسک

تست سبز — بیمه‌ای برای بازآفرینی (رفاکتورینگ) است. توسعه‌دهنده می‌تواند پیاده‌سازی را بازنویسی کند، عملکرد را بهینه کند یا خوانایی را بهبود بخشد، با اطمینان از اینکه تست فوراً هر انحرافی از رفتار مورد انتظار را کشف خواهد کرد. در توسعه موبایل در Android این مرحله برای جداسازی رابط‌های مشترک و کاهش تکرار کد بسیار مهم است.

مزایای TDD در توسعه موبایل

استفاده از TDD در پروژه‌های موبایل مزایای قابل اندازه‌گیری می‌دهد که هم توسط تحقیقات آکادمیک و هم توسط تجربه استودیوهای توسعه پیشرو تأیید شده است.

کاهش تراکم نقص

تحقیق IBM (Bhat & Nagappan, 2006) روی چهار پروژه صنعتی نشان داد که تیم‌های استفاده‌کننده از TDD ۴۰٪ نقص کمتری در مقایسه با تیم‌های مشابهی که به صورت سنتی کار می‌کنند دارند. برای توسعه موبایل، جایی که هزینه رفع اشکال پس از انتشار در Google Play به طور قابل توجهی بالاتر از مرحله نوشتن کد است، این معیار حیاتی است.

مستندسازی کد از طریق تست

تست‌های نوشته شده بر اساس TDD به عنوان مستندات زنده API عمل می‌کنند. توسعه‌دهنده‌ای که به پروژه می‌آید می‌تواند تست‌ها را بخواند و بفهمد که هر کامپوننت چگونه باید استفاده شود. این به ویژه در شرایط گردش بالای نیروی کار — مشکل معمول استودیوهای موبایل — ارزشمند است.

بازآفرینی مطمئن

پوشش کد بیش از ۹۰٪ به توسعه‌دهندگان اجازه می‌دهد بدون ترس از شکستن چیزی بازآفرینی کنند. Google در کتاب خود «Software Engineering at Google» (۲۰۲۰) پوشش تست را عامل کلیدی می‌نامد که امکان تمیز نگه داشتن پایگاه کد را در پروژه‌هایی با میلیون‌ها خط کد فراهم می‌کند.

ابزارها و فریم‌ورک‌های TDD

اکوسیستم TDD در توسعه موبایل شامل ابزارهایی برای تست واحد، mocking و بررسی کامپوننت‌های UI است — هم برای Android و هم برای iOS.

ابزارپلتفرمکاربرد
JUnit 5Android (Kotlin/Java)فریم‌ورک پایه برای تست‌های واحد
MockitoAndroidایجاد اشیاء mock و تأیید فراخوانی‌ها
MockKAndroid (Kotlin)Mocking با syntax Kotlin-first و پشتیبانی از کوروتین‌ها
TurbineAndroidتست Kotlin Flow و جریان‌های واکنش‌گرا
XCTestiOS (Swift)فریم‌ورک استاندارد تست

انتخاب فریم‌ورک برای Android

برای پروژه‌های Android روی Kotlin پشته استاندارد شامل JUnit 5 + MockK است. MockK بر Mockito ترجیح داده می‌شود زیرا از توابع درجه یک Kotlin — sealed class، کوروتین‌ها و suspend-توابع بدون تنظیمات اضافی پشتیبانی می‌کند.

ابزارهای iOS

در توسعه iOS، TDD از طریق XCTest پیاده‌سازی می‌شود — فریم‌ورک داخلی Apple که assertions، کلاس‌های تست و یکپارچه‌سازی با CI/CD را از طریق Xcode Server یا GitHub Actions فراهم می‌کند. برای mocking در iOS از کتابخانه‌های Cuckoo و OHHTTPStubs استفاده می‌شود.

نمونه کد TDD در Kotlin

بیایید یک سناریوی واقعی TDD در Kotlin برای Android را بررسی کنیم — تست مخزن کاربران. ابتدا تست را می‌نویسیم، سپس — پیاده‌سازی که این تست را پاس می‌کند.

مرحله ۱: تست برای UserRepository

kotlin
class UserRepositoryTest {
    private val api = mockk<UserApi>()
    private val dao = mockk<UserDao>()
    private val repo = UserRepository(api, dao)

    fun `when api returns user then cache and emit`() = runTest {
        val user = User(1, "Alice")
        coEvery { api.getUser(1) } returns user
        every { dao.insert(user) } returns Unit

        val result = repo.getUser(1)

        assertEquals(user, result)
        verify { dao.insert(user) }
    }
}

مرحله ۲: حداقل پیاده‌سازی

kotlin
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUser(id: Int): User {
        val user = api.getUser(id)
        dao.insert(user)
        return user
    }
}

مرحله ۳: تست برای کش با حالت آفلاین

پس از عبور از تست اول، دومی را اضافه می‌کنیم — رفتار هنگام خطای شبکه را بررسی می‌کنیم. اکنون تست تعیین می‌کند که وقتی API ناموفق است، مخزن باید داده را از کش بازگرداند.

kotlin
fun `when api fails then return cached user`() = runTest {
    val cached = User(1, "Cached Alice")
    coEvery { api.getUser(1) } throws IOException()
    every { dao.getById(1) } returns cached

    val result = repo.getUser(1)

    assertEquals(cached, result)
}

اشتباهات رایج در پیاده‌سازی TDD

انتقال به TDD با اشتباهات معمولی همراه است که می‌تواند تمام مزایای روش‌شناسی را از بین ببرد. درک این دام‌ها به تیم‌ها کمک می‌کند تمرین را مؤثرتر پیاده‌سازی کنند.

تست‌های خیلی بزرگ

اولین و رایج‌ترین ضدالگو — تست حجم خیلی زیاد عملکرد در یک تست. تست باید دقیقاً یک ادعا را بررسی کند (یک assertion). اگر تست ناموفق باشد، توسعه‌دهنده باید بداند دقیقاً چه چیزی خراب شده است بدون دیباگ اضافی.

نادیده گرفتن مرحله قرمز

دومین اشتباه — نوشتن تستی که از ابتدا پاس می‌شود. اگر تست حداقل یک بار قرمز نبوده باشد، اطمینانی وجود ندارد که اصلاً چیزی را بررسی می‌کند. قانون: هرگز به تستی که آن را ناموفق ندیده‌ای اعتماد نکن.

رد شدن از بازآفرینی

سومین اشتباه معمول — توقف در مرحله سبز. بازآفرینی (رفاکتورینگ) یک مرحله اختیاری نیست، بلکه اجباری از چرخه است. بدون آن پایگاه کد تخریب می‌شود، تست‌ها شکننده می‌شوند و مزایای TDD از بین می‌رود.

  • تست پیاده‌سازی نه رفتار — تست‌ها به جزئیات وابسته می‌شوند و با هر بازآفرینی می‌شکنند
  • فقدان تست برای موارد مرزی — لیست‌های خالی، مقادیر null، شرایط مرزی پوشش داده نمی‌شوند
  • نادیده گرفتن سرعت تست — تست‌های کند چرخه بازخورد را کند می‌کنند و انضباط TDD را از بین می‌برند

سوالات متداول

TDD — تکنیک تست است یا طراحی؟

TDD در درجه اول یک تکنیک طراحی است نه تست. تست‌ها در TDD نقش مشخصات را ایفا می‌کنند: آنها API کامپوننت را قبل از پیاده‌سازی تعیین می‌کنند. خود Kent Beck TDD را «انضباط طراحی، نه تست» می‌نامد.

چقدر زمان برای یادگیری TDD نیاز است؟

طبق تحقیقات Microsoft Research، تیم‌ها به ۳ تا ۶ ماه تمرین مداوم نیاز دارند تا TDD به عادت تبدیل شود. ۲–۳ هفته اول بهره‌وری ۱۵–۳۰٪ کاهش می‌یابد، اما پس از سازگاری به سطح اولیه بازمی‌گردد یا به دلیل کاهش زمان دیباگ از آن فراتر می‌رود.

آیا TDD برای کامپوننت‌های UI مناسب است؟

بله، اما با محدودیت‌هایی. برای منطق UI (ViewModel, State) TDD مستقیماً قابل استفاده است. برای کامپوننت‌های بصری (Compose UI, SwiftUI Views) تست اسکرین‌شات (snapshot testing) TDD را تکمیل می‌کند اما جایگزین آن نمی‌شود. توصیه می‌شود منطق تجاری و نمایش را جدا کنید.

آیا می‌توان TDD را در پروژه‌های legacy اعمال کرد؟

برای کد legacy استراتژی «تست‌های شخصیت‌پردازی» (characterization tests) توصیه می‌شود — وقتی تست‌ها روی رفتار موجود نوشته می‌شوند و سپس کد بازآفرینی می‌شود. این رویکرد که در کتاب Michael Feathers «Working Effectively with Legacy Code» (۲۰۰۴) توضیح داده شده است امکان پیاده‌سازی تدریجی TDD را فراهم می‌کند.

TDD چگونه با Clean Architecture ترکیب می‌شود؟

TDD و Clean Architecture متقابلاً یکدیگر را تقویت می‌کنند. معماری تمیز نیاز به مرزهای واضح بین لایه‌ها دارد و TDD توسعه‌دهنده را مجبور می‌کند این مرزها را از طریق تست‌ها طراحی کند. لایه دامنه با وابستگی‌های mock به صورت ایزوله تست می‌شود، لایه داده — از طریق تست‌های یکپارچه‌سازی.

خلاصه

  • TDD — روش‌شناسی که در آن تست قبل از پیاده‌سازی نوشته می‌شود، API تمیز را شکل می‌دهد و معماری را هدایت می‌کند
  • چرخه Red-Green-Refactor — واحد پایه TDD: تست ناموفق → حداقل پیاده‌سازی → بازآفرینی
  • استفاده از TDD تراکم نقص را طبق تحقیقات IBM و Microsoft Research ۴۰–۹۰٪ کاهش می‌دهد
  • ابزارهای اصلی برای Android: JUnit 5، MockK، Turbine برای Flow
  • MockK به دلیل پشتیبانی از کوروتین‌ها و sealed class در پروژه‌های Kotlin بر Mockito ترجیح داده می‌شود
  • اشتباهات رایج: تست‌های خیلی بزرگ، رد شدن از مرحله قرمز، نادیده گرفتن بازآفرینی
  • استراتژی توصیه شده برای پیاده‌سازی — تدریجی، از لایه دامنه و ویژگی‌های جدید شروع کنید بدون تلاش برای پوشش یکباره تمام کد legacy

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

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

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

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