تست موثر و سیستماتیک نرم افزار
مقدمه
جامعه توسعه دهندگان دیگر نیازی به بحث در مورد اهمیت تست نرم افزار ندارند. هر توسعهدهنده نرمافزاری میداند که خرابیهای نرمافزار ممکن است باعث آسیب جدی به مشاغل، مردم یا حتی جامعه به عنوان یک کل شود. و اگرچه زمانی توسعهدهندگان نرمافزار مسئولیت اصلی ساختن سیستمهای نرمافزاری را بر عهده داشتند، امروزه مسئولیت کیفیت سیستمهای نرمافزاری را نیز بر عهده دارند.
چندین ابزار در سطح جهانی برای کمک به توسعه دهندگان برای آزمون تولید شده است، از جمله JUnit، AssertJ، Selenium و jqwik. ما آموخته ایم که از فرآیند نوشتن تستها برای بازتاب در مورد آنچه برنامهها باید انجام دهند و بازخورد در مورد طراحی کد (یا طراحی کلاس، اگر از زبان شیگرا استفاده میکنید) استفاده کنیم. همچنین آموختهایم که نوشتن کد تست چالش برانگیز است و توجه به کیفیت کد تست برای تکامل برازنده مجموعه تست اساسی است. و در نهایت، ما می دانیم که اشکالات رایج چیست و چگونه آنها را جستجو کنیم.
اما در حالی که توسعه دهندگان از ابزارهای تست به خوبی استفاده می کنند، به ندرت از تکنیک های تست سیستماتیک برای کاوش و یافتن باگ ها استفاده می کنند. بسیاری از شاغلان استدلال میکنند که آزمونها یک ابزار بازخورد هستند و باید بیشتر برای کمک به توسعه شما استفاده شوند. اگرچه این درست است در صورتیکه آزمون ها همچنین می توانند به شما در یافتن اشکال کمک کنند. از این گذشته، تست نرم افزار به این معنی است: یافتن باگ ها!
اکثر توسعه دهندگان از نوشتن تست لذت نمی برند. دلایل زیادی شنیده ایم: نوشتن کد تولید سرگرم کننده تر و چالش برانگیزتر است، تست نرم افزار خیلی وقت گیر است، ما برای نوشتن کد تولید پول می گیریم و غیره. همانطور که بلر و همکارانش در یک مطالعه تجربی خوب با صدها توسعهدهنده در سال 2019 دریافتند، توسعهدهندگان میزان زمان خود را برای آزمون بیش از حد تخمین میزنند. هدف این مقاله این است که شما را متقاعد کنم که (1) بهعنوان یک توسعهدهنده، مسئولیت شماست که از کیفیت چیزی که تولید می کنید اطمینان حاصل کنید؛ (2) که آزمون ها تنها ابزاری هستند که به شما در انجام این مسئولیت کمک می کنند. و (3) اینکه اگر مجموعه ای از تکنیک ها را دنبال کنید، می توانید کد خود را به روشی موثر و سیستماتیک آزمایش کنید.
تست موثر در فرآیند توسعه
در این مقاله، من یک جریان ساده را برای توسعه دهندگانی که از تست موثر و سیستماتیک استفاده می کنند، پیشنهاد می کنم. ابتدا، یک ویژگی را با استفاده از تستهایی برای تسهیل و هدایت توسعه پیادهسازی میکنیم. هنگامی که از ویژگی یا واحد کوچکی که کدگذاری کردهایم به طور معقولی راضی هستیم، به تست مؤثر و سیستماتیک مراجعه می کنیم تا مطمئن شویم که مطابق انتظار کار میکند (یعنی برای یافتن اشکالات آزمایش میکنیم). تصویر زیرگردش کار توسعه را با جزئیات بیشتری نشان می دهد.
- توسعه ویژگی (قابلیت)، اغلب با دریافت نوعی نیازمندی توسط یک توسعه دهنده شروع می شود. نیازمندی ها اغلب به شکل زبان طبیعی هستند و ممکن است از قالب خاصی پیروی کنند، مانند موارد استفاده از زبان مدلسازی یکپارچه (UML) یا داستان های کاربر چابک. پس از ایجاد مقداری درک (یعنی تجزیه و تحلیل نیازمندی)، توسعه دهنده شروع به نوشتن کد می کند.
- برای هدایت توسعه ویژگی، توسعهدهنده چرخه توسعه مبتنی بر آزمون (TDD) کوتاه را انجام میدهد. این چرخهها به توسعهدهنده بازخورد سریع درباره منطقی بودن کدی که به تازگی نوشتهاند را میدهند. آنها همچنین از توسعهدهندهها از طریق بازآفرینیهای فراوانی (refactoring) که هنگام پیادهسازی یک ویژگی جدید رخ میدهند، پشتیبانی میکنند.
- نیازمندی ها اغلب بزرگ و پیچیده هستند و به ندرت توسط یک کلاس یا متد پیاده سازی می شوند. توسعه دهنده چندین واحد (کلاس ها و روش ها) با قراردادهای مختلف ایجاد می کند و با هم همکاری می کنند و عملکرد مورد نیاز را تشکیل می دهند. نوشتن کلاس هایی به گونه ای که تست آنها آسان باشد، چالش برانگیز است و توسعه دهنده باید با در نظر گرفتن قابلیت تست طراحی کند.
- زمانی که توسعهدهنده از واحدهایی که ایجاد کردهاند راضی است و معتقد است که نیاز کامل شده است، به تست روی میآورد. اولین قدم، تمرین هر واحد جدید است. تست دامنه، تست مرزی و تست ساختاری تکنیک های پیشرو هستند.
- ممکن است توسعهدهنده برای برخی از بخشهای سیستم که نیاز باشد را با آزمایشهای بزرگتری (یکپارچهسازی یا تستهای سیستم) بنویسد. برای تعبیه موارد آزمایشی بزرگتر، توسعهدهنده از همان سه تکنیک استفاده میکند: آزمایش دامنه، آزمایش مرز، و آزمایش ساختاری، اما به بخشهای بزرگتری از سیستم نرمافزار نگاه میکند.
- هنگامی که توسعهدهنده موارد آزمون را با استفاده از تکنیکهای مختلف مهندسی کرده است، از ابزارهای تست خودکار و هوشمند برای جستجوی تست هایی استفاده میکند که انسان در تشخیص آنها خوب نیست. تکنیکهای رایج شامل تولید مورد آزمون، آزمون جهش (mutation testing) و تجزیه و تحلیل استاتیک است.
- در نهایت، پس از این آزمون دقیق، توسعه دهنده با انتشار این ویژگی احساس راحتی می کند.
تست موثر به عنوان یک فرآیند تکراری
در حالی که شرح قبلی ممکن است مانند یک فرآیند متوالی/آبشار به نظر برسد، اما تکراری تر است. یک توسعه دهنده ممکن است به شدت کلاسی را تست کند و ناگهان متوجه شود که تصمیمی که چند ساعت پیش برای کدنویسی گرفته است ایده آل نبوده است. سپس به عقب برمیگردد و کد را دوباره طراحی میکنند. آنها ممکن است در حال انجام چرخه های TDD باشند و متوجه شوند که نیاز در مورد چیزی نامشخص است. سپس توسعهدهنده برای درک بهتر انتظارات، به تحلیل نیازمندیها برمیگردد. معمولاً، هنگام تست، توسعهدهنده یک اشکال پیدا میکند. آنها به کد باز می گردند، آن را تعمیر می کنند و به تست ادامه می دهند. یا ممکن است توسعهدهنده فقط نیمی از ویژگی را پیادهسازی کرده باشد، اما آنها احساس میکنند که تست دقیق آن در حال حاضر مفیدتر از ادامه پیادهسازی است.
تمرکز بر توسعه و سپس آزمایش
من فکر می کنم که تمرکز جداگانه بر روی توسعه و آزمون، رهایی بخش است. وقتی در حال کدنویسی یک ویژگی هستم، نمیخواهم حواسام به گوشههای مبهم منحرف شود. اگر به یکی فکر می کنم، یادداشت می کنم تا فراموش نکنم بعداً آن را تست کنم. با این حال، ترجیح میدهم تمام انرژی خود را بر روی قوانین تجاری که اجرا میکنم متمرکز کنم و در عین حال اطمینان حاصل کنم که حفظ کد برای توسعهدهندگان آینده آسان است.
وقتی تصمیمات کدنویسی تمام شد، روی تست تمرکز می کنم. ابتدا تکنیکهای مختلف را دنبال میکنم، انگار که در حال انجام یک چک لیست سیستماتیک هستم. گویی یک چک لیست وجود دارد که میگوید «لیست پوچ، خالی، یک عنصر، عناصر متعدد». فقط در این صورت است که از خلاقیت و دانش حوزه خود برای اعمال سایر مواردی که به نظرم مرتبط میدانم استفاده می کنیم.
افسانه "صحت توسط طراحی"
اکنون که تصویر واضح تری از منظور من از تست موثر و سیستماتیک نرم افزار دارید، اجازه دهید یک افسانه را از بین ببرم. در بین توسعه دهندگان نرم افزار این تصور وجود دارد که اگر کد را به روشی ساده طراحی کنید، باگ نخواهد داشت، گویی راز کدهای بدون اشکال سادگی است.
تحقیقات تجربی در مهندسی نرم افزار مکرراً نشان داده است که کدهای ساده و بدون بو کمتر از کدهای پیچیده مستعد نقص هستند (برای مثال به مقاله 2006 شاتنوی و لی مراجعه کنید). با این حال، سادگی دور از دسترس است. ساده لوحانه است که باور کنیم تست را می توان به طور کامل با سادگی جایگزین کرد. همین امر در مورد "صحت توسط طراحی" نیز صادق است: طراحی خوب کد به این معنی نیست که از تمام اشکالات احتمالی اجتناب کنید.
هزینه تست
ممکن است فکر کنید که اجبار توسعه دهندگان برای اعمال تست¬های دقیق ممکن است بسیار پرهزینه باشد. تصویر قبل تکنیک های بسیاری را که توسعه دهندگان باید در صورت پیروی از جریانی که من پیشنهاد می کنم، اعمال کنند، نشان می دهد. درست است: تست نرم افزار بصورت درست کار بیشتری از انجام ندادن آن صرف می کند. بگذارید شما را متقاعد کنم که چرا ارزشش را دارد:
هزینه ایرادهایی که در محیط عملیاتی اتفاق می افتد اغلب از هزینه پیشگیری بیشتر است (همانطور که بوهم و پاپاچیو، 1988 نشان داده شده است). به یک فروشگاه اینترنتی پرطرفدار فکر کنید و اگر برنامه پرداخت به مدت 30 دقیقه به دلیل اشکالی که میتوانست به راحتی از طریق تست از آن جلوگیری شود، از کار بیفتد، چقدر برای فروشگاه هزینه خواهد داشت.
تیمهایی که باگهای زیادی تولید میکنند معمولاً زمان را در یک حلقه ابدی تلف میکنند که در آن توسعهدهندگان باگها را مینویسند، مشتریان (یا QAهای اختصاصی) باگها را پیدا میکنند، توسعهدهندگان اشکالات را برطرف میکنند، مشتریان مجموعهای از باگها را پیدا میکنند و غیره.
تمرین کلیدی است. زمانی که توسعهدهندگان به تست¬های مهندسی عادت کردند، میتوانند آن را بسیار سریعتر انجام دهند.
معنای مؤثر و سیستماتیک
من از دو کلمه برای توصیف اینکه چگونه انتظار دارم یک توسعه دهنده تست کند استفاده کرده ام: به طور موثر و سیستماتیک. موثر بودن به این معنی است که روی نوشتن تست های درست تمرکز می کنیم. تست نرم افزار همه چیز در مورد معاوضه است. آزمونگرها میخواهند تعداد باگهایی را که پیدا میکنند به حداکثر برسانند و در عین حال تلاش لازم برای یافتن باگها را به حداقل برسانند. چگونه به این امر برسیم؟ با دانستن اینکه چه چیزی را تست کنید.
تمام تکنیکهایی که ارائه می¬گردد یک شروع روشن (چه چیزی را باید تست کنیم) و یک پایان روشن (چه زمانی باید متوقف شود) دارند. البته منظورم این نیست که اگر از این تکنیک ها پیروی کنید، سیستم شما بدون اشکال خواهد بود. به عنوان یک جامعه، ما هنوز نمی دانیم چگونه سیستم های بدون اشکال بسازیم. اما با اطمینان می توانم بگویم که تعداد باگ ها کاهش می یابد و امیدوارم به سطوح قابل تحمل برسد.
سیستماتیک بودن به این معنی است که برای یک قطعه کد معین، هر توسعهدهندهای باید همان مجموعه تست¬ها را ارائه کند. آزمون اغلب به صورت موقت انجام می شود. توسعه دهندگان موارد آزمون را که به ذهن می رسد مهندسی می کنند. معمولاً مشاهده میشود که دو توسعهدهنده مجموعههای آزمون متفاوتی را برای یک برنامه توسعه میدهند. ما باید بتوانیم فرآیندهای خود را برای کاهش وابستگی به توسعه دهنده ای که کار را انجام می دهد، سیستماتیک کنیم.
من این استدلال را درک می کنم و موافقم که توسعه نرم افزار یک فرآیند خلاقانه است که توسط روبات ها قابل اجرا نیست. من معتقدم که انسان ها همیشه در جریان ساختن نرم افزار خواهند بود. اما چرا به توسعهدهندگان اجازه نمیدهیم روی چیزی که نیاز به خلاقیت دارد تمرکز کنند؟!
نقش اتوماسیون تست
اتوماسیون برای یک فرآیند تست موثر کلیدی است. هر تستی که در اینجا طراحی می کنیم بعداً از طریق یک چارچوب آزمایشی مانند JUnit خودکار می شود. اجازه دهید به وضوح بین طراحی تست و اجرای آزمایشی تمایز قائل شوم. هنگامی که یک مورد آزمایشی (test case) نوشته میشود، یک چارچوب آن را اجرا میکند و گزارشها، خرابیها و غیره را نشان میدهد. این تمام کاری است که این چارچوب ها انجام می دهند.
نقش آنها بسیار مهم است، اما چالش واقعی در تست نرم افزار نوشتن کد JUnit نیست، بلکه طراحی موارد آزمون مناسب است که ممکن است اشکالات را آشکار کند. طراحی موارد آزمون بیشتر یک فعالیت انسانی است و این همان چیزی است که اینجا در درجه اول بر آن تمرکز دارد.
اصول تست نرم افزار
یک دیدگاه ساده در مورد تست نرمافزار این است که اگر میخواهیم سیستمهای ما به خوبی تست شوند، باید به اضافه کردن تستها ادامه دهیم تا زمانی که به اندازه کافی در اختیار داشته باشیم. من آرزو می کنم که ساده بود. اطمینان از اینکه برنامهها باگ ندارند عملاً غیرممکن است و توسعهدهندگان باید بدانند چرا چنین است.
در این بخش، من برخی از اصولی را که زندگی ما را به عنوان آزمونگر نرمافزار دشوارتر میکنند و آنچه که میتوانیم برای کاهش آنها انجام دهیم، مورد بحث قرار میدهم. این اصول از اصول ارائه شده در کتاب بین المللی صلاحیت تست نرم افزار (ISTQB) توسط بلک، ونندال و گراهام (2012) الهام گرفته شده است.
1. آزمایش جامع غیرممکن است
ما منابع لازم برای تست کامل برنامه های خود را نداریم. تست همه موقعیت های ممکن در یک سیستم نرم افزاری ممکن است غیرممکن باشد، حتی اگر منابع نامحدودی داشته باشیم. یک سیستم نرم افزاری را با 300 پرچم مختلف یا تنظیمات پیکربندی (مانند سیستم عامل لینوکس) تصور کنید. هر پرچم را می توان روی true یا false تنظیم کرد و می تواند مستقل از دیگران تنظیم شود. سیستم نرم افزاری با توجه به ترکیب پیکربندی شده پرچم ها متفاوت رفتار می کند. داشتن دو مقدار ممکن برای هر یک از 300 پرچم، 2300ترکیب را به دست می دهد که باید آزمایش شوند. برای مقایسه، تعداد اتم های جهان 1080تخمین زده می شود. به عبارت دیگر، این سیستم نرم افزاری ترکیبات احتمالی بیشتری نسبت به اتم های جهان دارد.
با دانستن اینکه تست همه چیز ممکن نیست، باید انتخاب کنیم (یا اولویت بندی کنیم) چه چیزی را مورد آزمون قرار دهیم. به همین دلیل است که من بر نیاز به تست¬های مؤثر تأکید می کنم.
2. دانستن زمان توقف آزمایش
اولویتبندی آزمونها برای مهندسی دشوار است. ایجاد تستهای بسیار کم ممکن است سیستم نرمافزاری را برای ما به ارمغان بیاورد که آنطور که در نظر گرفته شده عمل نمیکند (یعنی پر از اشکال است). از سوی دیگر، ایجاد آزمون پس از آزمون بدون سنجش مناسب می تواند منجر به تست های بی اثر (و هزینه و زمان و هزینه) شود. همانطور که قبلاً گفتم، هدف ما همیشه باید این باشد که تعداد باگ های پیدا شده را به حداکثر برسانیم و در عین حال منابعی را که برای یافتن آن باگ ها صرف میکنیم به حداقل برسانیم. برای این هدف، معیارهای کیفی متفاوتی را مورد بحث قرار خواهم داد که به شما کمک می کند تصمیم بگیرید چه زمانی آزمون را متوقف کنید.
3. تنوع مهم است
هیچ گلوله نقره ای در تست نرم افزار وجود ندارد. به عبارت دیگر، هیچ تکنیک تست واحدی وجود ندارد که همیشه بتوانید از آن برای یافتن تمام اشکالات احتمالی استفاده کنید. تکنیک های مختلف تست به آشکار شدن باگ های مختلف کمک می کند. اگر فقط از یک تکنیک استفاده کنید، ممکن است تمام اشکالاتی را که می توانید با آن تکنیک پیدا کنید و نه بیشتر.
یک مثال ملموس تر، تیمی است که تنها بر تکنیک های تست واحد تکیه دارد. تیم ممکن است تمام اشکالاتی را که می توان در سطح تست واحد ثبت کرد، پیدا کند، اما ممکن است اشکالاتی را که فقط در سطح یکپارچه سازی رخ می دهد، از دست بدهند.
این به عنوان پارادوکس آفت کُشها شناخته می شود: هر روشی که برای پیشگیری یا یافتن باگ¬ها استفاده میکنید، بقایای باگ¬های ظریف تری را به جا می گذارد که این روش ها در برابر آن ها بی تاثیر هستند. آزمونگرها باید از استراتژیهای مختلف تست برای به حداقل رساندن تعداد باگهای باقیمانده در نرمافزار استفاده کنند.
4. اشکالات در برخی مکان ها بیشتر از جاهای دیگر اتفاق می افتد
همانطور که قبلاً گفتم، با توجه به اینکه آزمون جامع غیرممکن است، آزمونگرهای نرم افزار باید تست هایی را که انجام می دهند اولویت بندی کنند. هنگام اولویت بندی موارد آزمون، توجه داشته باشید که اشکالات به طور یکنواخت توزیع نشده اند. از نظر تجربی، جامعه ما مشاهده کرده است که برخی از مؤلفهها اشکالات بیشتری نسبت به سایرین دارند. به عنوان مثال، یک ماژول payment ممکن است به آزمایش دقیق تری نسبت به ماژول marketing نیاز داشته باشد.
به عنوان یک مثال در دنیای واقعی، شروتر و همکارانش (2006) را در نظر بگیرید، که اشکالات را در پروژه های Eclipse مطالعه کردند. آنها مشاهده کردند که 71 درصد از فایلهایی که بستههای کامپایلر را وارد میکردند باید بعداً رفع میشدند. به عبارت دیگر، این گونه فایل ها بیشتر از سایر فایل های سیستم در معرض نقص بودند. به عنوان یک توسعهدهنده نرمافزار، ممکن است مجبور باشید سیستم نرمافزاری خود را تماشا کنید و از آن یاد بگیرید. دادههای غیر از کد منبع ممکن است به شما در اولویتبندی تلاشهای تستی کمک کند.
5. مهم نیست چه آزمایشی انجام می دهید، هرگز کامل یا کافی نخواهد بود
همانطور که Dijkstra می گفت، "از تست برنامه می توان برای نشان دادن وجود اشکالات استفاده کرد، اما هرگز برای نشان دادن عدم وجود آنها استفاده نمی شود." به عبارت دیگر، در حالی که ممکن است با آزمون بیشتر، باگ های بیشتری را پیدا کنیم، مجموعه های تستی ما، هر چقدر هم که بزرگ باشند، هرگز تضمین نمی کنند که سیستم نرم افزاری 100٪ بدون اشکال است. آنها فقط اطمینان حاصل می کنند که مواردی که برای آنها تست می کنیم مطابق انتظار رفتار می کنند.
این یک اصل مهم برای درک است، زیرا به شما کمک می کند انتظارات خود (و مشتریانتان) را تعیین کنید. اشکالات همچنان رخ خواهند داد، اما (امیدواریم) پولی که برای تست و پیشگیری میپردازید، با اجازه دادن به اشکالات کمتر تأثیرگذار، جواب خواهد داد. "شما نمی توانید همه چیز را تست کنید" چیزی است که ما باید بپذیریم.
اگرچه مانیتورینگ موضوع اصلی این مقاله نیست، اما من سرمایه گذاری در سیستم های مانیتورینگ را توصیه میکنم. اشکالات رخ خواهند داد، و شما باید مطمئن باشید که آنها را در ثانیه ای که در محیط عملیات ظاهر میشوند، پیدا خواهید کرد. می توانید به مقاله بهترین تجربیات APM مراجعه کنید. این رویکرد گاهی اوقات تست در تولید نامیده می شود (Wilsenach, 2017).
6. زمینه پادشاه است
زمینه نقش مهمی در نحوه طراحی موارد آزمون دارد. به عنوان مثال، ایجاد موارد آزمون برای یک برنامه تلفن همراه با ایجاد موارد آزمون برای یک برنامه وب یا نرم افزار مورد استفاده در یک موشک بسیار متفاوت است. به عبارت دیگر، تست وابسته به زمینه است.
7. راستی آزمایی اعتبارسنجی نیست (Verification is not validation)
در نهایت توجه داشته باشید که سیستم نرم افزاری که بدون نقص کار می کند اما برای کاربرانش فایده ای ندارد، سیستم نرم افزاری خوبی نیست. همانطور که یک منتقد به من گفت: «اندازه گیری پوشش کد آسان است. پوشش نیازمندی ها موضوع دیگری است.» آزمونگر نرمافزار زمانی که صرفاً روی تأیید و نه تأیید اعتبار تمرکز میکنند، با این مغالطه عدم وجود خطا مواجه میشوند.
یک ضرب المثل رایج که ممکن است به شما کمک کند تفاوت را به خاطر بسپارید این است: «تأیید به درستی سیستم است. اعتبارسنجی مربوط به داشتن سیستم مناسب است.» راستیآزمایی و اعتبارسنجی میتوانند دست به دست هم بدهند و همچنین یک رویکرد تست سیستماتیک میتواند به شما کمک کند موارد گوشهای را که حتی کارشناسان محصول تصور نکردهاند، شناسایی نمایید.
شرکت مهندس پیشگان آزمون افزار یاس ، خدمات زیر را در حوزه ارزیابی و پایش کارایی نرم افزار ارائه می دهد:
آموزش روشهای ارزیابی کارایی سامانه های نرم افزاری از طریق آزمونهای بار و فشار
اجرای آزمونهای بار و فشار برروی سامانه های نرم افزاری
تهیه و آموزش ابزارهای تست پرفورمنس (تست بار و فشار) همچونWPLTو LoadTest
پایش و مانیتورینگ شاخص های کارایی سامانه های نرم افزاری از طریق ابزارهای مدیریت کارایی همچون AppDynamicsو DynaTrace
نویسنده: شرکت مهندس پیشگان آزمون افزار یاس
مراجع
[1] -Software Test Design- Write comprehensive test plans to uncover critical bugs in web, desktop, and mobile apps-Packt Publishing (2022)