استراتژی تست جعبه‌ سفید و تست جعبه‌ سیاه

در این مقاله،‌ می‌خواهیم چند استراتژی مهم برای تست نرم‌افزار را معرفی کنیم. این استراتژی‌ها به ما کمک می‌کنند اهداف تست را به صورت عملی محقق کنیم و شامل سه استراتژی جعبه‌ سفید،‌ جعبه‌ سیاه و همچنین تست نرم‌افزار بر مبنای شیءگرایی می‌باشد. البته برای اینکه مقاله خیلی طولانی و کسل‌کننده نشود؛‌ در این مقاله،‌ تنها دو استراتژی اول را معرفی می‌کنیم و در مقاله‌ی بعدی به استراتژی سوم می‌پردازیم.تست جعبه سفید چیست؟تست جعبه سفید (White-Box Testing) که با نام‌های دیگری مثل تست ساختاری (Structural Testing) یا تست جعبه‌ی شیشه‌ای (Glass-Box Testing) نیز شناخته می‌شود؛ بر این اساس طراحی شده است که انگار ماژول نرم‌افزاری مورد بررسی را درون یک جعبه‌ی شیشه‌ای قرار داده‌ایم و از بیرونِ دیواره‌های شفاف آن،‌ با دقت به جزئیات عملکرد هر بخش نگاه می‌کنیم و درستیِ عملکرد آنها را چک می‌کنیم تا از این بابت اطمینان پیدا کنیم که تمام مسیرهای ممکن برای اجرای برنامه، حداقل یکبار به‌درستی کامپایل و اجرا می‌شوند و دستوری وجود ندارد که درون کدها پیش‌بینی شده باشد اما از طریق جریان اجرای برنامه،‌ به هیچ طریقی نتوانیم به آن دسترسی پیدا کنیم. (در مقاله‌ی قبل مفصل در این‌باره صحبت کردیم. از شما دعوت می‌کنم برای بررسی بهتر، روی لینک ارائه شده کلیک کنید.)اهداف روش جعبه سفیدکلمه‌ی کلیدی استراتژی جعبه سفید،‌ "اطمینان" است! ما تست جعبه سفید را انجام می‌دهیم تا از نبود مشکل در ماژول نرم‌افزاریمان "اطمینان" حاصل کنیم. در این قسمت،‌ بخش‌های مختلفی که می‌توانیم به کمک این استراتژی،‌ از درست بودن عملکردشان اطمینان پیدا کنیم را معرفی می‌کنیم:- تمام مسیر های مستقلی که یک ماژول از برنامه می‌تواند طی کند را حداقل یک بار امتحان کرده و تضمین کنیم که تمام این مسیرها، می‌توانند بدون هیچگونه مشکلی اجرا شوند.- در ساختارهای شرطی و انشعابی مثل if و switch ،‌ مطمئن باشیم که تمام دستوراتی که درصورت وقوع حالتهای مختلف متغیر شرط نوشته شده‌اند؛‌ حداقل یک بار اجرا می‌شوند (یعنی دستوری وجود ندارد که تحت هیچ حالتی قابل اجرا نباشد) و از آن مهم‌تر اینکه دستورات اجرا شده درست و بدون مشکل اجرا می‌شوند.- تمام حلقه‌های موجود در برنامه را درون مقادیر مرزی و نزدیک به مرز شرط حلقه‌ها تست کنیم تا مطمئن‌شویم که در آن مقادیر، به هیچ مشکلی برنخواهندخورد.- تمام ساختمان‌داده‌های درونی برنامه را اعتبارسنجی کنیم تا از معتبر بود آنها اطمینان پیدا کنیم.تکنیک‌های اساسی استراتژی تست جعبه سفیدروش تست جعبه سفید،‌ به طور کلی سه تکنیک اصلی دارد:پوشش‌دهی دستورات (Statement Coverage)در این تکنیک، منظور از statement همان خط‌های کد می‌باشد. این تکنیک مشخص می کند که آیا تمام خط‌های کد،‌ حداقل یکبار به‌درستی کامپایل و اجرا می شوند یا خیر؟!نمونه ای از تحلیل Statement Coverage توسط JUnit در محیط intelliJپوشش‌دهی شاخه‌ها (Branch Coverage)در اینجا،‌ منظور از branch،‌ جایی از کد برنامه است که در آن، ساختار if و یا ساختار های شبیه به آن (مانند switch) وجود داشته باشد. همانطور که می‌دانید، در این بخش‌ها برنامه باید بسته به حالتی که متغیر شرطی آن دارد؛‌ به شاخه‌های مختلف تقسیم شود. این تکنیک وظیفه دارد که مشخص کند که آیا تمامی شاخه‌های برنامه (که برای if دو شاخه و برای switch‌چندین شاخه است) بسته به نیاز برنامه به درستی انجام می‌شود یا خیر؟!نمونه‌ای از خروجی HTML حاصل از اجرای Branch Coverage توسط JUnit در محیط intelliJ پوشش‌دهی مسیرها (Path Coverage) این تکنیک،‌ یک تکنیک جامع است که مشابه branch coverage اما قوی‌تر از آن بوده و برای آزمایش برنامه‌های پیچیده مفید است. در این تکنیک، هدف این است که مطمئن شویم تمام مسیرهای برنامه حداقل یکبار به‌درستی پیموده شده است. با توجه به هدف تکنیک پوشش‌دهی مسیرها،‌ طبیعی است که پردازش آن به مراتب سختتر از موارد دیگر هم باشد.تفاوت تئوری و عملکرد تست جعبه سفید در دنیای واقعیهمانطور که در بخش اهداف تست جعبه سفید گفتیم؛ در این استراتژی، هدف این است که به "اطمینان" برسیم از اینکه برنامه‌ی مورد بررسی‌مان درست کار می‌کند. و در دنیای کامپیوتر و احتمالات،‌ چه چیزی بهتر است از اینکه مطمئن شویم نرم‌افزارمان کاملا درست کار میکند؟! طبیعتا هیچ چیز. درست است که اگر تست جعبه سفید،‌ کاملا انجام شود و تمام مسیرهای اجرای برنامه کاملا چک شود؛‌ اتفاق خیلی خوبی می‌افتد. اما واقعیت این است که در دنیای واقعی،‌ تعداد راههای ممکن برای اجرای برنامه بسیار زیاد هستند و هرکدام از آنها،‌ طبیعتا نیازمند حجم زیادی از پردازش‌ها هستند. به همین دلیل،‌ عملا برای خیلی از سیستم‌های نرم‌افزاری،‌ نمی‌توانیم استراتژی جعبه سفید را به صورت کامل پیاده سازی کنیم! تنها راهی که برای تست کردن همچنین نرم‌افزارهایی داریم،‌ این است که بسته به نوع پروژه و ویژگی‌های حساس ماژول‌های آن،‌ تعداد معقولی از مسیرهای منطقیِ مهم را برای تست‌کردن انتخاب کنیم و تست جعبه سفید را تنها و تنها برای همین مسیرهای انتخاب شده اجرا کنیم. (در مقاله‌ی قبل درباره‌ی نقاط آسیب‌پذیر هر ماژول،‌ به‌طور مفصل صحبت کردیم)تست جعبه سیاه چیست؟بعد از اینکه از دیواره‌های شیشه‌ای جعبه‌‌ی سفید،‌ با دقت به ساختار درونی ماژول‌های مختلف نگاه کردیم و از درست‌بودن عملکرد آنها مطمئن شدیم؛ حالا وقت آن است که دیواره‌های شیشه‌ای جعبه را با رنگ سیاه،‌ کاملا بپوشانیم تا دیگر به ساختار درونی آن توجهی نکنیم. در این تست،‌ هدف این است که مطمئن شویم ماژول نرم‌افزاری ای که بعد از انجام تست جعبه‌ی سفید،‌ اطمینان داریم که حتما درست کار می‌کند؛ می‌تواند با ماژول‌های دیگرِ سیستم‌ نرم‌افزاریمان نیز به خوبی تعامل کند و با کمک یکدیگر خروجی‌ای که از آنها انتظار می‌رود را تولید کنند. در این روش، ما به هیچ وجه نمیخواهیم ساختار درونی ماژول را درنظر بگیریم و تنها تمرکز خود را روی ورودی‌ها و خروجی‌های سیستم نرم‌افزاری قرار می‌دهیم. این استراتژی،‌ با نام‌های تست رفتاری (Behavioral Testing) یا تست عملکردی (Functional Testing) نیز شناخته می‌شود.تست جعبه‌ی سیاه، یک جایگزین برای تست جعبه سفید نیست! بلکه مکمل آن است. این استراتژی،‌ وظیفه دارد انواع دیگری از خطاها را کشف کند که تست جعبه سفید نمیتواند آنها را کشف کند. تست جعبه سیاه چه زمانی باید انجام شود؟برای پاسخ به این سوال،‌ باید بگوییم که بهتر است تست جعبه سیاه را هم قبل از توسعه‌ی نرم‌افزار و هم بعد از آن انجام دهیم. چرا که در هرکدام،‌ می‌توانیم اهداف و رویکردهای متفاوتی را دنبال کنیم که می‌توانند به اعتبارسنجی برنامه‌ی ما کمک کند.برای نوشتن تست جعبه سیاه،‌ لزوما نیازی نیست تا کامل شدن فرآیند توسعه‌ی نرم‌افزار صبر کنیم. بلکه تنها زمانی که نوع و ترتیب داده‌های ورودی و خروجی ماژول و نحوه‌ی ارتباط ماژول های مختلف (که توسط معمار نرم‌افزار طراحی می‌شوند) مشخص شد، مسئول تست میتواند تست های مربوط به این سطح را بنویسد و حتی به کمک آنها مشخص کند که در هر مرحله باید چه ورودی هایی وارد هر ماژول شود تا همه ی نیازها را پوشش دهند. همچنین می‌توانیم مشخص کنیم که انتظار داریم از هر ماژول،‌ چه خروجی‌هایی دریافت کنیم که با ماژول های بعدی و کل سیستم نرم‌افزاری یکپارچه شود. این کار، حتی به توسعه ی بهتر نرم‌افزار نیز کمک می‌کند.علاوه بر آن،‌ لازم است بعد از توسعه‌ی سیستم نرم‌افزاری نیز تست جعبه سیاه انجام شود. چراکه در این تست، تمرکز بر روی ساختار برنامه و پیاده سازی آن نیست! بلکه تمرکز بر روی اطلاعات ورودی و خروجی سیستم می‌باشد که بعد از تکمیل توسعه نرم افزار تولید می شود. در نتیجه باید مطمئن باشیم که بعد از توسعه‌ی سیستم نرم‌افزاری،‌ جریان اطلاعات درون برنامه به درستی شکل گرفته است و ماژول‌های مختلف برنامه میتوانند به خوبی باهم کار کنند و نتیجه‌ی درستی تولید کنند.چه خطاهایی توسط تست جعبه سیاه کشف می‌شوند؟خطاهایی که توسط تست جعبه سیاه شناخته می‌شوند،‌ شامل موارد زیر می‌باشند:- بخشی از نرم‌افزار عملکرد نادرست دارد و یا بخشی از عملیات‌هایی که می خواهیم انجام دهد را انجام نمی‌دهد. (درصورتیکه داده‌های خروجی تولید شده از یک ماژول،‌ صحیح نباشد)- مشکل در interface ها. منظور از interface ورودی‌ها و خروجی‌های بخش‌های مختلف برنامه است.- مشکل در ساختمان‌داده های استفاده شده برای ورودی‌ها و خروجی‌ها و یا مشکل در دسترسی به دیتابیس خارجی- خطاهای مربوط به رفتار یا بازدهی سیستم- خطاهای مربوط به مقداردهی‌های اولیه و تنظیمات مورد نیاز برای شروع‌شدن اجرای نرم‌افزار و یا خطاهای مربوط به پایان یافتن اجرای برنامهانواع روش‌های تست جعبه سیاهتست جعبه سیاه،‌ دارای تکنیک‌ها و راههای مختلفی است که در این بخش،‌ آنها را بررسی می‌کنیم:تست ورودی‌ها و خروجی‌ها (Interface Testing)در اکثر برنامه ها، کامپوننت‌های مختلف جوری طراحی می‌شوند که باید وابسته به هم کار انجام دهند و به تنهایی کاربرد خاصی ندارند. به همین دلیل این موضوع خیلی مهم است که مطمئن شویم این کامپوننت‌های مجزا از هم، زبان یکدیگر را می فهمند و اصطلاحا با همدیگر یکپارچه هستند. به این صورت که اگر قرار باشد داده‌ای به یک کامپوننت نرم‌افزاری برسد، با ترتیب و نوع داده ای باشد که در تعریف ورودی‌های خود انتظار آمدنش را داشته است و خودِ این کامپوننت هم خروجی‌ای تولید کند که از نظر ترتیب و نوع داده، طوری باشد که مراحل بعدی برنامه از او میخواهند. تا کامپوننت های بعدی نیز بتوانند از نتیجه ی این بخش برای انجام کارهای بعدی استفاده کنند. این تست در واقع جزء مراحل Integration Test محسوب میشود. البته با توجه به اهمیت این یکپارچگی خروجی‌ها و ورودی‌ها، خیلی از توسعه‌دهنده‌ها نیاز می‌دانند که در حین توسعه نیز، با اضافه کردن کدهای اضافی تا حدی که امکان دارد آنها را چک کنند و برای بررسی صحت آنها، منتظرِ به پایان رسیدن برنامه نمانند.نحوه‌ی نگرش ما به سیستم نرم‌افزاری در هنگام بررسی ورودی و خروجی‌ها ( interfaceها )دسته‌بندی هم‌ارزها (Equivalence Partitioning)دسته بندی هم‌ارزها، یکی از روش های تست جعبه سیاه است که در آن، ورودی ها را به دسته های مختلفی تقسیم می کنیم به طوریکه هر دسته، بتواند یکی از حالتهای Test case را نشان بدهد. یک Test case ایده آل، باید جوری طراحی شده باشد که بتواند به تنهایی یک دسته از خطاها را آشکارکند. (مثلا همه ی جاهایی که کاراکترهای اطلاعات، اشتباه پردازش شده اند) در غیراینصورت (اگر Test case ها اینطور طراحی نشوند)، ممکن است قبل از اینکه تست مربوطه اجرا شده و خطاها شناخته شود؛ در جاهای دیگرِ برنامه این فرآیند اجرا شده باشد و به این ترتیب خروجی‌های اشتباهی تولید شده و در جریان برنامه قرار بگیرند که هیچوقت شناسایی نمی‌شوند.نحوه‌ی دسته بندی گروههای هم‌ارزهر Test case طراحی شده برای این روش، براساس سنجش کلاس های هم‌ارزی برمبنای شرطِ ورودی تعیین می‌شود. به این ترتیب، اگر یک مجموعه‌ای از اشیاء با هم رابطه‌ی تقارنی، تعدی و ترایایی داشته باشد؛ براساس قوانین مجموعه‌ها، آن مجموعه دارای رابطه‌ی هم ارزی است و اعضای آن با یکدیگر هم‌ارز هستند. یک کلاس هم‌ارزی، نشان دهنده‌ی مجموعه‌ای از شرط‌های ورودی معتبر و غیرمعتبر می‌باشد که میتواند شامل یک عدد یا یک رنجی از اعداد و یا مجموعه ای از مقادیر مرتبط با هم یا حتی یک شرط بولین باشد. البته Test case ها به گونه ای انتخاب می‌شوند که بیشترین تعداد صفات از یک کلاس هم‌ارزی به طور همزمان اعمال شود. این کار باعث میشود که تعداد Test case ها تا حد امکان کاهش پیدا کند و تعداد فرآیند های تستی که باید ایجاد شود، به مراتب کمتر شده ودر نتیجه، بار محاسباتی پردازش تست نیز به‌شدت کاهش پیدا کند. چرا که نوشتن تست برای تمام حالات ممکن، حتی برای یک برنامه‌ی حلقه‌ی ساده با شرط محدود هم به شدت سنگین خواهد شد و خیلی زود ممکن است از توان سخت‌افزارهای در دسترس ما خارج شود. بعد از تعریف‌شدن کلاسهای هم ارزی، حالت های زیر ممکن است برای Test caseهای ما ایجاد شود. با استفاده از این دستورالعمل و کلاس بندی، میتوانیم به راحتی داده‌ها را تقسیم بندی کرده و پردازش کنیم:1. اگر شرط ورودی، محدوده‌ای را مشخص کند؛ یک کلاسِ معادلِ معتبر و دو کلاسِ معادلِ نامعتبر تعریف می‌شود. چرا که باید قبل از آن محدوده و بعد از آن را به عنوان کلاسهای نامعتبر در نظر بگیریم.2. اگر شرط ورودی به مقدار خاصی نیاز داشته باشد؛ یک کلاسِ معادلِ معتبر و دو کلاسِ معادلِ نامعتبر تعریف می شود. چرا که باید قبل از آن مقدار وبعد از آن را به عنوان کلاسهای نامعتبر در نظر بگیریم.3. اگر شرط ورودی یک عضو از یک مجموعه را مشخص کند؛ یک کلاسِ معادلِ معتبر و یک کلاسِ معادلِ نامعتبر تعریف می شود. چون این عضو مشخص، یا در مجموعه وجود دارد و یا وجود ندارد!4- اگر شرط ورودی بولین باشد، یک کلاسِ معتبر و یک کلاسِ نامعتبر تعریف می شود. چون این شرط یا درست است و یا غلط و به هرحال در یکی از این دو مجموعه قرار می‌گیرد.نمونه‌ای از کلاسهای هم‌ارزی ایجاد شده برای یک مسئله‌ی کوچکارزیابی متغیرهای مرزی (Boundary Value Analysis) همانطور که قبلا هم اشاره کردیم؛ تعداد قابل توجهی از خطاهایی که ممکن است در برنامه رخ دهد، زمانی رخ می‌دهد که نرم‌افزار می‌خواهد ورودی‌هایی را پردازش کند که در مرزهای رنج کلاس‌های هم‌ارزی قرار دارند. نه در مرکز رنج. به همین دلیل است که در تست نرم افزار، یک تکنیک مجزا به نام تجزیه و تحلیل مقادیر مرزی یا Boundary Value Analysis ایجاد شده است. این تکنیک، در واقع مکمل تکنیک قبلی یعنی تکنیک دسته‌بندی هم‌ارزها است که در آن، به جای آنکه روی تمام مقادیر، عملیات تست را انجام دهیم؛ بعد از کلاس‌بندی اعضای هم‌ارز، تنها مقادیر مرزی را چک می کنیم تا از نبودن خطا برای این ورودی ها مطمئن شویم.البته که اکثر برنامه‌نویس‌ها، برای کاهش احتمال وجود خطا، درهنگام توسعه نیز این تست‌ها را انجام می دهند. بعد از اینکه مقادیر مرزی را به‌صورت جداگانه حساب کردیم، مقادیر خروجی متناظر با این ورودی‌های خاص نیز به‌طور خودکار محاسبه می شوند و به این ترتیب BVA میتواند به رنج‌بندی‌ شدن خروجی‌های Test case ما نیز کمک کند.دستورالعمل‌های BVA از بسیاری از جهات مشابه دستورالعمل‌های تکنیک دسته‌بندی هم‌ارزهاست:1- اگر شرط ورودی، یک رنج محدودی از اعداد (از aتاb) را مشخص کند؛ Test case ها باید با مقادیر a و b و یک واحد پایین تر از a و یک واحد بالاتر از b آزمایش شوند.2- اگر شرط ورودی، تعدادی از مقادیر گسسته را مشخص کند؛ Test case ها باید با کمترین و بیشترین مقدار از میان آن مقادیر و یک واحد پایین تر از کوچکترین مقدار و یک واحد بالاتر از بزرگترین مقدار آزمایش شوند.3- دستورالعمل 1و 2 باید برای خروجی‌ها هم اعمال شوند. برای مثال اگر برنامه‌ای داشته‌باشیم که دما را به عنوان ورودی می‌گیرد و فشار را به ما می‌دهد؛ باید مطمئن باشیم که Test case های طراحی‌شده، گزارشی را به عنوان خروجی ایجاد کند که حداکثر (و حداقل) تعداد ورودی جدولِ مجاز را تولید کند.4- اگر درون برنامه، ساختمان‌داده‌ای مشخص‌ شده‌ بود که توسط توسعه‌دهنده، مرزهای محدودکننده‌ای برای آن تعریف شده بود، (به عنوان مثال ، یک آرایه که دارای 100 ورودی مشخص باشد) ، باید مطمئن شویم که Testcaseهای طراحی شده، مرزهای مشخص شده برای آن ساختمان‌داده را نیز پوشش می دهند.یک مثال ساده برای ارزیابی متغیرهای‌ مرزیSource : Roger S. Pressman, Bruce R. Maxim. "Software Engineering A Practtitioner's Approach" - 9th Editionدرمقاله ی بعدی،‌ درباره‌ی سومین استراتژی مهم تست نرم‌افزار که مخصوص تست‌کردن برنامه‌هایی است که طبق اصول شیءگرایی توسعه‌داده شده‌اند، صحبت می‌کنیم. از آنجا که مقاله‌ی بعدی، به نوعی ادامه‌ی مطالب گفته شده در این مقاله است؛ از شما دعوت می‌کنم آن مقاله را نیز حتما مطالعه کنید.