آیا نوشتن تست، فرآیند توسعه نرم‌افزارتان را واقعا کندتر می‌کند ؟

در حوزه نرم‌افزار تفکری قالب شده مبنی بر اینکه نوشتن تست (Automated Test) وقت‌گیر بوده و ننوشتن تست باعث سریعتر پیش رفتن کارها می‌شود. در این نوشته سعی دارم از تجارب شخصی‌ام در این رابطه نوشته و توضیح دهم چرا چنین تفکری در اکثر موارد اشتباه است.

چرا فکر می‌کنیم نوشتن تست، سرعت توسعه را کند می‌کند ؟

همیشه وقتی صحبت از تست‌نویسی می‌شود، شاید اولین تصویری که در ذهن مخاطب نقش می‌بندد نوشتن صدها خط کدِ تست در قالب ده‌ها Test Case است، آن‌هم برای یک اندپوینت ساده و ناچیز HTTP. حالا تصور کنید یک پروژه با بیش از صد اندپوینت دارید، پس حتما صدها تست‌کِیس و هزاران تست خواهید داشت که نوشتن آنها روزها و هفته‌ها زمان می‌برد.

شما به مدیرتان می‌گویید برای بالا بردن کیفیت پروژه باید تست بنویسیم، او به چشمان شما زُل می‌زند و با چهره‌ای نسبتا عصبانی و لحنی تَحَکُم آمیز به شما می‌گوید:

فقط کارو بزن بره، نمی‌خواد تست بنویسی، باید به دِدلاین‌ها برسیم.

خب به نظر، حق با مدیر شماست. اما اگر برای نوشتن تست، مجبور نباشیم روزها و هفته وقت بگذاریم چطور ؟

نوشتن فقط یک تست چقدر زمان می‌برد ؟

تصور کنید یک اندپوینت HTTP را پیاده‌سازی کرده‌اید که متد آن POST بوده و Payload ی را در Body نیز باید قرار دهید، همینطور در Header آن نیز مقداری را برای این اندپوینت باید ارسال کنید. برای تست آن اگر قصد بر نوشتن Automated Test نداشته باشید، احتمالا از یک کلاینت HTTP مانند Postman استفاده می‌کنید.

پست‌مَن را باز می‌کنید،‌ یک ریکوئست جدید در آن ایجاد می‌کنید، متد را روی POST قرار می‌دهید، آدرس (مسیر) اندپوینت را وارد می‌کنید، وارد تب Body شده و مقادیر آن را وارد می‌کنید، وارد تب Header شده و مقادیر آن را نیز وارد می‌کنید، و در نهایت کلید Send را زده تا ریسپانس را ببینید و فرضا از 200 بودن کد HTTP مطمئن شوید.

شاید تمام این فرآیند حدودا یک دقیقه از شما زمان‌ ببرد.

حالا می‌خواهیم دقیقا همین پروسه را در قالب یک Automated Test بنویسیم. یک فایل Test Case جدید در پروژه ایجاد میکنیم (احتمالا فایل Example را Copy Paste می‌کنیم)، فانکشن زیر را در آن می‌نویسیم:

public function test_making_an_api_request()
{
    $this->post('/api/something', ['name' => 'mehrad'], ['Authorization' => 'Bearer some_token'])->assertStatus(200);
}

و در نهایت در ترمینال کامند مربوط به اجرای تست‌ها را ران می‌کنیم.

به نظر شما این‌کار چقدر زمان می‌برد ؟ بعید است بیشتر از یک دقیقه زمان ببرد.

اگر بخواهیم موارد دیگری مانند اطلاعات درون دیتابیس را نیز چک کنیم، همچنان از نظر زمان تفاوت خاصی نخواهد داشت. فرض کنیم بعد از دیدن نتیجه درخواست در پست‌من، کلاینت دیتابیس خود را باز می‌کنید و از صحت اطلاعات اطمینان حاصل می‌کنید. در زمان نوشتن تست هم چنین کاری را با اضافه کردن این Assertion می‌توانید انجام دهید:

$this->assertDatabaseHas('users', [
    'name' => 'mehrad'
]);

که از نظر زمانِ انجام، تفاوت خاصی باهم ندارند.

نوشتن یک تست چه فایده‌ای دارد ؟

به این سناریو توجه کنید:

تصور کنید ۵۰ اندپوینت در پروژه خود دارید. اگر برای آنها تست ننویسید قاعدتا ۵۰ ریکوئست در پست‌من به ازای هرکدام از این اندپوینت‌ها باید داشته باشید. مدیر شما به سراغتان آمده و از شما می‌خواهد لاجیک (منطق) یکی از اندپوینت‌های موجود را تغییر دهید و شما همین کار را می‌کنید. به عنوان یک برنامه‌نویس مسئولیت‌پذیر حدس می‌زنید که شاید تغییری که در کد داده‌اید Side Effect هایی داشته باشد و به همین دلیل تمام ۵۰ اندپوینتی که در پست‌من دارید را یکبار تست می‌کنید تا مطمئن شوید همه‌چیز سر جای درستش است. نیم ساعت بعد یک باگ کریتیکال به شما ریپورت می‌شود و باید سریع تغییراتی جدید در کد ایجاد کنید. از آنجایی که برنامه‌نویس متعهدی هستید دوباره همه اندپوینت‌ها را تست می‌کنید. روز بعد تسک جدیدی به شما اختصاص داده می‌شود و باید یک اندپوینت جدید به پروژه اضافه کنید، بعد از انجام اینکار دوباره تمام اندپوینت‌ها را اجرا می‌کنید و عملا هر نوع توسعه‌ای که در پروژه می‌دهید، باید یکبار تک تک اندپوینت‌ها را برای اطمینان به صورت Manual (دستی) تست کنید.

حالا تصور کنید از همان ابتدای پروسه توسعه، به ازای هر اندپوینتی که نوشته بودید، همان یک تستِ اصطلاحا Happy Path (تستهایی که فقط حالت و شرایط صحیح را تست می‌کنند) را می‌نوشتید. به ازای هر تغییری که دوباره در برنامه ایجاد می‌کردید، به ازای هر اندپوینت جدیدی که اضافه می‌کردید و ...، فقط کافی بود با یک کامند در ترمینال یکدور تست‌ها را ران کنید تا از صحت بقیه بخش‌های پروژه اطمینان حاصل کنید.

همانطور که می‌بینید نوشتن تست سرعت کار ما را نتنها کاهش نمی‌دهد،‌ بلکه افزایش می‌دهد. وقتی از تست نویسی صحبت می‌کنیم، منظور این نیست که از همان ابتدا صدها تست‌کیس بنویسیم. باید شرایط پروژه، نوع پروژه و پارامترهای دیگری را در نظر گرفت.

اگر دِدلاین های کوتاه مدت دارید، به تست‌های Happy Path اکتفا کنید. نوشتن آنها شما را در ادامه توسعه نجات خواهند داد. اگر ددلاین‌های آزادتری دارید تست‌های Corner Case (تست‌هایی که حالت‌های خاصِ برنامه را تست می‌کنند) را نیز بنویسید.

سناریوهای پیچیده‌تر

سناریو‌ی اول

تصور کنید چیزی که باید تست کنید، صرفا یک اندپوینت نیست، و باید یک فلویی از اندپوینت‌ها را مورد تست قرار دهید. مثلا در اندپوینت اول کاربر باید ثبت‌نام شود و یک کد تایید که برای مدت ۳۰ ثانیه معتبر است برایش ساخته شود، در اندپوینت بعدی باید کد تایید را وارد کند تا آدرس ایمیلش تایید شود، و در اندپوینت بعد باید اطلاعات تکمیلی‌اش را کامل کند تا از این پس بتواند از پنل و داشبورد سایت ما استفاده کند.

تستِ چنین فلویی با پست‌من سخت و خسته‌کننده خواهد شد، زیرا هم در حین توسعه همین اندپوینت‌ها، و هم در ادامه کار پروژه که تغییری در بقیه بخش‌های پروژه ایجاد می‌کنید، باید بارها و بارها این فلو را به ترتیب و به صورت دستی در پست‌من اجرا کنید.

در مقابل با داشتن Automated Test برای چنین فلویی، همیشه باخیال راحت‌تر و سرعت بیشتری می‌توانید بخش‌های جدید را توسعه دهید و یا در کدهای فعلی تغییر ایجاد کنید.

سناریوی دوم

تصور کنید در پروژه‌‌ای مشغول به کار هستید که معماری پیچیده‌تری دارد. مثلا از سه دیتابیس همزمان استفاده می‌کند و اندپوینت‌هایی که در پروژه وجود دارند، بعضا روی دو یا هر سه دیتابیس تغییراتی ایجاد می‌کنند.

تست کردن اندپوینت‌های چنین پروژه‌ای به صورت Manual به شدت طاقت‌فرسا و زمان‌بر خواهد بود. همینطور احتمال خطای انسانی در این شرایط بیشتر وجود دارد. در مقابل با داشتن Automated Test سرعت تست و توسعه پروژه به شدت افزایش می‌یابد و احتمال خطا نیز کاهش خواهد یافت.

سناریوی سوم

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

با بیشتر شدن تیم‌های نرم‌افزاری چنین دغدغه‌هایی هر روز بیشتر خواهد شد و راه توسعه صحیح در این شرایط، داشتن Automated Test است، تا هر تغییری که در کد می‌دهیم، به واسطه تست‌ها مطمئن باشیم که کار سایر اعضای تیم دچار مشکل نمی‌شود و از صرفِ ساعت‌ها وقت برای دیباگِ پروژه جلوگیری کنیم.

سناریوی چهارم

تصور کنید به تازگی وارد پروژه‌ای شده‌اید که سورسی لِگِسی (Legacy) داشته و دارای میلیون‌ها خط کد بوده که طی چند سال برنامه‌نویس‌های مختلف روی آن کار کرده‌اند. در چنین شرایطی اگر Automated Test در پروژه وجود نداشته باشد، هر تغییری که به عنوان عضو جدید در پروژه می‌دهید، به احتمال خیلی خیلی زیاد Side Effect هایی را در سایر بخش‌های پروژه ایجاد می‌کند که ساعت‌ها و حتی روز‌ها برای دیباگ از شما زمان خواهد برد.

سناریوی پنجم

تصور کنید خودتان به صورت انفرادی پروژه‌ای را از صفر شروع کرده‌اید و نسبت به تمام اجزای پروژه تسلط دارید. بعد از مدتی توسعه (فرضا بعد از ۱۰ ماه) ناگزیر یکسری از ریز جزییات پروژه از حافظه شما خارج شده و در زمان توسعه یا تغییر در بخش‌های مختلف، می‌بینید که باگ‌هایی ایجاد می‌کنید (که شما را درگیر ساعت‌ها دیباگینگ می‌کند). در این شرایط هم داشتن Automated Test ها به شکل قابل توجهی به شما کمک می‌کنند تا سرعت و دقت کارتان افزایش بیابد.

سناریوی ششم

ترکیب دو یا سه مورد از سناریوهای بالا.

توصیه‌ای برای پروژه‌هایی که هیچ تستی ندارند

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

در این شرایط می‌توانید کار تست نویسی را فقط برای توسعه‌های جدید انجام دهید. همانطور که بالاتر نیز اشاره شد، تست‌های Happy Path برای فیچر‌های جدیدی که توسعه می‌دهید بنویسید. در همین حین با برنامه‌ریزی تست‌های Happy Path برای سایر بخش‌های پروژه نیز بنویسید تا به مرور زمان و بعد از چند ماه تمام پروژه تست‌های Happy Path داشته باشد. بعد از آن با همین روش می‌توانید تست‌های Corner Case را نیز اضافه کنید.

جمع‌بندی

در این نوشته توضیح داده شد که چرا نوشتن تست بر عکس تصور قالب، باعث افزایش سرعت توسعه ما خواهد شد. شاید در بعضی شرایط (برای نمونه پروژه‌هایی که یک برنامه‌نویس به صورت مقطعی انجام داده و بعد از تحویل کار سرنوشت پروژه دقیقا مشخص نخواهد بود و مثال‌هایی از این دست) نوشتن Automated Test صرفه‌ی اقتصادی نداشته باشد. اما به طور کلی اگر به اصول و ابزارهای اینکار تسلط داشته باشید، سرعت توسعه شما افزایش یافته و آینده پروژه مطمئن‌تر خواهد بود.