مطالبی راجع به فرایندهای توسعه ی نرم افزار

تست

بانک کشاورزی بهترین بانک ایران.

 

 

بانک کشاورزی

۰۹ دی ۹۸ ، ۱۷:۵۸ ۰ نظر موافقین ۰ مخالفین ۰
ابوذر کمائی

مقدمه ای بر توسعه بر اساس رفتار

مقدمه 

توسعه مبتنی بر تست (Test Driven Design) روشی است که اولین بار توسط Kent Beck مطرح گردید. در این روش از تست، نه تنها به عنوان ابزاری برای بررسی عملکرد سیستم بلکه ابزاری برای تعیین معماری استفاده می شود. در این روش قبل از پیاده سازی هر بخش ابتدا تست واحد آن را پیاده سازی می کنیم تا مطمئن شویم برای پیاده سازی قابلیت، حداقل کار ممکن انجام شده است.
از این روش در کنار رویکردهای دیگر مثل برنامه نویسی دونفره (Pair Programming) در متدولوژی eXtreme Programming استفاده می گردد. به نظر می رسد این روش کمک می کند برنامه نویس های عادی بتوانند به مرور به طراحی ها بهتری دست پیدا کنند برنامه نویسی که به روش توسعه ی مبتنی بر تست، طراحی و پیاده سازی انجام می دهد با اعتماد به نفس بیشتری می تواند در سیستم تغییرات ایجاد نماید زیرا رفتار سیستم توسط تست های فراوانی که تعریف شده اند به خوبی قابل کنترل است.
این روش در کنار تمام این مزایا مشکلاتی نیز برای سازمان هایی که به تازگی شروع به استفاده از آن کرده اند ایجاد می کند. به عنوان مثال برنامه نویس های نمی دانند از کجا باید کار را شروع کرد یا کدام بخش های برنامه را تست کنند، برای نام گذاری تست های خود از چه قالبی استفاده نمایند، چگونه خیلی سریع بتوانند بفهمند چرا یک تست با موفقیت پاس نمی شود و باید مشکل را در کدام قسمت از کد پیدا کنند و ... 
تجربه نشان داده است استفاده از روش توسعه بر اساس تست برای اولین بار می تواند باعث شود تیم ها منابع زیادی را برای پیدا کردن روش درست انجام کار تلف کنند، توسعه بر اساس رفتار (Behavioral Driven Design) تلاش می کند این اتلاف منابع را به حداقل برساند و به تیم ها کمک کند هر چه سریعتر با این روش هماهنگ شده و بیشترین سود را از آن ببرند.
به مرور ابزارهای فراوانی برای این روش پیاده سازی شده است که اولا استفاده از آن را آسان تر می نماید ثانیا نشان دهنده ی اقبال برنامه نویسان به این روش و سرمایه گذاری به منظور طراحی و پیاده سازی ابزارهایی برای استفاده از آن می باشد.

 انتخاب عنوان مناسب برای تست

ایده ی طراحی بر اساس رفتار، اولین بار زمانی به ذهن Dan North رسید که با agiledox که توسط Chris Stevenson همکارش در شرکت Thoughtworks پیاده سازی شده بود آشنا گردید. این ابزار یک کلاس تست واحد را اسکن نموده و نام توابع آن را به عنوان جملاتی که بیانگر توصیف رسمی سیستم هستند چاپ می نماید.

 public class CustomerLookupTest extends TestCase {

	testFindsCustomerById() {
		...
	}

	testFailsForDuplicateCustomers() {
		...
	}

	...

} 
به عنوان مثال اگر کلاس تست بالا به عنوان ورودی به این ابزار داده شود خروجی به صورت زیر است
Customer Lookup
- finds customer by id
- fails for duplicate customers
- ...
با استفاده ی موثر از این ابزار یک حلقه ی افزاینده مثبت در توسعه ی نرم افزار ایجاد می گردد. در صورتی که توسعه دهنده برای تمام قابلیت های نرم افزار تست واحد تهیه کرده باشد و نام هر کدام از این قابلیت ها نیز به طور واضح نشان دهنده ی آن قابلیت باشد می توان از خروجی agiledox به عنوان توصیف رسمی سیستم استفاده کرد. از طرف دیگر اگر برنامه نویس بداند قرار است از تست ها به عنوان توصیف رسمی سیستم استفاده گردد اولا تلاش می کند تست ها تمام قابلیت نرم افزار را پوشش دهند ثانیا سعی می کند عنوان تست ها تا حد امکان واضح باشند و قابلیتی که قرار است مورد تست قرار بگیرد را توصیف نماید. در صورتی که تمام قابلیت های نرم افزار تست شوند توسعه دهنده می تواند با اعتماد به نفس بخش های موردنیاز خود را تغییر دهد همچنین هنگامی که عنوان تست ها بیان گر قابلیتی باشند که قرار است تست شود اولا نگه داری از تست های بسیار راحتتر می شود ثانیا در صورت بروز خطا در تست خیلی سریع می توان محلی که باعث بروز خطا شده است را پیدا کرد.

پیروی از ساختاری برای عنوان تست واحد

عناوین در مهندسی نرم افزار همواره حائز اهمیت هستند (به عنوان مثال در صورتی که هنگام طراحی نمی توانید برای موجودیت نام مناسبی انتخاب کنید احتمالا در تحلیل دچار مشکل شده اید و بهتر است تحلیل مورد بازنگری قرار بگیرد) از طرف دیگر اگر قبلا سعی کرده باشید تست واحد ایجاد نمایید حتما متوجه شده اید نام گذاری تست واحد می تواند فرایند زمانبری باشد. با پیروی از این ساختار برنامه نویس زمانی را برای تعیین نام تست تلف نمی کند و می تواند روی نوشتن تست هایی که واقعا ارزش آفرینی می کنند تمرکز نماید.
ساختار پیشنهادی تست از قالب "این کلاس باید فلان کار را انجام دهد" تبعیت می کند. این ساختار برنامه نویس را وادار می کند تنها رفتارهایی که مربوط به همین کلاس است تست نماید. در صورتی که نتوان برای یک کلاس در چنین قالب تست ایجاد کرد به این معنی است که احتمالا این تست بایستی در جای دیگری تعریف شود.
به عنوان مثال فرض کنید قرار است کلاسی نوشته شود که اعتبار ورودی ها را بررسی می نماید. بیشتر ورودی ها اطلاعات معمولی مثل نام، نام خانوادگی و ... هستند اما ورودی مثل تاریخ تولد و سن هم وجود دارد. به عنوان مثال فرض کنید نام این کلاس ClientDetailsValidatorTest باشد که توابع testShouldFailForMissingSurname و testShouldFailForMissingTitle را پیاده سازی می نماید.
سپس باید تاریخ تولد، سن و مطابقت آن ها را بررسی نماییم. به عنوان مثال باید بررسی کنیم که اگر تاریخ تولد و سن هر دو وارد شده باشند با هم تطابق دارند یا خیر. اگر فقط تاریخ تولد وارد شده است سن چگونه باید حساب شود؟ اگر تلاش کنید برای این قابلیت ها تست هایی ایجاد نمایید متوجه می شوید که نام توابع تست به شدت نامنظم می شود و برای آن ها نمی توان از قالب پیشنهادی استفاده نمود. پس به نظر می رسد بهتر است این قابلیت را به کلاس دیگری (مثلا AgeCalculator) واگذار کرد و برای این کلاس تست های مربوطه را در کلاس AgeCalculatorTest ایجاد نمود. به این ترتیب تمام قابلیت هایی که برای محاسبه ی سن ایجاد شده اند و تست های آن ها در قالب مورد نظر در این کلاس قرار داده می شود. 
اگر مشخص شود که کلاس بیش از یک کار انجام می دهد معمولا باید کلاس دیگری تعریف نماییم که وظیفه ی دوم را انجام دهد. بهتر است رابط برنامه نویسی به این منظور ایجاد نماییم و کلاسی تعریف کنیم که این رابط برنامه نویسی را پیاده سازی نماید. هنگام ایجاد کلاس اصلی نمونه ای از این رابط برنامه نویسی را به کلاس بفرستیم. به عنوان مثال به صورت زیر
public class ClientDetailsValidator {

	private final AgeCalculator ageCalc;

	public ClientDetailsValidator(AgeCalculator ageCalc) {
		this.ageCalc = ageCalc;
	}
}
این الگو تزریق وابستگی ها نامیده می شود و به ویژه برای تست الگوی مناسبی است زیرا هنگام تست می توان از این الگو برای ارسال اشیاء قلابی استفاده نمود.

اهمیت عنوان مناسب تست هنگام بروز خطا

در صورتی که از عنوان واضح و با معنی برای تست استفاده نماییم در صورت بروز خطا می توان خیلی سریعتر دلیل بروز خطا را پیدا کرد. به این صورت که از عنوان تست می توان به سرعت به آن بخش از کد که باعث بروز خطا گردیده است دست پیدا کرد. در صورتی که اجرا تست با خطا مواجه شود یکی از سه مورد زیر رخ داده است:
  • خطای برنامه نویسی (Bug) جدیدی به سیستم اضافه شده است که برای برطرف شدن خطا باید این خطای برنامه نویسی را برطرف کنیم.
  • رفتار مورد نظر همچنان توسط سیستم انجام می شود اما به بخشی دیگر از سیستم منتقل شده است. برای رفع مشکل در این حالت بهتر است تست را نیز به بخش دیگری از برنامه منتقل نماییم.
  • رفتار سابق برنامه دیگر قابل پذیرش نیست. در این حالت باید تست را حذف نماییم.
مورد آخر بیشتر از موارد دیگر در پروژه های چابک اتفاق می افتد زیرا در این پروژه ها شناخت ما به راجع به سیستم به تدریج تکامل پیدا می کند. اما کسانی که تازه شروع به استفاده از توسعه بر مبنای تست کرده اند معمولا از حذف کردن تست ها وحشت دارند چون می ترسند با حذف تست خطای برنامه نویسی جدیدی به سیستم اضافه شود یا کیفیت کد کاهش پیدا کند.
استفاده از کلمه ی "باید" در قالب پیشنهادی به ما یادآوری می کند که آیا واقعا این کلاس باید این قابلیت را ارائه کند یا خیر. بنابراین راحتتر می توانیم تصمیم بگیریم خطا در تست به دلیل تعریف خطای برنامه نویسی جدید است یا این که فرض سابق ما راجع به سیستم اکنون نامعتبر گردیده است.

رفتار مناسب تر از تست

بسیاری از افراد به دلیل وجود کلمه ی "تست" در روش توسعه مبتنی بر تست گمان می کنند این روش تنها راجع به تست سیستم است در حالی که این روش راجع به نحوه ی طراحی سیستم می باشد. به نظر می رسد پیش فرض افراد راجع به کلمه ی تست باعث دامن زدن به این کژفهمی شده است.

تجربه نشان داده است استقاده از واژه ی "رفتار" به جای "تست" می تواند ابهام را از بین ببرد. با این جایگزینی توسعه دهندگان راجع به این که چه چیزی بایستی تست شود؟ محدوده ی هر تست کدام است؟ شرایط تست ها چیست؟ تست ها بهتر است چگونه نام گذاری شوند و ... ابهام ندارند. با توجه به آن که رفتار، مفهومی عینی (و غیر انتزاعی) است توسعه دهندگان می توانند راجع به آن ها با مشتری صحبت کنند و توسعه ی خود را مبتنی بر این روش انجام دهند.

JBehave ابزاری برای توسعه بر اساس رفتار زبان برنامه نویسی جاوا

اواخر سال 2003، Dan North ابزاری برای توسعه بر اساس رفتار در جاوا طرحی کرد. با استفاده از JBehave می توان رفتار سیستم را بیان کرد و پیوسته رصد نمود که آیا این رفتار توسط سیستم پاسخ داده می شوند یا خیر. مزیت استفاده از این روش این است که بر خلاف تست واحد، کسانی که این رفتارها را تعریف می نمایند لازم نیست به زبان های برنامه نویسی و جزئیات پیاده سازی مسلط باشند بلکه می توان رفتارهای سیستم را به زبان محاوره ای انگلیسی بیان نمود. به این ترتیب خود مشتری می توان رفتارهای مورد نظر خود را بیان کند. در ادامه برنامه نویس می تواند شرایطی را فراهم کند که تضمین نماید این رفتار به درستی پیاده سازی می شوند و با هر بار اجرای برنامه (مشابه تست واحد) رفتارهای ثبت شده بررسی می گردند تا تضمین شود رفتار قبلی سیستم به اشتباه تغییر داده نشده است.
به عنوان مثال کلاس CustomerLookup مفروض است. می خواهیم برای رفتارهای این کلاس، کلاس CustomerLookupBehavior را ایجاد کنیم که تعریف رفتارهای کلاس CustomerLookup را در خود جای داده است. کلاس CustomerLookupBehavior توابعی در خود جای داده است که همگی با عبارت should (باید) آغاز می شوند. اجرا کننده ی رفتارها هر کدام از این توابع را اجرا نموده و موفقیت یا خطا در فراهم نمودن رفتار را بررسی می کند.
اولین قدم Dan North برای پیاده سازی JBehave این بوده است که با همین ابزار بتواند عملکرد JBehave را تایید نماید. (دقیقا مشابه کاری که Kent Beck برای پیاده سازی JUnit انجام داده است.) بنابراین در ابتدا وی تنها قابلیت هایی را به این ابزار اضافه کرده است که برای بررسی رفتارهای همین ابزار کافی باشند. با استفاده از این ابزار می توان (مشابه JUnit) از عملکرد سیستم به سرعت بازخورد مناسب را دریافت کرد.

تعیین قابلیت برای پیاده سازی

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

public void shouldDoX() {
    // ...
}
به این طریق می توانیم به یکی دیگر از سوالاتی که در روش توسعه مبتنی بر تست باعث ابهام در کار توسعه دهندگان می شود پاسخ دهیم. از کجا باید تست ها را آغاز کرد و در هر مرحله تست بعدی که باید پیاده سازی شود کدام است؟

نیازمندی ها به مثابه رفتار سیستم

استفاده از روش توسعه مبتنی مزایای روش توسعه مبتنی بر تست را فراهم کرده و از معایب آن جلوگیری می کند. در سال 2004 زمانی که Dan North این روش توسعه را برای یکی از همکارانش توضیح می داد وی به Dan North گفته است: "این رفتارها در واقع تحلیل سیستم هستند." آن ها به این نتیجه رسیده اند که اگر بتوانند زبانی برای توصیف رفتارهای سیستم تعریف نمایند که توسط تحلیل گر، برنامه نویس، مدیر و تمام اعضای تیم به صورت یکسان قابل فهم باشند می توان از رفتار سیستم برای تحلیل سیستم استفاده نمایند. به این ترتیب ابتدا تحلیل را بر مبنای چندین رفتار انجام می دهیم سپس این رفتارها را در قالب رسمی که برای همگان قابل فهم باشند توصیف می نماییم سپس در هر مرحله مهمترین قابلیت را انتخاب کرده و آن را پیاده سازی می کنیم. استفاده از ابزارهایی شبیه  Jbehave تضمین می نمایند دقیقا همان رفتارهایی که مشتری در نظر داشته است به درستی پیاده سازی شده اند. به این ترتیب مشکل کژفهمی در درک نیازمندی ها از بین می رود.

زبانی برای تحلیل سیستم

مصادف با تلاش های Dan North برای تحلیل بر مبنای رفتار کتاب Eric Evans راجع به طراحی بر اساس دامنه (Domain-Driven Design) منتشر گردید. در این کتاب زبانی مطرح شده است که بر اساس آن نیازمندی ها توصیف می گردند. بنابراین نیازمندی ها به صورت مستقیم به پیاده سازی کد منجر می شوند.
Dan North متوجه شد آن ها به دنبال کشف زبانی برای توصیف فرایند تحلیل بوده اند! برای شروع کار توجه آن ها به قالب توصیف داستان های کاربری جلب شد. این قالب از سه جزء اساسی تشکیل شده است. که به صورت زیر هستند :

  • به عنوان X
  • Y را می خواهم
  • تا Z
در این ساختار Y رفتاری است که بایستی پیاده سازی شود، Z ارزشی است که از پیاده سازی این قابلیت فراهم می شود و X نقشی است که از پیاده سازی این قابلیت سود می برد. قدرت این قالب این است که شما را مجبور می کند از همان ابتدا که یک قابلیت جدید تعریف می کنید ارزشی که ایجاد می شود و نقشی که از آن بهره مند می شود را مد نظر قرار دهید.
در صورتی که قصد داشته باشیم قابلیت جدید تعریف کنیم که برای هیچ کدام از ذی نفعان ارزش خاصی ایجاد نمی کند امکان بیان آن در این قالب وجود ندارد بنابراین این قالب به ما کمک می کند که محدوده ی پروژه را کنترل نماییم.
همچنین در این قالب شرط پذیریش نیز تعیین می شود. اگر قابلیتی که در داستان کاربری (در مثال بالا Y) درخواست شده است در اختیار کاربر قرار داده شود قابلیت با موفقیت پیاده سازی شده است و اگر این قابلیت با موفقیت پیاده سازی نشود قابلیت در پیاده سازی شکست خورده است.
قالبی که برای توصیف رفتار سیستم مطرح می گردد باید موجز و انعطاف پذیر باشد تا این حس در هنگام تحلیل ایجاد نشود که این قالب مصنوعی و محدود کننده است در عین حال باید به صورتی دارای ساختار باشد که بتوان داستان کاربری را به این ساختار تقسیم کرد و از آن برای اجزای خودکار استفاده نمود. Dan North و همکارش ساختار زیر را پیشنهاد کرده اند:

با در نظر گرفتن فرض هایی
زمانی که یک اتفاق رخ می دهد
سیستم باید خروجی هایی را تضمین نماید.
به عنوان مثال در ادامه نمونه ی داستان کاربری دریافت پول از خودپرداز بانک را در این قالب بیان می کنیم.

+ عنوان: مشتری از خودپرداز وجه دریافت می کند+
به عنوان یک مشتری
می خواهم از خودپرداز بانک وجه نقد دریافت نمایم.
تا مجبور نباشم برای دریافت وجه در صف بانک منتظر بمانم.

اما چه طور می توان مطمئن شد این داستان کاربری با موفقیت پیاده سازی شده است؟ سناریوهای مختلف برای این داستان کاربری وجود دارد:  به عنوان مثال ممکن است حساب مشتری موجودی نداشته باشد یا مثلا ممکن است مشتری به اندازه ی سقف مجاز برداشت روزانه از دستگاه خودپرداز برداشت کرده باشد یا مثلا اگر دستگاه به اندازه ی کافی پول برای آن که به مشتری تحویل دهد نداشته باشد.
در ادامه دو سناریوی ممکن را بر اساس قالب پیشنهادی توسط Dan North بررسی می کنیم.

+سناریوی اول: حساب اعتباری+
فرض کنید حساب موردنظر اعتباری است
و کارت معتبر است
و دستگاه خودپرداز به اندازه ی کافی پول دارد.
زمانی که مشتری درخواست وجه نقد می نماید.
بایستی مطمئن شویم مبلغ از حساب کاربر کسر گردیده است
و وجه نقد به مشتری تحویل داده شده است
و کارت به مشتری برگردانده شده است.

+سناریوی دوم: بدهی مشتری به بانک بیشتر از سقف اعتبار وی می باشد+
فرض کنید بدهی مشتری به بانک بیشتر از اعتبار وی می باشد
و کارت معتبر است
زمانی که مشتری درخواست وجه نقد می نماید
مطمئن شوید پیغامی خطای واضح به مشتری نشان داده می شود
و دستگاه به مشتری پولی تحویل نمی دهد
و کارت به مشتری برگردانده می شود.

رفتارهای قابل اجرا

در قالب مورد نظر درشت دانگی بخش های مختلف به حدی است که بتواند توسط برنامه اجرا شود. برای استفاده از ابزار JBehave کلاس هایی می کنیم که به ما اجازه می دهند این سناریوها را مستقیما به کلاس های جاوا تبدیل نماییم؛ بنابراین برای هر شرط اولیه کلاسی به صورت زیر ایجاد می شود:

public class AccountIsInCredit implements Given {
	public void setup(World world) {
		...
	}
}

public class CardIsValid implements Given {
	public void setup(World world) {
		...
	}
}
همچنین برای رویداد نیز یک کلاس ایجاد می کنیم:
public class CustomerRequestsCash implements Event {
	public void occurIn(World world) {
		...
	}
}
ابزار JBehave همه ی این موارد را در کنار هم قرار می دهد و اجرا می کند. این ابزار "جهان" ایجاد می کند که محلی برای ذخیره پیش فرض ها است و این "جهان" به تمامی کلاس های پیش فرض درگیر ارسال می شود تا در صورت نیاز در آن تغییراتی ایجاد کرده و آن را به وضعیت مورد نظر خود تبدیل کنند. در مرحله ی بعد JBehave دستور رخ دادن رویداد های مربوطه را در "جهان" تعریف شده صادر می کند. در نهایت کنترل را به تمام خروجی هایی که تعریف کرده ایم تحویل می دهد.
با این روش (استفاده از کلاس برای تعریف پیش فرض ها، رویدادها و خروجی ها) می توان از آن ها در سناریوهای مختلف استفاده نمود. 
در ابتدا این کلاس ها به صورت قلابی پیاده سازی می شوند اما به تدریج با پیاده سازی برنامه، این کلاس ها واقعا پیاده سازی می شوند که منجر می شود در نهایت بتوانیم تست های پذیرش داشته باشیم که عملکرد واقعی برنامه را تست می کنند و دقیقا منطبق بر نیازمندی های مشتری هستند.

آینده ی توسعه بر اساس رفتار

بعد از توقف مختصر ابزار JBehave دوباره در حال توسعه است. هسته ی این ابزار کامل و کاملا پایدار است. مرحله ی بعدی در یکپارچه سازی این ابزار با محیط های برنامه نویسی جاوا مثل Eclipse و IntelliJ IDEA می باشد. در حال حاضر ابزاری تحت عنوان story runner برای بررسی پذیرش/عدم پذیرش شرایط نهایی در حال توسعه می باشد.
۲۱ تیر ۹۵ ، ۰۶:۳۵ ۲ نظر موافقین ۳ مخالفین ۰
ابوذر کمائی