تست کردن چیست و چرا برنامه نویسی را آسان تر می کند

در این مقاله قدم به قدم بررسی خواهیم کرد که مزایای تست کردن به صورت اتوماتیک چیست؟ و چرا نوشتن تست برای برنامه مان باعث راحت تر شدن برنامه نویسی می شود ،چرا برنامه نویسان حرفه ای و با تجربه ترجیح می دهند برای برنامه شان تست بنویسند و چرا بعضی از برنامه نویسان این کار را نمی کنند

تست
تست

به طور کل چرا برنامه را تست می کنیم

کدی قابل اعتماد است که خطا ندهد و عملیاتی که از آن انتظار می رود را درست انجام دهد. اگر کدی که نوشتیم یک بار در برنامه مان اجرا شد به این معنا نیست که قابل اعتماد است.خب برای اعتماد پیدا کردن از کد خودمان چه کار کنیم؟

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

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

مشکلات و محدودیتهای تست کردن به صورت غیر اتوماتیک یا دستی

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

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

مشکل دوم : سخت بودن یا زمانبر بودن بازسازی مراحل قبل از رسیدن به مرحله تست

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

مشکل سوم : زیاد بودن وضعیت های دستگاه یا برنامه ما

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

مشکل چهارم : عدم تست پذیری به صورت دستی

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

مشکل پنجم : هزینه زیاد برطرف کردن باگ بعد از انتشار برنامه

هزینه ای که باید برای برطرف کردن یک باگ بعد از انتشار برنامه بپردازیم می تواند به مراتب بیشتر از هزینه ای باشد که می توانیم برای برطرف کردن آن قبل از انتشار بپردازیم. یکی از دلایلی که در شرکت‌های حرفه‌ای اقدام به تست برنامه به صورت اتوماتیک می کنند یا در این زمینه سرمایه گذاری می کنند همین مسئله می باشد ، صرفه جویی در هزینه ها. در تحقیقات انجام شده توسط لینکدین، این آمار به دست آمد که هزینه ای که برای برطرف کردن یک باگ بعد از انتشار اپلیکیشن می شود می تواند ۴ تا ۵ بار بیشتر از آن هزینه برای برطرف کردن همان باگ قبل از انتشار باشد. علاوه بر هزینه های مالی هزینه های دیگری نیز می تواند وجود داشته باشد. مثل از دست دادن کاربران برنامه، خطشه دار شدن اعتبار برنامه نویس یا شرکت ، عدم رضایت مشتریان و …

مشکل ششم : طولانی شدن زمان توسعه

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

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

یکی دیگر از مسائلی که برنامه نویسانی که از نوشتن تست برای برنامه شان فرار می کنند در نظر نمی‌گیرند ، صرف انرژی است که در دیباگ کردن و ریفکتور کردن های بی مورد از دست می دهند. این مسئله برای بسیاری از ما پیش آمده است همان زمان هایی که مدام صرف دیباگ کردن برنامه می‌کنیم. همه ما از این مشکل خسته می شویم و اعصابمان خرد می شود. اگر یک دلیل وجود داشته باشد که یک برنامه نویس بخواهد برای برنامه ای که توسعه می دهد یونیت تست بنویسد این بهترین دلیل است

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

در زیر به مواردی که باعث می شود که ما نیاز داشته باشیم که همه تست هایی که قبلا برای برنامه مان انجام داده ایم را ، دوباره انجام دهیم ذکر می کنیم توجه کنید که تاکید می کنیم که اجرای دوباره تست ها عملی منطقی است و یکی از نیازهای توسعه برنامه میباشد

مواردی که باعث میشود تا همه تست های انجام شده قبلی را ، دوباره انجام دهیم

مورد اول : تغییر دادن قسمت مورد نظر در برنامه مان

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

مورد دوم : تغییر یکی از وابستگی ها مربوط به قسمت مورد نظر ما

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

برای رهایی از مشکلات ذکر شده چه گزینه هایی داریم

راه حل اول : بی توجهی به مشکلات و تظاهر به عدم وجود آنها نکنیم

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

راه حل دوم : فرار از تست کردن را کنار بگذاریم

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

راه حل چهارم : قبول مشکلات بیان شده و حل این مشکلات با استفاده از تست کردن به صورت اتوماتیک

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

مزایای دیگر تست کردن علاوه بر حل مشکلات تست دستی چیست

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

مزیت دیگر اینکه با خواندن تست های برنامه میتوان رفتار برنامه را تشخیص داد. به این صورت که هر تست یکی از رفتارهای برنامه مان را مشخص میکند. به عبارت دیگر یونیت تست هایی که برای کد های مان می نویسیم مانند یک داکیومنت عمل می کنند.

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

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

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