بیاید قبل از هرچیزی یه مروری کنیم که چرا اصلا باید نرم افزارمون رو تست کنیم !
همیشه نرم افزار هایی که تولید می کنیم به احتمال 90٪ دارای باگ هستند که با رشد نرم افزار قطعا تعداد باگ هاهم بالا می ره! خب چاره چیه ؟ چطور می شه این هارو از بین برد؟
معمولا کاری که اپلیکیشن ها می کردند این بود که خطاهای کاربر رو لاگ می گرفتن یا یه بخشی داشتن که کاربر ها باگ های دیده شده رو گزارش بدن :) ولی خب همیشهه در طول تولید و نگهداری نرم افزار دغدغه این رو داریم که تعداد باگ هارو به حداقل یا به صفر برسونیم ! ولی چطور؟
ساده ترین راهی که می شه انجام داد manual testing هستش! یعنی با تشکیل دادن یه تیم تست که به عنوان کاربر نهایی بیان روی نرم افزار و تک تک بخش هارو تست کنن و باگ هارو پیدا کنن :) ولی کار درستیه ؟ خیر! چرا؟ چون هزینه های بالایی داره که از جمله اونها می شه به زمان زیادی که می گیره و خطای انسانی که می تونه داشته باشه، اشاره کرد !
توی این روش ما برخلاف manual testing که توسط یه تیم انسانی انجام می شد, فرایند تست کردن رو با یک ابزار یا ماشین و بصورت خودکار انجام می دیم، که وابسته به نیازمون تست های مختلفی اعم از unit testing, integration testing, system testing و .. می تونیم داشته باشیم.(توی این مقاله به یونیت تستینگ می پردازیم)
یونیت تستینگ یه روش automation testing هست که ما میایم قسمت/unit هایی از برنامه که می تونه شامل یه تابع/کلاس یا .. باشه ( که به اون SUT(system under test) می گن) رو مستقل ازهم دیگه تست می کنیم تا به انتظاراتی که داریم ازاون قسمت/unit برسیم. خب این یعنی چی؟ بیاین با یه مثال پیش بریم تا بیشتر درکش کنیم :)
فرض کنید شما یه تابع دارید که کارش ثبت نام کردن یوزر هستش..(ما وابسته به نرم افزارمون منطق های مختلفی رو برای ثبت نام به کار می بریم..) خب تابع ما دوتا ارگومان name, email می گیره و این دوتارو اعتبارسنجی می کنه و چک می کنه اگر یوزری بااین مشخصات توی دیتابیس نبود درنهایت بااستفاده از ارگیومنت های داده شده توی دیتابیس یه یوزر جدید ثبت کنه و در خروجی یوزر جدید رو برمی گردونه و بهمون می گه که یوزر با موفقیت ثبت شد !
حالا یونیت تستینگ این تابع به چه شکلی هست؟ ما سه حالت مختلف رو توی این تابع داریم اعم از اعتبارسنجی مقادیر داده شده و یا بعدش چک کردن وجود نداشتن یوزر از قبل و درنهایت ثبت کردن یوزر جدید داخل دیتابیس.. ، پس درنتیجه وابسته به نیاز و مواردی که از تابع انتظار داریم تست هامون رو می نویسیم.. سه تا از تست هایی که می تونیم بنویسیم احتمالا به این شکل باشه:
1- یک تست برای اینکه مطمعن بشیم وقتی سرویس/تابع(SUT) مقادیر موردنیاز رو دریافت نمی کنه یا مقادیر دریافت شده درست نیستن، اررور اعتبارسنجی بگیریم و یوزری در دیتابیس ثبت نشه !
2- اگر یوزری با ایمیل mmd@gmail.com توی دیتابیس بود اررور برگردونه و یوزر جدیدی ثبت نشه !
3- سومین تستی که می تونیم داشته باشیم اینه که وقتی سرویس مقادیر موردنیاز و درست رو دریافت کرد یوزری در دیتابیس ساخته و آن را برگردونه !
همونطور که گفتم وابسته به منطق تابع ما حالت های زیادی رو برای تست کردن خواهیم داشت.. این 3تا مثال بالا رو برای درک بهتر موضوع زدم ..
بدون تست! مشکلش چیه اصلا ؟! تست نداشتن باعث می شه برنامه نویس زمانی که برروی تست نویسی نذاشته برروی رو دیباگ کردن بزاره و این موضوع هی قراره تکرار بشه متاسفانه :) ولی اگه کمی همت کنید و تست بنویسید اطمینان خاطر خواهید داشت که موقع اضافه کردن یه قسمت جدید یا تغییر یه قسمت برنامه از کار نمی افته یا اگر از کار افتاد بلافاصله متوجه می شید و دیباگ می کنید :))
خب خب! بریم سراغ مباحث اصلی یونیت تستینگ :)
1- با دیتابیس درارتباط باشه
2- با fs درارتباط باشه
3- با شبکه در ارتباط باشه
1- fixture 2- exercise 3- asserts 4- teardown
1- Fixture :
این مرحله یعنی فراهم کردن محیط موردنیاز برای رسیدن به سناریو تستی که داریم انجام می دیم !
مثال: فرض کنید ما می خوایم حالت دوم رو تست کنیم که اگر یوزری با ایمیل mmd@gmail.com در دیتابیس وجود داشت سرویس/تابع(SUT) ما اررور برگردونه .. برای این کار قبل از اجرا کردن سرویس/تابع(SUT) ما یوزری با مشخصات mmd@gmail.com وارد دیتابیس می کنیم تا سرویس(SUT) چیزی که انتظار داریم رو برگردونه, همین !
بیاید با یه مثال نه چندان واقعی پیش بریم XD و تست کردن حالت دوم رو انجام بدیم :(جاوااسکریپت)
2- Exercise :
در این مرحله ما سرویس(SUT) خودمون رو اجرا می کنیم؛ بعنوان مثال:
3- Asserts :
در این مرحله ما انتظاراتی که از سرویس(SUT) داریم رو مشخص می کنیم.
ما دو روش داریم که تایید کنیم سرویس طبق انتظار ما عمل کرده یانه که در ادامه و بعداز مورد 4 بهش می پردازیم ..
4- Teardown :
خب حالا نوبتیم که باشه نوبت تخریبه :)
یادتونه توی مرحله اول(Fixture) ما اومدیم یه یوزر با ایمیل mmd@gmail.com به دیتابیس اضافه کردیم!حالا توی این مرحله نوبتش رسیده که دیتای تولید شده و ثبت شده توی دیتابیس رو تخریب کنیم و دیتابیس رو خالی نگه داریم .
تمامی این قسمت هایی که بالا مشاهده کردید چرخه حیات تست ما هستش و هرسری که بصورت خودکار این تست هارو اجرا می کنیم این چرخه شروع و به اتمام می رسه :)
چرا تخریب؟ بیاید فرض کنیم دفعه اول که تست هارو رو اجرا می کنیم توی تست حالت سوم ما (ثبت یوزر جدید) یه دیتا(یوزر) به دیتابیس اضافه کردیم و تست پاس می شه و تمام .. حالا بنظر شما توی بار دوم هم این تست سوم ما پاس می شه؟ خیر! چرا؟! چون توی اجرای تست های قبلی تست حالت سوم ما اومد و یوزری با مشخصاتی که از قبل مشخص کردیم به دیتابیس اضافه کرد و توی اجرای بار دوم دیگه نمی تونه چنین کاری بکنه چون از قبل این یوزر ساخته شده(توی اجرای اول) و تستمون fail می شه :) (احتمالا مثالم ازلحاظ فنی مشکل داشته باشه ولی خب مهم اینه منظور رو می رسونه :) )
خبب از چهار مراحل خارج و وارد یه سری نکات بشیم :)
طبق تعریفی که از یونیت تستینگ داشتیم ما توی هر تست روی یک شی یا قسمت(SUT) تمرکز می کنیم، معمولا قسمت هایی که تست می کنیم به قسمت های دیگری دپندنسی یا وابستگی دارن، مثلا سرویس ثبت نام یوزر قطعا به دیتابیس نیاز و وابستگی داره. مثال:
و اگر همینطوی اون رو تست کنیم قطعا شرایط گفته شده در بالا(درارتباط بودن با دیتابیس) رو داریم و دیگه یونیت تستی نخواهیم داشت! چاره چیه؟ اینجاست که مفهومی به نام Test Double ها وارد می شود :)
اولا Test Double یعنی چی؟!
یعنی بیایم یک شی ساختگی رو به جای شی واقعیِ وابسته به سرویس(SUT)، قرار بدیم و وانمود کنیم که مثلا این همون دیتابیسه :)
درواقع Test Double همون شی ساختگی هست که می سازیم و به سرویس(SUT) می دیم، همین :)
مثال: درحالت تست سوم (عملیات ثبت نام یوزر جدید) قطعا سرویس(SUT) ما باید به دیتابیس دسترسی داشته باشه تا بتونه یوزر رو ثبت کنه و انتظاری که ما داریم رو براورده کنه؛ ما برای یونیت تست نیاز داریم تا یه شی ساختگی رو بجای دیتابیس به تابع بدیم تا کارش رو بااون انجام بده نه دیتابیس واقعی و درنهایت داشتن شرایط بالا :)
که خب انواع مختلفی برای Test Double وجود داره: (types of test double)
- Mock - Stub - Spy - Dummy - Fake
حالا بریم سراغ دو روش برای تایید کردن عملکرد سرویس(SUT) و درواقع انتظاراتی که ازاون داریم:
1- State Verification :
به صورت خیلی خلاصه بگم توی این روش ما میایم چک می کنیم که ایا سرویس(SUT) پاسخ/های مورد انتظار مارو پاس می کنه یا خیر...
2- Behavior Verification :
توی این مورد ما درگیر جزئیات می شیم.. یعنی چک می کنیم که سرویس(SUT) سرویس های وابسته به خودش(مثلا: متد های دیتابیس) رو چطور اجرا می کنه با چه ارگیومنت هایی و ...
معمولا توی یونیت تست تا جایی که امکانش هست تلاش می کنن درگیر Behavior verification نشن! چرا؟ چون با تغییر دادن منطق یا روش ارتباط وابستگی ها با سرویس(SUT) کل تست ها fail می شن و ما این رو نمی خوایم :)
خیلی ممنونم که تا اینجا وقت گذاشتید و مقاله رو مطالعه کردید، من سعی کردم حداقل دانش و درکی که دارم رو دراختیارتون بزارم، لطفا اگر قسمتی از متون اشتباه بود یا می تونست بهتر باشه, خوشحال می شم توی نظرات اعلام کنید تا هم من و هم باقی دوستان استفاده کنیم :)