تست کدها در اندروید – بخش اول

نویسنده : سید ایوب کوکبی ۲۸ تیر ۱۳۹۷

تست کدها در اندروید - بخش اول

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

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

خب بدون فوت وقت، برویم سراغ اصل مطلب.

اصلاً دلیل اهمیت تست چیست؟

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

افزایش سطح اطمینان کد

بارها پیش آمده، توسعه‌دهندگانی را دیده‌ام که به محض اضافه کردن فیچر جدیدی به برنامه، آن را به تیم تضمین کیفی (QA) می‌دهند تا ببینند مشکلی دارد یا نه. در چنین شرایطی ممکن است ساده‌ترین تست‌های پذیرش محصول (Acceptance Test) هم با مشکل مواجه شود. این در حالی است که اگر توسعه‌دهنده هنگام کدنویسی، برنامه را تست می‌کرد چنین اتفاقی نمی‌افتاد. شناسایی حالت‌های مختلف موفقیت و شکست کد و نوشتن تست برای این سناریوها باعث میشود تا کدی مطمئن و بدون مشکل داشته باشیم؛ کدی که در مراحل بعدی تست هم سربلند بیرون بیاید.

رهایی از شر باگ‌های رگرسیون

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

بخوانید  قسمت دوم :اکتیویتی ها

کدهای مینیمال‌تر

وقتی شروع می‌کنید به نوشتن تست، کدهایی که می‌نویسید کوتاه‌تر می‌شوند. اگر با توسعه‌ی آزمون‌محور (TDD: Test Driven Development) آشنا باشید (اگر نبودید حتماً لینک را مطالعه کنید) می‌دانید که در این رویکرد درست به همان میزانی کد نوشته می‌شود که نیاز باشد؛ نه بیشتر و نه کمتر. یعنی به اندازه‌ای کد می‌نویسید که تست مورد نظر را پاس کنید. در واقع این روش، مدت زمان لازم برای توسعه و اشکال‌زدایی کد را کاهش می‌دهد چرا که کدها مختصر و مفید هستند و در لابه‌لایشان کدهای اضافی، تزئینی وجود ندارد. همه چیز به اندازه و کافی. به قول سرآشپزها: نمک به میزان لازم!

هرم تست : آشنایی با انواع تست و جایگاه آن در فرایند تست

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

هرم تست

آزمون واحد (Unit Test)

بخش عمده‌ی تست‌هایی که در فرایند توسعه نوشته می‌شوند همین آزمون‌های واحد است. تقریباً بیش از ۷۰ درصد تست‌ها متعلق به Unit Test است. این تست‌ها بسیار کوچک و متمرکز بوده و روی سیستم توسعه‌دهنده اجرا می‌شود. از آنجایی که اجرای این تست‌ها به صورت لوکال در خودِ سیستم صورت می‌گیرد، خیلی سریع اجرا می‌شوند. آزمون واحد برای منطق تجاری کد (Business logic) نوشته می‌شود و معمولاً در سطح متدهاست. مثلاً فلان متد دو عدد را دریافت و مجموعشان را برمی‌گرداند. شما با نوشتن چندین تست در برابر ورودی‌های مختلف مثلا دو عدد منفی، یک عدد منفی و یک عدد مثبت، دو عدد برابر، دو عدد خیلی بزرگ و … صحت عملکرد آن متد را تست می‌کنید.

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

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

بخوانید  Clean Code - کتابی که هر توسعه‌دهنده‌ای باید بخواند

تست یکپارچگی (Integration Test)

این نوع تست روی دستگاه‌های واقعی اجرا می‌شود و هماهنگی و یکپارچگی بین ماژول‌های مختلف برنامه را آزمایش می‌کند. این آزمون معمولاً کارکرد دو ماژول در کنار هم را بررسی می‌کند. اینکه آیا عملکرد آن‌ها در کنار هم آنطوری که باید باشد، هست یا نه. تست یکپارچگی کاملاً با تست منطق کد متفاوت است. به لحاظ اندازه در رده‌ی میانی قرار دارد (نه یه کوچکی آزمون واحد و نه به بزرگی آزمون end to end)  و معمولاً ۲۰ درصد کل تست‌های برنامه را در بر می‌گیرد.

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

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

آزمون End-to-End

در برنامه‌نویسی اندروید معمولاً خیلی کم نیازمند نوشتن این نوع تست هستید ولی در حالت ایده‌آل ۱۰ درصد کل تست‌ها مربوط به آزمون End-To-End است. همانطور که از نامش پیداست این نوع تست جریان کامل برنامه را از یک طرف تا طرف دیگر را بررسی می‌کند.

فرض کنید فرایند ثبت‌نام در برنامه شما، شامل چندین مرحله است و شما می‌خواهید صحت جریان مرحله به مرحله این فرایند را تست کنید. اینجاست که باید تست end-to-end انجام دهید. این تست نیز همانند Integration Test روی دستگاه اصلی یا ایمیولیتور اجرا می‌شود بنابراین سرعت اجرایش پایین است.

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

انواع تست‌ها در اکوسیستم اندروید

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

تست‌های محلی

این تست‌ها همان‌هایی هستند که همچون آزمون واحد روی کامپیوتر اجرا می‌شوند. این تست‌ها در مسیر module/src/test/java مستقر هستند و هیچ نوع دسترسی به واسط برنامه‌نویسی فریم‌ورک اندروید (Android Framework APIs) ندارند.

بخوانید  پیش گفتار:چرا من باید برنامه نویس اندروید بشم؟!

فرض کنید می‌خواهید عملکرد متدی تحت عنوان (validateEmailAddress(String را آزمایش کنید. برای این کار بایستی سناریوهای مختلفی برای رشته‌ی ورودی در نظر بگیرید و برای هر کدام یک تست کوچک بنویسید. دقت کنید هر تست فقط و فقط یک کار باید انجام دهد. مثلاً اگر رشته تهی (null) بود، خالی بود، معتبر نبود و … .

برای آزمون واحد در محیط اندروید استودیو از چارچوب محبوب JUnit استفاده می‌کنیم که به ما کمک می‌کند تا تست‌هایی شفاف و قابل درک بنویسیم. البته چارچوب‌های خوب دیگری نظیر Robolectric نیز وجود دارند که بنا به سلیقه‌ی شخصی می‌توانید انتخاب کنید. اما به خاطر محبوبیت بیشتر JUnit استفاده از این فریم‌ورک را توصیه می‌کنم.

تست‌های قابل اجرا روی دستگاه

این‌ تست‌ها در دسته Integration Test و End-To-End Test قرار می‌گیرند که طبق توضیحات گذشته روی دستگاه یا ایمیلیتور اجرا می‌شوند. همه این تست‌ها در مسیر module/src/androidTest/java قرار دارند. توجه کنید که صرفنظر از پیشوند android در ابتدای این تست‌ها که نشان‌دهنده‌ی نوع آن‌هاست، همه‌ی این تست‌ها روی دستگاه اندرویدی یا ایمیلیتور اجرا می‌شوند. بنابراین دسترسی کاملی به وابستگی‌های اندروید (Android Dependencies) وجود دارد. این وابستگی‌ها معمولاً Source sets عنوان می‌شوند.

این تست‌ها بسیار کند اجرا می‌شوند چرا که همراه برنامه، هر بار یک فایل test APK هم برای آزمون‌ها تولید می‌شود. هر دو apk روی یک پردازه (process) اجرا می‌شوند تا به فیلدها و متدهای برنامه‌ی اصلی دسترسی وجود داشته باشد و بتوان تعاملات کاربر را خودکار کرد. شما می‌توانید از این نوع تست‌ها برای خودکارسازی کلیک روی دکمه‌ها، تایپ کردن چیزی در فیلدهای متنی، سوایپ کردن ویوها، اسکرول کردن لیست و اجرای هر نوع عملیات دیگری که تنها توسط کاربر و به صورت دستی قابل اجراست بهره ببرید.

این تست‌ها در ادبیات اندروید به Instrumentation Tests یاد می‌شوند. اما چرا این نام‌گذاری؟ چون اندروید به جای اینکه به سیستم، اجازه‌ی کنترل کامپوننت‌ها را بدهد از طریق InstrumentationRegistry به Android Instrumentation API دسترسی داشته و کامپونت‌ها و چرخه‌ی حیاتشان را به صورت دستی کنترل می‌کند.

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

خب، احتمالاً تا الان فهمیده‌اید که فایده‌ی نوشتن تست‌ها چیست. همچنین درک خوبی از انواع تست‌ها و تفاوتشان با هم کسب کرده‌اید. در این مقاله تازه بخش کوچکی از دنیای تست در فریم‌ورک اندروید مطرح شد. ان‌شاءا… در بخش‌های بعدی حقایق بیشتری درباره‌ی تست خواهم گفت، با ابزارهای مختلف و فریم‌ورک‌های بیشتری آشنا خواهید شد و اطلاعات اندک شما به مرور قوت خواهد گرفت.

سید ایوب کوکبی

نویسنده و مترجم...

0 دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *