آیا تا به حال برای برنامه‌ای که نوشته‌اید تست نوشته‌اید و یا جزو آن دسته از افرادی هستید که تست نویسی را یک کار بی‌فایده تصور می کنند؟ آیا تاکنون برای شما پیش آمده است که اول تست بنویسید و بعد برای آنکه تست شما پاس شود کد نوشته باشید؟ یک لحظه صبر کنید! اول تست بنویسیم و بعدش برای اینکه تست پاس شود برنامه بنویسیم؟ به نظر غیرمنطقی و مسخره می آید. شاید در نگاه اول برای شما مسخره باشد اما این یک روش برنامه نویسی است. چون ما قبلاً یاد گرفته‌ایم که برنامه‌ای بنویسیم که یک مشکلی را حل کند به صورت مستقیم شروع به نوشتن کد کنیم و خیلی به فکر تست نوشتن نبودیم. حال اگر کسی از ما بخواهد که اول برای مشکل تست بنویسیم و بعد برای آنکه تست ها پاس شوند برنامه بنویسیم شاید از نظر ما عجیب باشد. اما این روش واقعاً وجود دارد و نام آن برنامه نویسی تست محور یا Test-Driven development است.

یکی از کابوس های برنامه نویسی این است که برنامه‌ای که نوشتیم را تغییر دهیم و بعد از آن تغییر، یک جای دیگر برنامه به مشکل بخورد یا درست کار نکند. اینجاست که به مزایای تست نویسی پی می‌بریم و آرزو می‌کنیم که کاش برای هر قسمت برنامه تست نوشته بودیم که بتوانیم برنامه را با خیال راحت تغییر دهیم و نگران نباشیم که یک جای دیگر برنامه خراب می شود.

برنامه نویسی تست محور یا TDD روشی است که راه حلی برای این مشکل و کابوس دارد. در این مقاله می‌خواهیم ابتدا بفهمیم که TDD چیست و این روش را بررسی کنیم ببینیم واقعاً این روش کارایی دارد یا خیر.

TDD چیست؟

به صورت رسمی TDD به این شکل تعریف می‌شود که یک فرایند توسعه و تولید نرم‌افزار است که برنامه نویسان قبل از نوشتن برنامه اصلی ابتدا تست های آن را می نویسند. اما فقط همین نیست. TDD یک تغییر ذهنیت و یک فرایند طراحی نرم‌افزار است. در TDD اول کار ما انتظارات خود را از ریزترین بخش‌های برنامه را به صورت تست مستند سازی می‌کنیم. یعنی هر بخش برنامه باید به چه شکل نوشته شود. مثلاً برای یک تابع قبل از اینکه آن را بنویسیم اول آن را طراحی می‌کنیم و بعد به کمک تست ها انتظارات را پیاده‌سازی می‌کنیم که این تابع اگر مثلاً ۲ ورودی 5 هزارتومانی بگیرد باید خروجی اش ۱۰ هزار تومان باشد. پس قبل از نوشتن کد اصلی تابع مشخص می‌کنیم که ورودی این تابع چیست و خروجی مورد انتظار چه چیزی باید باشد. همچنین نکته مهمی که وجود دارد این است که هنگام طراحی مشخص می‌کنیم که اگر ورودی اشتباه داده شد چه اتفاقی باید بیفتد. بعد همه ی این‌ها را با تست پیاده‌سازی می‌کنیم و در نهایت کدی می نویسیم که این تست ها را پاس کند.

چرخه TDD چیست؟ قرمز-سبز-بازسازی (Red-green-Refactor)

کل فرایند TDD از سه مرحله تشکیل شده است که شما برای پیاده‌سازی هر بخش از برنامه باید این سه مرحله را طی کنید.

۱. قرمز: نوشتن یک تست شکست‌خورده

شما ابتدای کار یک تست می نویسید که شکست بخورد. دقت کنید که ابتدا هیچ کدی در مورد مسأله شما وجود ندارد و شما شروع به تست نوشتن می کنید. دقت داشته باشید که خطاهای کامپایلری مانند نشناختن تابع یا کلاس هم جزوی از مرحله قرمز است. وقتی تست نوشته شده را اجرا کنید به خطا می‌خورد و تست پاس نمی شود.

۲. سبز. نوشتن حداقل کد برای پاس شدن تست.

در این مرحله شما باید فقط و فقط به اندازه‌ای کد بنویسید که تست پاس شود و سبز شود. دقت کنید که دراین مرحله شما نباید به فکر پیاده‌سازی منطق اصلی برنامه نویسی باشید و فقط باید کاری کنید که تست سبز شود حتی اگر کدی می نویسید احمقانه باشد. مثلاً اینکه فقط یک خط return 4 بنویسید. در این مرحله هدف این است که تست سبز شود.

۳ بازسازی (Refactor): تمیز کردن کد

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

حال همین چرخه را باید برای کارهای بعدی برنامه باید تکرار کنیم.

آیا TDD بهره وری را بالا می برد؟

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

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

فواید TDD

۱. کاهش بسیار چشمگیر زمان debugging و خطایابی

برنامه نویسان درصد بالایی از وقت خودشان را به جای اینکه در حال نوشتن کد باشند درگیر خطایابی و دیباگ کردن هستند. حال اگر محل باگ برنامه مشخص نباشد و منشاء خطا معلوم نباشد این زمان بسیار بیشتر هم خواهد بود. اما در TDD وقتی یک تست پاس نمی‌شود مشخص است که منشاء خطا کجا است و مستقیم به سراغ کد مشکل دار می‌رویم و آن را درست می‌کنیم و معمولاً خطا روی کدی است که به تازگی آن را نوشته‌اید یا تغییر داده اید.

۲. عدم نگرانی و ترس از refactor کردن.

اگر TDD را به درستی پیش برده باشید دیگر نگران تغییر کدهای قدیمی و کثیف ندارید. زیرا بعد از هر تغییر کوچک یک بار تست ها را اجرا می‌کنید تا مطمئن شوید جایی از برنامه به مشکل نخورده است و بعد دوباره شروع به بهبود برنامه می‌کنید بدون اینکه اضطراب این را داشته باشیدکه برنامه درست کار نکند. پس با خیال راحت کدهای قدیمی را ریفکتور می کنید.

۳ کد تمیزتر و طراحی بهتر

در TDD کدهای نوشته شده تست پذیر هستند. کد برای اینکه تست پذیر باشد باید ساده و کوتاه باشد و وابستگی غیرضروری نداشته باشد و وابستگی‌های کمی داشته باشد. این همان تعریف کد تمیز و طراحی خوب است. پس TDD کد تمیزی به شما تحویل می‌دهد.

۴ کد مستند

همیشه مستندسازی جزوی از کار برنامه نویسی بوده و هست. مثلاً شاید فراموش کنید که کدی را که یک سال پیش نوشته‌اید چه کاری انجام می‌دهد و هدف از نوشتنش چه بود. اگر بخواهیم برای آن توضیحات متنی بنویسیم مجبوریم که در هر بار تغییر کد مستندات متنی را نیز بروزرسانی کنیم که تجربه نشان داده این اتفاق نمی‌افتد و در آخر توضیحات متنی بلااستفاده و بی‌ربط خواهند بود. ولی در TDD اینگونه نیست. زیرا که تستی که نوشته شده است کد برنامه است و دقیقاً بیان می‌کند که کلاس x و تابع y دقیقاً برای چه کاری نوشته شده اند. یعنی تست ها مستندات کد ما هستند.

TDD بهتر است یا اول برنامه بنویسیم و بعداً برای آن تست بنویسیم؟ (Test-Last)

تا اینجای کار شاید بگویید خب این‌ها مزایای تست نویسی بود و قبول کنید که تست نویسی کمک کننده است ولی ترجیح بدهید که اول کد اصلی را بنویسید و بعداً تست های آن کد را تولید کنید یا مثلاً کد اصلی را خودتان بنویسید و تست ها به هوش مصنوعی واگذار کنید.

روش Test-last شاید در تئوری خوب به نظر برسد ولی در عمل وتجربه شکست می‌خورد. زیرا:

۱. کدی که شما نوشته‌اید شاید تست پذیر نباشد

برای مثال شما کدی نوشته‌اید که مستقیماً به دیتابیس متصل است و در یک تابع ۲۰ تا کار را انجام می‌دهد. در این صورت یا نمی‌توان برای آن تست نوشت یا تست نوشتن به‌قدری سخت و زمان بر خواهد بود که قید تست نوشتن را خواهید زد.

۲. سوگیری تأیید (Confirmation bias)

سوگیری تأیید به این معنی است که کدی را که خودتان نوشته‌اید مثل کاردستی شماست و دوست دارید که بقیه آن را تأیید کنند و نه اینکه به چالش بکشند. با این حال اگر برای آن تست هم بنویسید تستی خواهید نوشت که آن را تأیید کند و نقاط کور آن و بخش‌های مشکل دار آن را نخواهید دید. اما در TDD تست قرمز کد شما را اول به چالش خواهد کشید.

چه زمانی نباید از TDD استفاده شود؟

TDD هم یک روش است و با اینکه مزایای زیادی دارد شاید همه جا بهترین انتخاب نباشد. در حالت‌های زیر استفاده از TDD توصیه نمی شود:

  • کدهای دور ریختنی (Throw-away code) وقتی که می‌خواهید یک ایده را سریع تست کنید و قرار نیست کدی که می نویسید بخشی از یک برنامه بزرگ باشد.
  • کدهای آزمایشی: وقتی که دارید با یک API و یا یک تکنولوژی جدید کار می‌کنید و فقط می‌خواهید بدانید که چگونه کار می کند.
  • پروژه های ساده UI-based: وقتی که منطق خاصی در برنامه شما وجود ندارد و یک رابط کاربری طراحی شده است و logic برای آن لازم نیست. مثلاً یک دکمه روی صفحه است که وقتی روی آن کلیک می‌کنیم بک گراند صفحه تغییر می کند.

نکات پایانی

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

جمع‌بندی

پس گفتیم که TDD بهره وری برنامه را بالا تر می برد. اما این به معنای نوشتن کد کمتر نیست و برعکس کد بیشتری در TDD لازم است که نوشته شود ولی کیفیت نرم‌افزار را بسیار بالا می‌برد و برنامه قابلیت نگهداری بیشتری دارد. دقت کنید که TDD سرعت شما را در دو ماراتن بالا می‌برد نه در دو ۱۰۰ متر.

برای اینکه TDD را یاد بگیرید و قدم به قدم در آن پیشرفت کنید سعی کنید تسک بعدی که به شما سپرده می‌شود را با آن پیاده‌سازی کنید. اوایل به خاطر تغییر ذهنیت کار شما سخت خواهد بود ولی کوتاه نیایید و ادامه دهید.