طراحی، خودکارسازی و جمع‌آوری نتایج تست پروژه gREST

توی نوشته قبلی که راجع به gREST و علل بوجود اومدنش نوشتم، به این موضوع اشاره کردم که براش با استفاده از pytest، یونیت تست نوشتم، اجرای تست‌ها رو هم با استفاده از Travis-CI خودکار کردم و از سرویس Coveralls برای استخراج نتایج استفاده کردم. اینکه بتونم نرم‌افزار آزاد/متن‌باز خوبی رو بنویسم که برای انجام کار قابل اعتماد باشه و تست‌هاش تقریبا تمام کد رو دربر بگیره، خیلی برام مهم بود و به همین دلیل از سرویس‌های موجود استفاده کردم. همچنین از FOSSA برای بررسی تطابق مجوزهای نرم‌افزاری استفاده کردم. به همین خاطر توی این نوشته می‌خوام روند این کار و اینکه چطور تست‌ها رو نوشتم و از چه روش‌هایی استفاده کردم، براتون بگم.

اولین نکته اینه که چرا از pytest استفاده کردم. مهم‌ترین و بهترین علتش اینه که کار باهاش ساده‌اس و یه سری افزونه داره که میشه باهاش (مثلا) Flask رو تست کرد. و چون gREST برمبنای Flask نوشته شده، طبیعتا gREST رو هم میشه تست کرد. اسم اون افزونه pytest-flask هستش و می‌تونید با کمترین دردسر راه‌اندازی و اجراش کنید. دومین نکته هم اینه که هر وقت فرصت کردم یه متن آموزشی در مورد نحوه ساخت یک Restful API با استفاده از gREST می‌نویسم و هدفم از این نوشته فقط موضوع تست هستش.

طبیعتا برای تست Restful API باید HTTP verbs که پشتیبانی می‌کنه رو تست کرد و پارامترهای ورودی مختلف رو روش چک کرد و خروجی رو بررسی کرد. توی gREST افعال GET, POST, PUT, PATCH و DELETE رو پیاده‌سازی کردم، در نتیجه برای این پنج فعل و تمام ورودی‌هاش تست نوشتم.

سه تا فایل تست برای gREST وجود داره که یکی برای تست کردن مولفه validation_rules هستش و دو تای دیگه برمبنای دو تا app هستن که تو مسیر examples وجود دارند. اولین اپ، یه endpoint ساده است که به یه مدل توی دیتابیس به اسم Person اشاره داره و اون یکی یه اپ پیچیده‌تره که رابطه‌ی Person و Pet داخلش تست میشه و رابطه این بین توسط PetInfo مدیریت میشه. در واقع یه nested endpoint به وجود میاد.

برای مثال، در app ساده در کل یه endpoint وجود داره به اسم persons/ که با POST کردن یک JSON با استفاده از یک Rest Client (مثل Postman) می‌تونید یه شخص جدید ایجاد کنید، به همین سادگی. البته دیگه نصب و راه‌اندازی پایتون و Neo4j از نظر من در این مطلب مفروضه.

در app دوم، یک persons/ وجود داره که می‌تونه یک یا چند حیوان خانگی داشته باشه: pets/. برای اینکه بتونیم این app رو تست کنیم، باید یک شخص، یک حیوان خانگی و یک رابطه بین این‌ها ایجاد کنیم که توی تست‌ها می‌تونید ببینید.

یک نمونه تست از کد تستی که نوشتم این شکلیه:

تو این قسمت می‌خوایم بدونیم که شخصی در پایگاه داده وجود داره یا خیر. طبیعتا توی اولین تست، چون ما داده اولیه توی پایگاه داده ایجاد نکردیم، چیزی وجود نداره و انتظار میره که API هم همچین پیغامی رو به ما بده یعنی خطای 404 و پیغام No person exists. باقی تست‌ها هم به همین منوال هستند یعنی یک فعل و یک سری پارامتر ورودی و یک جواب یا خطای مورد انتظار از API. برای مثال همین 4 خط بالا (به علاوه یک خط توضیح)، متد index داخل grest.py رو تست می‌کنه.

تا اینجا یه سری مفاهیم و فایل‌های مهم برای تست رو بهش اشاره کردم. حالا می‌خوام بگم که چطور این تست اجرا میشه و خروجی میده. ساده‌ترین راه برای اجرای این تست، اجرای دستور pytest داخل مسیر پروژه است. با این کار pytest، تست‌های شما رو جمع می‌کنه (اصطلاحاً collect می‌کنه) و یک به یک اجراشون می‌کنه. البته اگر بخوایم اطلاعات پوشش کد که پایین‌تر توضیح میدم رو هم داشته باشیم باید از دستور coverage استفاده کنیم.

این کار ساده رو می‌شه خودکارش کرد که هر وقت کد رو تغییر می‌دید و روی git، ارسال (push) می‌کنید، سیستمی/سرویسی وجود داشته باشه که خودش تست رو انجام بده و نیازی نباشه که این کار رو دستی انجام بدید. یکی از اون سیستم‌ها/سرویس‌ها اسمش Travis-CI هست و از اسمش پیداست که برای Continuous Integration (یکپارچه‌سازی مداوم) بکار میره.

روند کارش هم ساده است. کافیه یه حساب کاربری تو وب‌سایتش ایجاد کنید و پروژه‌تون رو داخلش ثبت کنید. بعدش یه فایل ایجاد کنید توی repo خودتون به اسم travis.yml. و تنظیمات رو داخلش به همراه نسخه پایتون و Neo4j و دیگر تنظیمات مورد نظرتون وارد می‌کنید و منتظر اجرای تست از سمت Travis-CI بمونید. اگر همه چیز رو درست انجام داده باشید، به راحتی تست اجرا میشه و خروجی رو می‌تونید توی قسمت Job log پروژه‌تون ببینید.

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

تست موفقیت آمیز
تست موفقیت آمیز
تست ناموفق
تست ناموفق

برای اینکه خروجی تست، غیر از بحث pass/fail، موضوعات دیگه رو هم دربر بگیره، باید یه سری نرم‌افزار و سرویس دیگه هم استفاده کنید. یکی از اون نرم‌افزار/سرویس‌ها اسمش coveralls هست که باهاش می‌تونید بحث code coverage (پوشش کد) رو انجام بدید به این معنی که تستی که اجرا کردید چه خطوطی از اصل کد رو دربر گرفته و تستشون کرده، چون بعضی اوقات ممکنه unreachable code path داشته باشید یعنی تست شما اون بخش از کد رو بهش نمیرسه که تست کنه، مثلا یک سری از exceptionها ممکنه اتفاق نیافته که البته تست اون‌ها هم راه حل داره که توضیحش بمونه برای بعد!

برای اینکه gREST رو تست کنم در مجموع 32 تا تست نوشتم که هر کدوم بخشی از کد رو تست می‌کنه و مجموعاً این تست‌ها 78 درصد از کد رو تست می‌کنند.

در نهایت از FOSSA برای بررسی تطابق مجوزهای نرم‌افزاری استفاده کردم که یک کتابخانه بود که تطابق نداشت و من با کتابخانه مشابهی عوضش کردم تا مشکل مجوز نرم‌افزاری وجود نداشته باشه.

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