Test-Driven Development (TDD) — یک روششناسی توسعه است که در آن تستها قبل از پیادهسازی کد نوشته میشوند. توسعهدهنده ابتدا رفتار مورد انتظار را در قالب یک تست ناموفق فرموله میکند، سپس حداقل کد را برای عبور از آن مینویسد و پس از آن نتیجه را بازآفرینی (رفاکتور) میکند. به گفته Martin Fowler (2023)، TDD یک تکنیک تست نیست — بلکه یک تکنیک طراحی است که معماری را منظم میکند و تعداد نقصها را در مرحله نوشتن کد کاهش میدهد.
نکات کلیدی
Test-Driven Development — یک تمرین توسعه نرمافزار است که در آن تستهای خودکار نوشتن کد تولیدی را تعیین میکنند. بر خلاف رویکرد سنتی که در آن کد نوشته میشود و سپس تست میشود، TDD توالی را معکوس میکند: ابتدا تست نوشته میشود، سپس کدی که این تست را پاس میکند.
بنیانگذار TDD Kent Beck در نظر گرفته میشود که این تمرین را در اواخر دهه ۱۹۹۰ در چارچوب روششناسی Extreme Programming (XP) فرموله کرد. در کتاب «Test-Driven Development: By Example» (۲۰۰۲) بک پنج قانون TDD را توصیف کرد که به قوانین متعارف تبدیل شدند: تست را قبل از کد تولیدی بنویس، دقیقاً به اندازهای کد بنویس که برای عبور تست لازم است و پس از هر چرخه بازآفرینی کن.
اولین اصل — تست رابط را تعیین میکند. توسعهدهنده مجبور است قبل از اینکه به نحوه پیادهسازی کامپوننت فکر کند، به نحوه استفاده از آن فکر کند. این از همان ابتدا یک API تمیز را شکل میدهد.
دومین اصل — حداقل پیادهسازی. وقتی تست نوشته شد، توسعهدهنده دقیقاً به اندازهای کد تولیدی مینویسد که برای عبور تست لازم است — نه یک خط بیشتر. این از انتزاع زودهنگام و پیچیدگی بیش از حد جلوگیری میکند که Martin Fowler آن را Speculative Generality مینامد.
تفاوت کلیدی بین TDD و تست «پس از واقع» — انضباط توالی است. در TDD تست نه تنها کد را بررسی میکند — بلکه ساختار آن را هدایت میکند. طبق تحقیقات Microsoft Research (Nagappan et al., 2008)، تیمهایی که TDD را اعمال میکنند، کاهش تراکم نقص را به میزان ۴۰–۹۰٪ در مقایسه با تیمهایی که از رویکرد سنتی استفاده میکنند نشان میدهند.
چرخه Red-Green-Refactor — یک توالی سه مرحلهای است که برای هر تست جدید تکرار میشود. Red: تستی بنویس که عبور نمیکند. Green: حداقل کد را بنویس تا تست عبور کند. Refactor: کد را بدون تغییر رفتارش بهبود بده.
توسعهدهنده تستی مینویسد که عملکرد هنوز پیادهسازی نشده را بررسی میکند. در این مرحله تست باید ناموفق باشد — این تأیید میکند که تست واقعاً چیزی را بررسی میکند. در محیط توسعه Android فریمورک JUnit 5 نشانگر قرمز را برای تستهای ناموفق نمایش میدهد که نام این مرحله را داده است.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
در این مرحله حداقل کد تولیدی نوشته میشود که برای عبور تست کافی است. هیچ افزونگی — فقط آنچه برای نشانگر سبز نیاز است. اگر پیادهسازی میتواند ثابت باشد — بگذار ثابت باشد. بازآفرینی در مرحله بعدی انجام خواهد شد وقتی تستهای جدید ظاهر شوند.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
تست سبز — بیمهای برای بازآفرینی (رفاکتورینگ) است. توسعهدهنده میتواند پیادهسازی را بازنویسی کند، عملکرد را بهینه کند یا خوانایی را بهبود بخشد، با اطمینان از اینکه تست فوراً هر انحرافی از رفتار مورد انتظار را کشف خواهد کرد. در توسعه موبایل در Android این مرحله برای جداسازی رابطهای مشترک و کاهش تکرار کد بسیار مهم است.
استفاده از TDD در پروژههای موبایل مزایای قابل اندازهگیری میدهد که هم توسط تحقیقات آکادمیک و هم توسط تجربه استودیوهای توسعه پیشرو تأیید شده است.
تحقیق IBM (Bhat & Nagappan, 2006) روی چهار پروژه صنعتی نشان داد که تیمهای استفادهکننده از TDD ۴۰٪ نقص کمتری در مقایسه با تیمهای مشابهی که به صورت سنتی کار میکنند دارند. برای توسعه موبایل، جایی که هزینه رفع اشکال پس از انتشار در Google Play به طور قابل توجهی بالاتر از مرحله نوشتن کد است، این معیار حیاتی است.
تستهای نوشته شده بر اساس TDD به عنوان مستندات زنده API عمل میکنند. توسعهدهندهای که به پروژه میآید میتواند تستها را بخواند و بفهمد که هر کامپوننت چگونه باید استفاده شود. این به ویژه در شرایط گردش بالای نیروی کار — مشکل معمول استودیوهای موبایل — ارزشمند است.
پوشش کد بیش از ۹۰٪ به توسعهدهندگان اجازه میدهد بدون ترس از شکستن چیزی بازآفرینی کنند. Google در کتاب خود «Software Engineering at Google» (۲۰۲۰) پوشش تست را عامل کلیدی مینامد که امکان تمیز نگه داشتن پایگاه کد را در پروژههایی با میلیونها خط کد فراهم میکند.
اکوسیستم TDD در توسعه موبایل شامل ابزارهایی برای تست واحد، mocking و بررسی کامپوننتهای UI است — هم برای Android و هم برای iOS.
| ابزار | پلتفرم | کاربرد |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | فریمورک پایه برای تستهای واحد |
| Mockito | Android | ایجاد اشیاء mock و تأیید فراخوانیها |
| MockK | Android (Kotlin) | Mocking با syntax Kotlin-first و پشتیبانی از کوروتینها |
| Turbine | Android | تست Kotlin Flow و جریانهای واکنشگرا |
| XCTest | iOS (Swift) | فریمورک استاندارد تست |
برای پروژههای Android روی Kotlin پشته استاندارد شامل JUnit 5 + MockK است. MockK بر Mockito ترجیح داده میشود زیرا از توابع درجه یک Kotlin — sealed class، کوروتینها و suspend-توابع بدون تنظیمات اضافی پشتیبانی میکند.
در توسعه iOS، TDD از طریق XCTest پیادهسازی میشود — فریمورک داخلی Apple که assertions، کلاسهای تست و یکپارچهسازی با CI/CD را از طریق Xcode Server یا GitHub Actions فراهم میکند. برای mocking در iOS از کتابخانههای Cuckoo و OHHTTPStubs استفاده میشود.
بیایید یک سناریوی واقعی TDD در Kotlin برای Android را بررسی کنیم — تست مخزن کاربران. ابتدا تست را مینویسیم، سپس — پیادهسازی که این تست را پاس میکند.
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) }
}
}
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 ناموفق است، مخزن باید داده را از کش بازگرداند.
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 با اشتباهات معمولی همراه است که میتواند تمام مزایای روششناسی را از بین ببرد. درک این دامها به تیمها کمک میکند تمرین را مؤثرتر پیادهسازی کنند.
اولین و رایجترین ضدالگو — تست حجم خیلی زیاد عملکرد در یک تست. تست باید دقیقاً یک ادعا را بررسی کند (یک assertion). اگر تست ناموفق باشد، توسعهدهنده باید بداند دقیقاً چه چیزی خراب شده است بدون دیباگ اضافی.
دومین اشتباه — نوشتن تستی که از ابتدا پاس میشود. اگر تست حداقل یک بار قرمز نبوده باشد، اطمینانی وجود ندارد که اصلاً چیزی را بررسی میکند. قانون: هرگز به تستی که آن را ناموفق ندیدهای اعتماد نکن.
سومین اشتباه معمول — توقف در مرحله سبز. بازآفرینی (رفاکتورینگ) یک مرحله اختیاری نیست، بلکه اجباری از چرخه است. بدون آن پایگاه کد تخریب میشود، تستها شکننده میشوند و مزایای TDD از بین میرود.
سوالات متداول
TDD در درجه اول یک تکنیک طراحی است نه تست. تستها در TDD نقش مشخصات را ایفا میکنند: آنها API کامپوننت را قبل از پیادهسازی تعیین میکنند. خود Kent Beck TDD را «انضباط طراحی، نه تست» مینامد.
طبق تحقیقات Microsoft Research، تیمها به ۳ تا ۶ ماه تمرین مداوم نیاز دارند تا TDD به عادت تبدیل شود. ۲–۳ هفته اول بهرهوری ۱۵–۳۰٪ کاهش مییابد، اما پس از سازگاری به سطح اولیه بازمیگردد یا به دلیل کاهش زمان دیباگ از آن فراتر میرود.
بله، اما با محدودیتهایی. برای منطق UI (ViewModel, State) TDD مستقیماً قابل استفاده است. برای کامپوننتهای بصری (Compose UI, SwiftUI Views) تست اسکرینشات (snapshot testing) TDD را تکمیل میکند اما جایگزین آن نمیشود. توصیه میشود منطق تجاری و نمایش را جدا کنید.
برای کد legacy استراتژی «تستهای شخصیتپردازی» (characterization tests) توصیه میشود — وقتی تستها روی رفتار موجود نوشته میشوند و سپس کد بازآفرینی میشود. این رویکرد که در کتاب Michael Feathers «Working Effectively with Legacy Code» (۲۰۰۴) توضیح داده شده است امکان پیادهسازی تدریجی TDD را فراهم میکند.
TDD و Clean Architecture متقابلاً یکدیگر را تقویت میکنند. معماری تمیز نیاز به مرزهای واضح بین لایهها دارد و TDD توسعهدهنده را مجبور میکند این مرزها را از طریق تستها طراحی کند. لایه دامنه با وابستگیهای mock به صورت ایزوله تست میشود، لایه داده — از طریق تستهای یکپارچهسازی.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید