آموزش برنامه نویسی به کمک تست Test-Driven Development
-
مقدمه ای بر توسعه تستمحور (TDD)
-
The Test Driven Development cycle
-
Why TDD?
-
Introducing our example application
-
Demonstrating the reverse polish calculator
-
TDD Walkthrough
-
Introduction to real-world testing
-
Introducing SOLID design principles
-
Single responsibility
-
Open closed
-
Liskov substitution
-
Interface segregation
-
Dependency inversion
-
Introducing test doubles
-
Stubs
-
Fakes
-
Mocks
-
Demonstrating test doubles
-
Mock frameworks
-
Dealing with legacy code
-
Demonstration of testing legacy code
-
Legacy code summary
-
Testing principles
-
Testing anti-patterns: The singleton
-
Testing anti-patterns: Create the world
-
Testing anti-patterns: Completely mocked
-
Testing anti-patterns: The exceptional test
-
Testing anti-patterns: Usually passes
-
Testing anti-patterns: One big test
-
Testing anti-patterns: The slow test
-
Testing anti-patterns: Second class test
-
Applying test-driven development
-
TDD Kata
مقدمه ای بر توسعه تستمحور (TDD)
تصور کنید مشغول توسعه یک پروژه نرمافزاری هستید. همه چیز خوب پیش میرود، اما ناگهان یک تغییر کوچک در کد باعث میشود بخشهای دیگری از برنامه از کار بیفتند. شما زمان زیادی را صرف رفع مشکل میکنید، اما مطمئن نیستید که آیا تغییرات شما باعث بروز مشکلات جدید نشدهاند. آیا این تجربه برایتان آشناست؟
توسعه مبتنی بر تست (TDD) یه روش توسعه نرمافزاره که تمرکزش روی بازخورد سریع (rapid feedback) و چرخههای خیلی کوتاه توسعه (very short development cycles) است. به این معنی که برخلاف روشهای معمول که اول کد برنامه رو مینویسیم و بعد میایم تستش میکنیم، توی TDD تستها رو قبل از نوشتن کد برنامه مینویسیم.
تاریخچه (TDD (Background
توسعه مبتنی بر تست (Test-Driven Development یا TDD) یک فرآیند توسعه نرمافزار است که بر اساس بازخورد سریع و چرخههای بسیار کوتاه توسعه اجرا میشود. برخلاف روشهای سنتی که ابتدا کد کاربردی نوشته میشود و سپس تستها اجرا میگردند، در TDD این روند برعکس است؛ یعنی ابتدا تستها نوشته میشوند و سپس کد کاربردی برای گذراندن آنها توسعه مییابد.
این رویکرد ابتدا در دهه ۱۹۹۰ توسط Kent Beck در چارچوب متدولوژی برنامهنویسی چابک (Extreme Programming یا XP) به شهرت رسید. هدف TDD بهبود کیفیت نرمافزار و افزایش تطبیقپذیری با نیازهای متغیر است، که این اهداف به طور مستقیم با اصول توسعه چابک و نوشتن تستها برای اطمینان از کیفیت نرمافزار همخوانی دارند.
تصورات نادرست در مورد TDD
با وجود قدمت TDD، هنوز سوءتفاهمهای رایجی در مورد چیستی آن وجود دارد. بسیاری از تیمها تصور میکنند TDD فقط به معنای نوشتن تستهای زیاد یا داشتن پوشش تستی مناسب است. اما TDD در واقع به یک فرآیند توسعه خاص اشاره دارد که بر نوشتن تستها پیش از کد کاربردی و توسعه تدریجی کد تست و کد کاربردی تاکید دارد.
در TDD، تمرکز اصلی بر نوشتن تستهای سطح پایین (مانند تستهای واحد) است. اگرچه تستهای سطح بالاتر مانند تستهای یکپارچهسازی یا پذیرش نیز مهم هستند، اما معمولاً خارج از فرآیند TDD ایجاد میشوند.
چرخه TDD چطور کار میکنه؟
1.نوشتن تست: اولین قدم نوشتن یک تست است. برای این کار باید نیازمندیها را به دقت بررسی کنید، چه این نیازمندی یک ویژگی جدید باشد یا تغییری در عملکرد فعلی.
در این مرحله، باید در مورد طراحی نیز فکر کنید؛ مانند انتخاب نام متدها، نوع دادههای ورودی و خروجی و تعامل کلاسها.
2. اجرای تست و شکست آن: تست نوشتهشده را اجرا کنید. در این مرحله، انتظار میرود تست شکست بخورد، زیرا هنوز هیچ کد کاربردی وجود ندارد. این شکست تضمین میکند که تست موردنظر معتبر است.
3. نوشتن کد برای گذراندن تست: تنها به اندازهای کد بنویسید که تست موردنظر موفق شود. از پیشبینیهای زیاد در طراحی یا نوشتن کد اضافی اجتناب کنید.
اجرای دوباره تست: کد نوشتهشده را آزمایش کنید تا مطمئن شوید که تست موفق شده است. در این مرحله، اطمینان حاصل میکنید که تغییرات ایجادشده عملکرد موردنظر را پوشش میدهند.
4. بازسازی (Refactor): بدون تغییر رفتار کد، طراحی آن را بهبود دهید. این مرحله شامل حذف تکرارها، بهینهسازی ساختارها و سادهسازی کد است.
5. تکرار چرخه: پس از اطمینان از کیفیت کد و تست، به سراغ تغییر یا ویژگی بعدی بروید و این چرخه را تکرار کنید.
چرا TDD؟
- رویکرد پیشگیرانه به کیفیت: TDD به جای واکنش به مشکلات پس از وقوع، بر پیشگیری از آنها تمرکز دارد. با نوشتن تستهای کوچک و متمرکز، احتمال ورود خطاها به سیستم کاهش مییابد.
- بازخورد سریع: چرخههای کوتاه توسعه در TDD امکان شناسایی و رفع سریع مشکلات را فراهم میکند.
- افزایش تمرکز و بهرهوری: TDD توسعهدهندگان را مجبور میکند تا به صورت متمرکز و مرحلهبهمرحله کار کنند، که این امر به کاهش سردرگمی و تسهیل بازگشت به کار پس از حواسپرتی کمک میکند.
- بهبود همکاری تیمی: با ایجاد تغییرات کوچک و مکرر، اعضای تیم میتوانند بهراحتی روی تغییرات یکدیگر کار کنند و از مشکلات ناشی از تغییرات بزرگ و همزمان جلوگیری کنند.
- تشویق به بازسازی مستمر: TDD ارزش زیادی برای بازسازی قائل است و از انباشت مشکلات طراحی و افزایش هزینههای آینده جلوگیری میکند.
- طراحی بهتر: به دلیل نیاز به تستپذیری، TDD توسعهدهندگان را مجبور میکند تا سیستمهایی با اتصال کم و انسجام بالا طراحی کنند.
- مستندسازی: تستها به عنوان یک مستند اجرایی عمل میکنند و رفتار سیستم را بهوضوح نشان میدهند. این امر در شناسایی فرضیات نادرست و رفع اشکالات موثر است.
مراحل توسعه مبتنی بر تست (TDD) و جزئیات اجرای آن
توسعه مبتنی بر تست یک چرخه تکرارشونده است که به صورت گامبهگام اجرا میشود. هر گام از این چرخه کمک میکند تا تغییرات کوچک و مطمئن به سیستم اضافه شود. این چرخه معمولاً به صورت قرمز، سبز، بازسازی (Red, Green, Refactor) توصیف میشود:
۱. نوشتن تست (مرحله قرمز)
اولین مرحله در چرخه TDD، نوشتن تست است. در این مرحله:
باید دقیقاً مشخص کنید که رفتار جدیدی که سیستم باید اجرا کند چیست.
درباره طراحی سیستم تصمیمگیری میکنید؛ برای مثال، انتخاب نام متدها، نوع بازگشتی، و پارامترها.
تست نوشته شده ابتدا شکست میخورد (قرمز)، زیرا هنوز کدی برای اجرای آن وجود ندارد. این شکست اطمینان میدهد که تست شما معتبر است و عملکرد جدیدی که قرار است اضافه شود، واقعاً موردنیاز است.
نکته: ممکن است تست شما حتی کامپایل نشود، بهویژه اگر با یک سیستم جدید کار میکنید. این اتفاق کاملاً طبیعی است و بخشی از فرآیند TDD محسوب میشود.
۲. نوشتن کد کاربردی برای گذراندن تست (مرحله سبز)
در این مرحله:
تنها به اندازهای کد بنویسید که تست موردنظر سبز (پاس) شود.
از طراحیهای پیچیده یا اضافهکاری اجتناب کنید.
هدف اصلی این است که مطمئن شوید کد نوشته شده، عملکرد موردنیاز تست را برآورده میکند.
نکته: در این مرحله نیازی به نگرانی درباره طراحی زیبا یا بهینهسازی کد نیست؛ هدف اصلی گذراندن تست است.
۳. بازسازی (Refactor)
پس از گذراندن تست:
کد کاربردی را بررسی کنید و هرگونه تکرار یا طراحی ناپایدار را حذف کنید.
بدون تغییر رفتار کد، ساختار آن را بهبود دهید.
علاوه بر کد کاربردی، کد تست را نیز بازسازی کنید. کد تست نیز باید خوانا و قابل نگهداری باشد، زیرا در طول زمان به طور مداوم تکامل مییابد.
نکته: وجود تستهای معتبر این امکان را میدهد که بازسازی کد را با اطمینان انجام دهید؛ زیرا هرگونه تغییر ناخواسته سریعاً شناسایی میشود.
۴. تکرار چرخه
پس از اطمینان از کیفیت کد و تست، به مرحله اول بازگردید و تغییر یا ویژگی بعدی را به همین ترتیب اضافه کنید. این چرخه برای هر تغییر یا بهبود سیستم تکرار میشود.
مزایای چرخه TDD
- تغییرات کوچک و مطمئن: تغییرات به صورت تدریجی و در بخشهای کوچک اعمال میشوند، که این امر ریسک خطا را کاهش میدهد.
- رفع سریع خطاها: اگر تستی شکست بخورد، به راحتی میتوانید علت آن را شناسایی کنید؛ زیرا تغییرات شما محدود و متمرکز هستند.
- بازخورد سریع: اجرای کوتاهمدت و مکرر تستها اطمینان حاصل میکند که سیستم مطابق انتظارات عمل میکند.
- طراحی بهبود یافته: TDD توسعهدهندگان را به نوشتن کدهایی با انسجام بالا و وابستگیهای کم تشویق میکند.
- افزایش کیفیت کلی کد: تستهای مداوم و بهبودهای ساختاری از انباشت بدهی فنی جلوگیری میکنند.
چرا توسعه مبتنی بر تست (TDD)؟ مزایا و کاربردها
پس از آشنایی با چرخه توسعه مبتنی بر تست (TDD)، حالا بهتر است بررسی کنیم چرا باید از این رویکرد استفاده کنیم و چه مزایایی میتواند به ما ارائه دهد.
۱. رویکرد پیشگیرانه به کیفیت نرمافزار
یکی از مزایای اصلی TDD این است که برخلاف روشهای واکنشی که در بسیاری از سازمانها رایج است، یک رویکرد پیشگیرانه برای بهبود کیفیت نرمافزار ارائه میدهد. در رویکردهای سنتی:
- تستها معمولاً بعد از نوشتن کد کاربردی نوشته میشوند، اگر زمانی باقی بماند یا فشار زمانی زیاد نباشد.
- تستها اغلب زمانی نوشته میشوند که یک مشتری یا تیم تست باگهایی را کشف کرده باشند.
- در بسیاری موارد، زمان زیادی صرف دیباگ کردن مشکلات میشود.
در مقابل، با استفاده از TDD:
- از همان ابتدا کیفیت نرمافزار مورد توجه قرار میگیرد.
- احتمال وجود باگ در مراحل نهایی کاهش مییابد.
- خطاهایی که در هنگام اجرای تستها پیدا میشوند، به دلیل کوچک بودن محدوده تغییرات، به سرعت و به راحتی قابل رفع هستند.
۲. کاهش ریسک تغییرات و انعطافپذیری بیشتر
با استفاده از TDD، توسعهدهندگان مجبورند از همان ابتدا نیازمندیها را دقیق بررسی کنند. این بررسی شامل:
- درک عمیقتر از نیازمندیها و مشخص کردن اثرات آنها.
- کشف ابهامات و حل مشکلات نیازمندیها قبل از شروع به نوشتن کد.
این مزیت به خصوص زمانی مفید است که:
- نیازمندیها در مراحل اولیه هنوز ممکن است تغییر کنند.
- تغییرات به راحتی اعمال میشوند بدون این که نیاز به بازنویسی گسترده کد وجود داشته باشد.
۳. سیستم بازخورد سریع
TDD یک سیستم بازخورد سریع ایجاد میکند که در آن تغییرات کوچک به صورت تدریجی و مرحلهبهمرحله اعمال میشوند. این سیستم به چند دلیل موثر است:
- احساس پیشرفت: دیدن موفقیت تستها (شکست و سپس پاس شدن آنها) انگیزه ایجاد میکند و به توسعهدهنده حس پیشرفت مداوم میدهد.
- مدیریت حواسپرتی: با توجه به ماهیت کار توسعهدهندگان که شامل جلسات، مرور کد، و کمک به همکاران است، کار با تغییرات کوچک به جای تغییرات پیچیده و گسترده باعث میشود که بتوانید بعد از هر وقفه سریعاً کار را از جایی که متوقف کرده بودید ادامه دهید.
- پیادهسازی مطمئن: تغییرات کوچک و قابل مدیریت، احتمال خطاهای بزرگ را کاهش میدهد و اشکالیابی را سادهتر میکند.
۴. طراحی بهتر و کد با کیفیتتر
توسعهدهندگان در TDD، علاوه بر کد کاربردی، روی کیفیت تستها نیز تمرکز میکنند. این تمرکز باعث میشود:
- کدی که نوشته میشود، ساختارمندتر، منظمتر، و از تکرارهای غیرضروری خالی باشد.
- تستها نیز خوانا و قابل نگهداری باشند، که در طولانیمدت هزینه نگهداری سیستم را کاهش میدهد.
مزایای دیگر توسعه مبتنی بر تست (TDD) در تیمهای توسعه
وسعه مبتنی بر تست (TDD) نه تنها کیفیت کد را بهبود میبخشد، بلکه فرآیند توسعه را در محیط تیمی نیز سادهتر و موثرتر میکند. در ادامه، به بررسی این مزایا میپردازیم:
۱. تغییرات تدریجی و مداوم
در TDD، تغییرات به صورت تدریجی و کوچک اعمال میشوند، نه به صورت یک مجموعه بزرگ و پیچیده. این ویژگی در کار تیمی مزایای متعددی دارد:
- چکاین مکرر و سریع: توسعهدهندگان میتوانند تغییرات کوچک و آزمایششده خود را به طور مرتب در مخزن کد اعمال کنند. این کار به سایر اعضای تیم امکان میدهد تا بدون نیاز به منتظر ماندن، بر اساس تغییرات انجامشده، کار خود را ادامه دهند.
- جلوگیری از انسداد تیمی: هیچ چیز بدتر از گیر افتادن یک تیم به دلیل تأخیر در اعمال یک تغییر بزرگ نیست. با چکاینهای مکرر، توسعهدهندگان میتوانند به طور پیوسته از پیشرفتهای همکاران خود بهرهمند شوند.
۲. اهمیت بالای بازسازی (Refactoring)
در چرخه TDD (قرمز، سبز، بازسازی)، بازسازی یکی از مراحل کلیدی است. چرا؟
- رسیدگی به مسائل کد از همان ابتدا: صرف زمان کافی برای بازسازی، حتی برای مسائل کوچک، به جلوگیری از انباشت مشکلات طراحی در آینده کمک میکند.
- پرهیز از بازسازیهای پرهزینه و پرخطر: تأخیر در بازسازی میتواند منجر به تغییرات وسیع و پرهزینه در آینده شود که نه تنها زمانبر است، بلکه خطر بروز مشکلات جدید را نیز افزایش میدهد.
- بهبود طراحی: بازسازی منظم باعث میشود طراحی کد تمیز، قابل فهم، و قابل نگهداری باقی بماند.
۳. ایجاد طراحی تمیز و قابل تست
TDD به صورت طبیعی طراحی تمیز و قابل تست را تقویت میکند:
- کاهش اتصالها (Low Coupling): کد باید به گونهای نوشته شود که اجزای مختلف سیستم به حداقل وابستگی به یکدیگر داشته باشند.
- افزایش انسجام (High Cohesion): هر ماژول یا کلاس باید مسئولیت مشخصی داشته باشد.
- طراحی سادهتر: سیستمهای پیچیده و نامنسجم به سختی قابل تست هستند، اما با پیروی از TDD، طراحی مناسب تقریباً اجتنابناپذیر میشود.
۴. مستندسازی از طریق تستها
تستها میتوانند به عنوان مستندات اجرایی عمل کنند:
- شرح رفتار سیستم: تستها نشان میدهند که سیستم چگونه باید عمل کند و چه رفتارهایی انتظار میرود.
- شفافیت تصمیمگیریها: با نگاه به تستها، میتوان تصمیمات و فرضیات توسعهدهنده را هنگام پیادهسازی فهمید.
- راهنمای اشکالیابی: در صورت بروز مشکلات، تستها میتوانند به عنوان مرجع مفیدی برای شناسایی و رفع باگها عمل کنند.
۵. سیستمی قابل نگهداری و توسعهپذیر
با پیروی از TDD، سیستم نهایی به گونهای توسعه مییابد که:
- به راحتی قابل نگهداری است: کد تمیز و تستشده تغییرات آینده را سادهتر میکند.
- اضافه کردن ویژگیهای جدید آسانتر میشود: طراحی مناسب و مستندات اجرایی (تستها) تغییرات بعدی را سریعتر و ایمنتر میسازند.