تجربه‌ای از تست خودکار نرم افزار و توسعه تست محور

این متن یک تجربه شخصی از نوشتن تست های خودکار برای یک محصول نرم‌افزاری هست و نتایجی که حین این تجربه به دست آوردم و به نظرم اومده ارزش نوشتن و ثبت کردن رو داشتن (به خصوص برای خودم). در شرکتی که من براش کار میکنم تعدادی محصول نرم افزاری تولید شده که چندتا از این نرم‌افزارها بستری هستن که به عنوان هسته اصلی تمام خدمات شرکت کار میکنن و نرم‌افزارهای دیگه معمولا از طریق همین سیستم‌های اصلی میتونن خدمات ارايه کنن. اخیرا شرکت تصمیم گرفت هسته‌ای‌ترین سیستم (و همچنین بزرگترین از لحاظ تعداد فانکشنالیتی‌ها) که برای بیش از یک دهه مشغول توسعه و نگهداریش بود رو بازنویسی کنه و معماریش رو بهبود بده (البته با حفظ حداکثر backward compatibility ممکن برای این محصول). تا همینجا میشه تصور کرد که این تصمیم چقدر برای همه ترسناک بود. میشه گفت همه نرم‌افزارهای دیگه به نحوی با این سیستم بزرگ سر و کار دارن و نسخه جدید باید میتونست همه کارکردهای نسخه قبلی رو حفظ کنه یا بهبود بده. خوشبختانه من از روز اول توی این پروژه بازنویسی نبودم (ورود به تیم در زمان استارت پروژه بزرگ خیلی سخت‌تر از وقتی می‌بود که پروژه تا حد خوبی رو روالش افتاده بود و من فقط در ادامه همون روند کمک میکردم). من وقتی به تیم اضافه شدم که سیستم نسخه‌ای داشت که آماده برای نصب دولوپرهای دیگه بود. ولی هنوز تعدادی از بخشهای سیستم کار نمیکردن (بعضی وقتها این بخش‌ها نوشته شده بودن اما درست کار نمی‌کردن، یا رفتارشون تغییر کرده بود و این تغییرات هنوز هیچ جایی داکیومنت نشده بودن ).طبق روال پروژه ما همزمان با توسعه، تعداد زیادی تست واحد (unit test) برای بخشهای مختلف می‌نوشتیم که تا حد ممکن جلوی خطای برنامه‌نویس‌ها رو بگیرن ولی مثلا چیزی که در مورد تغییر رفتار گفتم که باعث عدم تطابق کامپوننت‌ها می‌شد مشکلی بود که با تست واحد قابل شناسایی نبود. شناسایی این مشکل‌ها معمولا توسط تستهای دیگه انجام میشه که بعد از تست واحد اجرا میشن. به این تستها معمولا I&T یا integration testing گفته میشه، ولی ما این تستها رو تو شرکت استفاده نمی‌کردیم. من دلیل‌اش رو نمی‌دونم، ولی به هر حال نوشتن این تستها برای سیستمی که ورودی و خروجی خیلی از کامپوننت‌ها در حال تغییره همزمان خوب و همزمان بد هست. این تست‌ها میتونن جلوی مشکل عدم تطابق در کامپوننت‌ها رو بگیرن، ولی این برای زمانیه که سیستم تا یه حدی پایدار شده باشه. تو مرحله‌ای که ما بودیم، رفتار هر بخشی از سیستم ممکن بود هر روز عوض بشه. در این صورت به ازای هر تغییر، کسی که کد رو تغییر داده بود (یا یک فرد دیگه) باید زمانی بیشتر از نوشتن اون تغییرات رو مشغول عوض کردن تستها می‌بود تا بتونه سیستم (Continuous Integration) CI رو دوباره راه بندازه ( پلن CI معمولا بعد از هر تغییر در کدهای نرم افزار اون رو بیلد میکنه و تستهای نوشته شده رو روی این سیستم اجرا میکنه و اگه تستی خطا بده، اون فرآیند بیلد fail میشه).یک نکته دیگه این بود که حداقل در جلساتی که من توش بودم، چندین بار در مورد اینکه تست‌های واحد چجوری و در چه فرآیندی نوشته بشن بحث می‌شد (مثلا اینکه برای کدوم دسته تیکت‌ها باید چه تست‌هایی حتما نوشته بشن؟ تست‌ها باید قبل از یک رفع باگ نوشته بشن یا بعدش؟ تستها رو کسی که باگ رو حل کرده باید بنویسه یا یک برنامه نویس دیگه؟ بهتر نیست تستها رو به یک فرد مشخص بسپریم؟ لازمه یکی دیگه این تستها رو بعد از تموم شدن کار بخونه و چک کنه؟ و...). قطعا طراحی فرآیند برای تستهای I&T وقت و انرژی خیلی بیشتری نسبت به تست واحد لازم داره و از این لحاظ هم مدیریت فرآیندهای مربوط به اونها میتونست سخت باشه و از بیشتر از زمانی که برای تستهای واحد صرف شده رو لازم داشته باشه.برگردیم به داستان، روند کار روی پروژه به شکلی که اشاره کردم ادامه داشت و طبیعتا همه اعضای تیم استرس این رو داشتن که اون بخشی از نرم‌افزار که روش کار میکنن در روز نهایی، رفتار مورد انتظار رو خواهد داشت یا نه؟ تو این شرایط بودیم که به من تسک جدیدی دادن و گفتن که ما یه دسته تست E2E (End-to-End) قدیمی نوشته بودیم که سالی یه بار رو نسخه قدیمی اجراشون می‌کردیم، حالا تو اونا رو انتقال بده به سیستم جدید و یک لیست هم دادن که برای این لیست از فیچرهای جدید و قدیمی که تست ندارن هم تست‌های جدید اضافه کن. این تست‌ها قراره به پروسه CI(Continuous Integration) نرم‌افزار جدید اضافه بشن.عکس از اینجامن هم با هر ضرب و زوری بود این تسک رو براشون انجام دادم، ولی چون سیستم رو کامل نمیشناختم، خیلی چیزها رو حین نوشتن تستها یاد میگرفتم! (واسه اینکه عمق فاجعه رو نشون بدم این رو هم بگم که با بعضی چیزها هم با آزمون و خطا آشنا می‌شدم!! چون هنوز هیچ داکیومنتی نداشتن). این سیستم طوری هست که تعداد آدماهایی که کل سیستم رو میشناختن، یک یا دو نفر بیشتر نبودن و طبیعتا وقتشون برای شرکت خیی ارزشمندتر از این بود که من هر نیم ساعت برم و ازشون در مورد فیچری که دارم تست میکنم توضیح بخوام.بعد از اینکه تست‌ها رو نوشتم خیلی نگران این بودم که شاید من تستها رو به درستی ننوشته باشم. چند تا نگرانی بودند که رها نمیکردن:اگه من تست رو اشتباه بازنویسی کرده باشم چی؟اگه فراموش کردم بخشی از تست رو بازنویسی کنم چی؟همه جوانب فیچری که باید تست بشن رو در نظر گرفتم؟ یا فقط یه حالت روتین رو تست کردم که شاید ارزش کافی برای تست کردن رو نداشت.مطمئنم که رفتار مورد انتظار یک فیچر از سیستم رو اونطوری که انتظار میره تست میکنم، نه اونطوری که خودم با آزمون و خطا بهش رسیدم؟ (این یعنی من به اشتباه رفتار غلط رو به عنوان رفتار صحیح در نظر گرفتم و تستی نوشتم که وقتی رنگش سبزه، یعنی سیستم داره غلط کار میکنه!) و این سوال که اون تستی که نوشتم اصلا درست ارزیابی میکنه؟ مثلا نکنه من با یک API تعامل داشتم و با اینکه اون نتیجه اشتباه یا حتی پیغام خطا برمگیردونه ولی من بخش اشتباهی از پاسخ برگردونده شده رو چک میکنم؟این سوالات و استرس کلی پروژه ذهنم رو مشغول کرده بود که اگه کد یکی خطا داشته باشه و تست من که انتظار میرفته اون خطا رو پیدا کنه همچنان سبز بمونه خیلی برای من بد میشه.به همین خاطر رفتم دنبال مطالعه در مورد تست‌های خودکار تا ببینم دیگران با این مشکل چیکار میکنن.برای اینکه متن طولانی نشه متن رو به به چند قسمت شکستم که در ادامه همین پست منتشر خواهند شد. قسمت بعدی جستجوی من به دنبال راه حل‌های قابل استفاده برای سوالات بالا خواهد بود.