خلاصه: هرم تست یک مدل عالی برای طراحی Test Portfolio شماست. با این حال، هنگامی که شما از تست Progression(پیشرفت) به تست Regression(رگرسیون) شیفت میکنید، پایین هرم متمایل به سقوط کردن است. تستها شروع به شکست(Fail شدن) میکنند، و تعداد Unit Testهای عملیاتی در پایه هرم شما دچار سایش و تخریب میشوند. اگر شما منابع توسعه لازم برای نگهداشت Continuous Unit Test را ندارید، هنوز هم چیزهایی وجود دارد که میتوانید انجام دهید.
هرم تست مدل ایده آل برای تیمهای چابک(Agile) است که هنگام طراحی Test Portfolio خود از آن استفاده میکنند. Unit Test یک پایه محکم برای درک این موضوع را شکل میدهد که کد جدید به درستی کار میکند.
- آنها به راحتی کد را پوشش میدهند: توسعهدهندهای که کد را نوشته است، شخصا فردیست که برای تأیید این موضوع که تستهای وی، کدش را پوشش میدهد. یا نه، واجد شرایط است. توسعهدهنده مسئول درک آن چیزهاییست که هنوز پوشش داده نشده و همچنین Test Methodهایی را ایجاد میکند که این Gap و شکاف را پر میکنند.
- آنها سریع و ارزان هستند: Unit Test میتواند به سرعت نوشته شده و در عرض چند ثانیه اجرا شود. بدین منظور فقط نیاز به Test Harnessهای ساده است(در مقایسه با محیطهای تست گستردهتر که برای تست سیستم نیاز است).
- آنها قطعی هستند: هنگامی که Unit Test قابل انجام نباشد، شناسایی اینکه چه کدی باید بازبینی(Review) و اصلاح(Fix) شود نسبتا آسان است. این مانند این است که بخواهید یک سوزن را در میان چند پر کاه پیدا کنید، تا اینکه بخواهید آنرا در یک انیار کاه بیابید.
با این وجود، یک مشکل در این مدل وجود دارد: وقتی که شما از Progression Test(بررسی اینکه آیا Functionality جدید که به سیستم اضافه شده است به درستی کار میکند یا خیر) به سمت تست رگرسیون (بررسی اینکه آیا یک قابلیت توسط تغییرات انجام شده تحت تاثیر قرار میگیرد یا خیر) شیفت میکنید، پایین هرم از بین میرود. در این حالت هرم شما از بخش تحتانی مقداری دچار سایش شده، و شکلی لوزی شکل به خود میگیرد(این تصویر در پایین ارائه شده است):
این چیزیست که اخیرا در هنگام بررسی متدهای Unit Testing در بین تیمهای بالغ و پیشرفته Agile در میان دادههایی که جمعآوری شده است، بروز نموده است. در هر Sprint، توسعهدهندگان در مورد نوشتن تستهای مورد نیاز برای اعتبارسنجی هر داستان کاربری(User Story) متعصب هستند. به طور معمول، پاس شدن تستهای Unit یک بخش کلیدی از Definition of Done یا DoD است، و این امری اجتناب ناپذیر است. با پایان یافتن اکثر اسپرینتها، یک پایه محکم از تستهای Unit جدید وجود خواهد داشت که در تعیین این موضوع که کد جدید به درستی اجرا میشود(یا خیر) و آیا مطابق با به انتظارات است، بسیار حیاتی مینماید. دادههای استخراج شده میگوید این تستها معمولا حدود ۷۰ درصد از کد جدید را پوشش میدهند.
از اسپرینت بعدی، این تستها به رگرسیون تبدیل میشوند. در بسیاری از رویکردهای چابک، اندک اندک آنها شروع به Fail شدن را آغاز میکنند(تعداد Unit Testهای عملیاتی در پایه هرم تست و نیز سطح اعتماد Test Suiteای که یک بار ارائه شده است،دچار سایش میشود).
پس از چند تکرار(Iteration)، همان Unit Testها می که یک بار پوشش ۷۰ درصدی را بدست آوردند، تنها یک پوشش حدودا ۵۰ درصدی روی Functionality اصلی را به ارمغان خواهند آورد. دادههای یافته شده میگویند مقدار درصد مذبور پس از چندین تکرار به ۳۵ کاهش مییابد و پس از شش ماه به طور طبیعی به ۲۵ درصد کاهش خواهد یافت.
این فرسایش ظریف میتواند خطرناک باشد، اگر شما به تغییر کد نیازی ندارید، انتظار میرود Unit Test خود را به صورت یک Safety Net انجام دهید.
چرا Unit Test دچار سایش میشود؟
تست Unit به دلایل مختلف ساییده شده و تخریب میشود. با وجود اینکه تست Unit از لحاظ نظری پایدارتر از سایر انواع تستها(مانند تستهای UI) است، اما آنها نیز ناگزیر خواهند بود در طول زمان شکست بخورند.
کدها منبسط میشوند، Refactor میشوند، و آنچنانکه تکامل مییابد اصلاح هم میشود. در بسیاری از موارد، تغییرات پیادهسازی به اندازه کافی قابل توجه است که برای به روز رسانیهای تست Unit لازم است. تغییرات کد برای دفعه بعد، این واقعیت را نشان میدهد که متدهای اصلی تست و Test Harness بیش از حد در پیادهسازی فنی به هم پیوسته هستند(که دوباره نیازمند به روز رسانی Unit Test است).
با این حال، این به روز رسانی ها همیشه ساخته نمیشوند. پس از بررسی توسعهدهندگان روی تستها برای یک User Story جدید، آنها برای انتخاب و تکمیل یک داستان کاربری دیگر تحت فشار قرار میگیرند؛ و همینطور بعدی و بعدی. هر یک از این داستانهای کاربری جدید نیاز به انجام تستهای Unit دارد که باید انجام شود، اما اگر داستانهای قدیمی شروع به Fail شدن کنند، چه اتفاقی خواهد افتاد؟
معمولا هیچ اتفاقی نخواهد افتاد. در این وضعیت معمولا نارساییها(Failure) نادیده گرفته میشوند(آیا تمام تستها باید برای شفافسازی یک گیت کیفیت CI/CD پاس شوند) و یا اینکه تستهای مشکل دار غیرفعال میگردند. از آنجاییکه توسعهدهنده کسیست که این کد را نوشته و همچنان به حرکت خود ادامه میدهد، حل و فصل مناسب این Failureها، برای شناسایی دوباره کدی که خیلی وقت پیش نوشته شده و احتمالا تا کنون به بوته فراموشی سپرده شده است، و ایضا تشخیص اینکه چرا تست Fail شده است، و همچنین چگونگی رفع آن، به خود کدنویس مربوطه نیاز داریم. این موضوع بیاهمیت نیست و میتواند پیشروی با سرعت بالای فعلی را مختل کند.
صادقانه بگویم، نگهداشت Unit Test اغلب یک بار انجام میشود، که آن هم برای توسعهدهندگان شکل تحمیلی دارد. برای این منظور کافیست فقط Stack Overflow یا جوامع مشابه با آنرا جستجو کرده و سوالات مربوط به توسعهدهندگان در رابطه با Unit Test Maintenance را بخوانید.
چگونگی ایجاد موازنه در ساییدگی
میدانم که برخی از سازمانهای استثنایی نیاز به نگهداشت Unit Test دارند و حتی منابع مناسبی برای آن اختصاص میدهند. با این حال، چنین سازمانها و شرکتهایی تمایل دارند سازمانهایی با منابع توسعهایِ لوکس برای تست باشند. بسیاری از شرکتها برای ارائه حجم و دامنه نرمافزاری که Business از آنها انتظار دارد در تلاش هستند، اما آنها به سادگی قادر به تغییر منابع توسعه به تست اضافی نیستند.
اگر سازمان شما منابع توسعهایِ ساده لازم برای نگهداشت Continuous Unit Test را نداشته باشد، چه کاری میتوانید انجام دهید؟
یکی از گزینهها این است که تسترها برای پوشش گمشده(Lost Coverage) از طریق تستهای انعطافپذیر که میتوانند ایجاد و کنترل نمایند، به جبران این کمبود بپردازند. تسترهای حرفهای متوجه هستند که طراحی و نگهداشت تست کار اصلی آنهاست و در نهایت با موفقیت و کارآمدی Test Suite ارزیابی میشود. بیایید صادق باشیم: کدامیک بیشتر برای ادامه تستهای فعلی محتمل است:
- توسعهدهندگانی که تحت فشا بالا برای تحویل سریعتر کد تلاش میکنند
- یا تسترهایی که برای یافتن مسائل مهم پاداش میگیرند(یا به خاطر چشمپوشی روی آنها متهم هستند)
پاسخ چیست؟
در سازمانهای موفقی که تحت مطالعه قرار گرفتهاند، تسترها خطر سایش Unit Test را با اضافه کردن تستهای سطح Integration(عمدتا در سطح API، آن هم زمانی که امکانپذیر است) جبران میکنند. این امر آنها را قادر میسازد تا “Safety Net مربوط به تشخیص تغییر” را بدون ایجاد اختلال در پیشرفت سریع توسعهدهندگان بازگردانند.