وقتی برای پروژه کیف پول خود دهها تست مختلف مینویسید، شاید احساس کنید کار تمام شده و همهچیز زیر نظر است. اما از کجا مطمئن هستید که تستهای شما واقعاً تمام زوایای کدهای اصلی را لمس کردهاند؟ آیا خط یا بلاک خاصی در بدنه توابع وجود دارد که در طول اجرای تستها اصلاً اجرا نشده باشد؟ اینجاست که با مفهوم علمی و حیاتی پوشش کد یا همان Code Coverage روبرو میشویم.
پوشش تست مثل یک نورافکن قوی عمل میکند که روی کدهای اصلی پروژه میتابد. این ابزار دقیقاً نشان میدهد چند درصد از خطوط کدهایی که نوشتهاید، حداقل یکبار توسط تستها بیدار و اجرا شدهاند. اگر بخشی از کد در تاریکی بماند و تستی برایش اجرا نشود، یعنی آنجا پناهگاهی امن برای باگهای مخفی است.
در اکوسیستم پایتون و فریمورک pytest، استانداردترین روش برای سنجش این معیار، استفاده از ابزاری به نام pytest-cov است. این ابزار در پایان فرآیند ارزیابی نرمافزار، یک گزارش ریاضی و دقیق به شما تحویل میدهد. این گزارش مشخص میکند که کدهای مدیریت کیف پول چقدر به امنیت ۱۰۰ درصدی نزدیک شدهاند.
خلاصه کلام اینکه تعداد بالای تستها بهتنهایی تضمینی برای کیفیت نیست، بلکه میزان پوشش مؤثر آنها روی کدهای اصلی اهمیت دارد. در این درس یاد میگیریم چطور این ابزار سنجش را راهاندازی کنیم، نقاط تاریک و تستنشده پروژه را بکشیم بیرون و با عدد و رقم از سلامت نرمافزار خود دفاع کنیم.
Code Coverage چیست و چرا ۱۰۰ درصد بودن آن همیشه کافی نیست؟
معیار پوشش کد یا همان Code Coverage یک سنجه کمی در مهندسی نرمافزار است که نشان میدهد چه کسر یا درصدی از کدهای منبع پروژه شما هنگام اجرای بسته تستها فعال شدهاند. ابزارهای سنجش معمولاً این کار را با شمارش خطوط اجرا شده نسبت به کل خطوط قابل اجرا محاسبه میکنند. اگر پروژه کیف پول شما ۱۰۰ خط کد داشته باشد و تستهایتان باعث اجرای ۸۵ خط آن شوند، پوشش کد شما ۸۵ درصد است.
اما یک مغالطه بزرگ در دنیای توسعه نرمافزار وجود دارد: «پوشش ۱۰۰ درصدی یعنی کد ما هیچ باگی ندارد». این تصور کاملاً اشتباه است. مستندات علمی و تجربیات استاندارد فریمورکهای تست نشان میدهند که پوشش خطوط (Line Coverage) لزوماً به معنی پوشش شاخهها یا سناریوهای منطقی (Branch Coverage) نیست.
فرض کنید تابعی دارید که دو عدد را بر هم تقسیم میکند. اگر تستی بنویسید که عدد ۱۰ را بر ۲ تقسیم کند، ابزار سنجش اعلام میکند که تمام خطوط این تابع اجرا شدهاند و پوشش آن ۱۰۰ درصد است. اما اگر کاربر عدد صفر را به عنوان مقسومعلیه وارد کند چه؟ کد شما با خطای ZeroDivisionError متوقف میشود، با اینکه ابزار پوششسنج قبلاً به شما نمره کامل داده بود.
ابزار بررسی پوشش کد، فقط به ما میگوید کدام خطوط کد اجرا شدهاند، اما هرگز نمیتواند بفهمد آیا منطقی که پشت آن خطوط نوشتهاید برای تمام سناریوهای دنیای واقعی درست است یا نه. ما از این ابزار برای پیدا کردن بخشهای کاملاً فراموششده و تاریک پروژه استفاده میکنیم، اما کیفیت نهایی ارزیابی نرمافزار همچنان به هوشمندی شما در طراحی سناریوهای تست و در نظر گرفتن حالتهای مرزی بستگی دارد.
راهاندازی و اجرای اولین گزارش با پلاگین pytest-cov
برای اینکه ابزار سنجش پوشش کد را وارد پروژه کیف پول کنیم، ابتدا باید ابزار استاندارد این کار یعنی پلاگین pytest-cov را نصب کنیم. این ابزار به عنوان یک افزونه رسمی برای فریمورک pytest عمل میکند و هماهنگی کاملی با ساختار تستهای ما دارد.
با اجرای این دستور ساده در ترمینال، ابزار مورد نیاز روی محیط مجازی شما قرار میگیرد:
pip install pytest-cov
نصب کردن این پلاگین مثل این است که یک بورد الکترونیکی جدید روی دستگاه سنجش خود بگذارید. حالا فریمورک تست شما یک قابلیت جدید پیدا کرده است که میتواند همزمان با اجرای تستها، خطوط کدهای اصلی را هم ردیف و شمارش کند.
برای گرفتن اولین گزارش، باید نام ماژول یا فولدری که کدهای اصلی پروژه در آن قرار دارد را به ابزار معرفی کنیم. فرض کنیم کدهای کیف پول شما در فولدری به نام wallet قرار دارد. فرمان زیر را در ترمینال اجرا میکنید:
pytest --cov=wallet
وقتی این دستور را اجرا میکنید، سیستم ابتدا تمام تستهای موجود را تا آخرین مورد اجرا میکند. به محض تمام شدن ارزیابی نرمافزار، پلاگین دست به کار میشود و یک جدول متنی تمیز و خلاصه درون همان ترمینال به شما نشان میدهد. این جدول حاوی اطلاعات حیاتی از جمله تعداد کل خطوط، خطوط اجرا نشده و درصد نهایی پوشش کدهای شماست.
خلاصه کلام اینکه با همین یک دستور، فرآیند ارزیابی کدهای شما از حالت حدسی خارج شده و به یک گزارش مستند تبدیل میشود. این ساختار اولیه به شما کمک میکند تا در سریعترین زمان ممکن وضعیت کلی پروژه را بسنجید.
تحلیل گزارش خروجی و شناسایی خطوط تاریک پروژه
وقتی ابزار pytest-cov اجرای تستها را تمام میکند، یک جدول متنی با چهار ستون اصلی در ترمینال میبینید. فهمیدن دقیق مفهوم هر کدام از این ستونها به شما کمک میکند تا مثل یک مهندس نرمافزار حرفهای، نقاط ضعف کد خود را پیدا کنید.
بیایید اجزای این جدول را با هم کالبدشکافی کنیم:
| مفهوم فنی و کاربرد آن | |
| Name | نام فایل یا ماژولی که مورد ارزیابی قرار گرفته است. |
| Stmts | تعداد کل خطوط کد قابل اجرا در آن فایل (بدون احتساب کامنتها و خطوط خالی). |
| Miss | تعداد خطوطی که در طول اجرای تستها، پایتون اصلاً سراغشان نرفته است. |
| Cover | درصد نهایی پوشش کد که از فرمول ریاضی مشخصی به دست میآید. |
نحوه محاسبه ستون آخر ساده است؛ ابزار سنجش از این فرمول استفاده میکند:
ستون اصلی که باید مثل یک کارآگاه به آن زل بزنید، ستون Missing است. در این ستون، شماره خطوطی از کد که تست نشدهاند به صورت لیستی از اعداد (مثلاً ۲۳-۲۵، ۴۲) نمایش داده میشود. این اعداد دقیقاً همان خطوط تاریک و خطرناک پروژه کیف پول شما هستند.
فرض کنید در فایل کدهای خود یک شرط گذاشتهاید که اگر موجودی کاربر منفی شد، یک خطای خاص پرتاب کند. اگر در کل فایلهای تست خود، هیچ سناریویی برای موجودی منفی ننوشته باشید، شماره خطوط مربوط به آن شرط در ستون Missing ظاهر میشود. این یعنی ابزار سنجش مچ شما را گرفته و میگوید: «این بخش از کد هنوز در دنیای واقعی آزمایش نشده است».
به صورت کلی ستون Miss نقشه راه بعدی شما برای توسعه است. هر شماره خطی که در این ستون میبینید، یک دستور کار مستقیم برای نوشتن یک تست جدید است تا مطمئن شوید هیچ غافلگیری عجیبی در محیط عملیاتی منتظر کدهای شما نیست.
جادوی گزارشهای بصری با خروجی HTML
خواندن گزارشهای متنی در ترمینال برای بررسیهای سریع عالی است، اما وقتی تعداد فایلهای پروژه زیاد میشود، پیدا کردن خطوط دقیق کدهای تستنشده میان انبوهی از اعداد کار سختی خواهد بود. ابزار pytest-cov برای حل این مشکل، قابلیت تولید گزارشهای بصری و تعاملی در قالب فایلهای HTML را در اختیار ما میگذارد.
برای تبدیل آن جدول سیاه و سفید به یک صفحه وب رنگی و پویا، کافی است فلگ گزارشدهی را در دستور خود تغییر دهید:
pytest --cov=wallet --cov-report=html
با اجرای این دستور، پایتون فولدری به نام htmlcov در محیط پروژه شما میسازد. وقتی فایل index.html درون این فولدر را با مرورگر باز کنید، با یک داشبورد مدیریتی جذاب مواجه میشوید که لیست تمام فایلها را به همراه نمودار درصدی پوشش آنها نشان میدهد.
بذار ساده بگم؛ این گزارش مثل یک نقشه حرارتی برای کدهای شماست. وقتی روی نام هر فایل کلیک میکنید، بدنه کامل کدهای آن فایل باز میشود. ابزار سنجش، خطوط کد را با سه رنگ استاندارد تفکیک میکند: خطوط سبز نشاندهنده کدهایی هستند که با موفقیت تست شدهاند، خطوط قرمز بخشهایی را نشان میدهند که حتی یکبار هم اجرا نشدهاند، و خطوط زرد مربوط به بخشهایی مانند کامنتها هستند که نیازی به تست ندارند.
خلاصه کلام اینکه گزارش HTML فرآیند ارزیابی نرمافزار را از یک کار خشک مهندسی به یک بررسی چشمی راحت تبدیل میکند. شما مستقیماً روی بخشهای قرمز رنگ تمرکز میکنید و متوجه میشوید که مثلاً بلاک else در فلان شرطِ کیف پول کاملاً فراموش شده است.
نادیده گرفتن بخشهای خاصی از کد با فایل پیکربندی coveragerc.
در یک پروژه واقعی، خطوطی از کد وجود دارند که آزمایش کردن آنها عملاً غیرممکن است یا اصلاً ارزش مهندسی ندارد. برای مثال، کدهایی که فقط برای نمایش خطا در زیرساختهای شبکه نوشته شدهاند، یا متدهای جادویی پایتون (مثل __str__) که تأثیری روی منطق کیف پول ندارند. اگر این خطوط را به حال خود رها کنید، درصد گزارش نهایی شما را پایین میآورند و اجازه نمیدهند وضعیت واقعی پوشش کدهایتان را ببینید.
کتابخانه سنجش پوشش برای حل این مشکل، یک فایل تنظیمات رسمی به نام .coveragerc در اختیار ما میگذارد. شما این فایل را در ریشه اصلی پروژه میسازید تا مثل یک فیلتر هوشمند عمل کند و به پایتون بگوید کدام بخشها را در محاسبات ریاضی خود نادیده بگیرد.
بذار ساده بگم؛ این فایل مثل یک لیست استثنائات است. شما فرمولها یا کلمات خاصی را به آن معرفی میکنید و ابزار به محض دیدن آنها در کد، بدون محاسبه درصد منفي، از رویشان میپرد.
تنظیمات این فایل را به این صورت مینویسند:
[run]
omit =
# نادیده گرفتن تمام فایلهای تست در گزارش نهایی
tests/*
# نادیده گرفتن فایل تنظیمات اصلی
wallet/config.py
[report]
exclude_lines =
# نادیده گرفتن کدهای مربوط به دیباگ و پرینت
pragma: no cover
if __name__ == .__main__.:
به عبارت pragma: no cover در تنظیمات بالا دقت کنید. این یک قابلیت استاندارد و بسیار هوشمندانه است. از این به بعد، اگر در هر کجای پروژه کیف پول، این عبارت را به صورت کامنت جلوی یک خط کد بگذارید، ابزار سنجش آن خط را کاملاً نادیده میگیرد.
با این حساب با مدیریت فایل .coveragerc، شما گزارشهای ارزیابی نرمافزار خود را از دادههای پرت و بیاهمیت پاکسازی میکنید. این کار باعث میشود درصد پوششی که به دست میآورید، یک عدد واقعی، قابل اتکا و بدون نویز باشد.