تست نویسی در پایتون با pytest
آموزش پروژهمحور تستنویسی خودکار با pytest در پایتون، از صفر و در قالب ساخت یک پروژه مدیریت کیف پول دیجیتال و تراکنشها (Digital Wallet)
7
درس
- چرا تست خودکار؟ (سناریوی پروژه) تصور کنید یک روز با دهها پیام اعتراض از سوی کاربران سایت مواجه میشوید که همگی از ناپدید شدن موجودی حساب خود پس از واریز وجه شاکی هستند. در این وضعیت، احتمالاً مجبور خواهید شد ساعتها وقت بگذارید و به صورت دستی، فرم واریز را بارها پر کنید تا منشأ خطا را پیدا کنید. این سناریو، یک چالش جدی برای هر برنامهنویسی است که هنوز از تست خودکار استفاده نمیکند. در درس اول، دقیقاً وارد همین چالش کاربردی میشویم. با هم یک سیستم مدیریت کیف پول دیجیتال (Digital Wallet) طراحی میکنیم که وظایفی مانند افزایش موجودی، برداشت وجه و انتقال پول را انجام میدهد. اما برای درک بهتر اهمیت موضوع، عمداً یک خطای محاسباتی پنهان در کدهای آن قرار میدهیم تا ببینیم چگونه یک اشتباه کوچک، حسابکتاب کاربران را مختل میکند. هدف این است که تفاوت زمانبر بودن تست دستی را با سرعت بینظیر تست خودکار مقایسه کنید؛ جایی که پایتون با اجرای چند خط تست، در کمتر از یک ثانیه خطای کد را شناسایی میکند. پس از پایان این درس، رویکرد شما به توسعه نرمافزار تغییر خواهد کرد و دیگر هیچ کدی را بدون داشتن تست، به محیط واقعی منتقل نخواهید کرد. توسعه پروژه را از همین نقطه آغاز میکنیم.
- نصب ابزارها و راهاندازی ساختار پروژه برای شروع تستنویسی، ابتدا باید محیط کاربری و ابزارهای مورد نیازمان را آماده کنیم. در این درس، فریمورک pytest را روی سیستم نصب میکنیم و ساختار استاندارد فایلهای پروژه را میچینیم تا پایتون دقیقاً بداند کدهای اصلی کجا قرار دارند و فایلهای تست را چطور پیدا کند. یادگیری قوانین نامگذاری در pytest یکی از حیاتیترین بخشهای این درس است؛ زیرا این فریمورک بر پایه یک مکانیزم شناسایی خودکار (Test Discovery) کار میکند و اگر اصول نامگذاری پوشهها و توابع را رعایت نکنید، تستهای شما هرگز اجرا نخواهند شد. در ادامه، قدم به قدم ابزارهای لازم را پیکربندی میکنیم تا پروژه کیف پول دیجیتال ما ساختاری حرفهای و آماده برای توسعه پیدا کند. بدون اتلاف وقت، ترمینال خود را باز کنید تا وارد فاز عملیاتی شویم.
- نوشتن اولین تست با دستور assert تا اینجای کار ابزارها را چیدهایم و ویترین پروژه آماده است، اما هنوز هیچ منطق برنامه نویسی یا کد واقعی نداریم که بخواهیم سلامت آن را بسنجیم. در این درس، مستقیماً وارد فاز عملیاتی میشویم؛ قرار است اولین تابع واقعی پروژه، یعنی قابلیت افزایش موجودی در کیف پول دیجیتال را بنویسیم و بلافاصله با نوشتن اولین تست پایتون با دستور assert، مچ باگهای احتمالی آن را بگیریم. بسیاری از برنامهنویسان فکر میکنند ابزارهای ارزیابی نرمافزار پکیجهای پیچیدهای دارند، اما تمام جادوی فریمورک pytest روی یک کلمه کلیدی ساده و بومی در پایتون به نام assert میچرخد. با همین یک کلمه، شما به مفسر پایتون میگویید که خروجی تابع دقیقا باید چه عددی باشد؛ اگر محاسبات درست پیش برود، برنامه چراغ سبز میدهد و اگر حتی یک ریال جابهجا شود، ابزار خط فرمان با گزارش قرمز رنگ جلویتان را میگیرد. در این بخش یاد میگیرید که چطور سناریوی تست خود را به کدهای اصلی برنامه متصل کنید تا با هر تغییر در منطق کیف پول، نیازی به اجرای دستی و چک کردن با چشم نداشته باشید. ترمینال و ادیتور خود را آماده کنید تا اولین تست واقعی و هوشمند خودتان را بنویسید.
- تست سناریوهای تکراری با parametrize وقتی برای اولین بار یاد میگیرید که چطور کدهای اصلی برنامه را با ابزارهای تستنویسی پایتون ارزیابی کنید، همه چیز جذاب است. اما داستان از جایی فرسایشی میشود که مجبور باشید یک تابع را با ده کپی مختلف و مقادیر متفاوت بسنجید. فرض کنید میخواهیم متد برداشت وجه از کیف پول دیجیتال را با مبالغ مختلف، از اعداد خرد گرفته تا ارقام میلیونی و حتی عدد صفر، به چالش بکشیم؛ آیا منطقی است که برای هر کدام از این مقادیر، یک تابع تست مستقل و تکراری بنویسیم؟ قطعا خیر. نوشتن کدهای کپیپیست شده نه تنها حجم فایلهای پروژه را بیدلیل بالا میبرد، بلکه نگهداری و عیبیابی کدهای مالی را به یک کابوس تبدیل میکند. اینجاست که با جادوی تست سناریوهای تکراری در پایتون با parametrize آشنا میشوید. فریمورک pytest یک دکوراتور قدرتمند به نام @pytest.mark.parametrize در اختیارتان میگذارد که به شما اجازه میدهد فقط یک تابع تست هوشمند بنویسید، اما یک لیست کامل از ورودیها و خروجیهای مختلف را به آن تزریق کنید. در این درس، یاد میگیرید که چطور با این ابزار حرفهای، از شر نوشتن تستهای تکراری خلاص شوید و با یک چرخش قلم، ده سناریوی مختلف مالی را در کسری از ثانیه روی کلاس کیف پول دیجیتال خود اجرا کنید. ادیتور خود را باز کنید تا ساختار کدهای خود را به سطح برنامهنویسان ارشد برسانیم.
- تمیزکاری کدهای تست با Fixtures نگاهی به توابع تستی که تا اینجای کار برای پروژه کیف پول دیجیتال نوشتهاید بیندازید. یک الگوی تکراری و آزاردهنده در خطوط اولیه تمام آنها به چشم میخورد: نمونهسازی مداوم از کلاس اصلی. در شروع هر سناریو، مجبور بودهایم خطی مثل wallet = DigitalWallet() را بنویسیم تا یک آبجکت تازه با دارایی اولیه در اختیار داشته باشیم. این فرآیند راهاندازی تکراری، شاید در پروژههای کوچک به چشم نیاید، اما با بزرگ شدن پوشه تست، کدهای شما را سنگین، شلوغ و مستعد خطاهای ناخواسته میکند. توسعهدهندگان ارشد پایتون هرگز وقت خود را تلف نوشتن این کدهای تکراری برای آمادهسازی محیط ارزیابی نرمافزار نمیکنند. اینجاست که تکنیک تمیزکاری کدهای تست با Fixtures به عنوان یک راهکار نجاتبخش وارد بازی میشود. فریمورک یک مکانیزم بومی و فوقالعاده هوشمند به نام فیکسچرهای pytest در اختیارتان میگذارد که وظیفهاش مدیریت وابستگیها و تزریق خودکار نمونههای آماده به توابع ارزیابی است. فرض کنید یک دستیار هوشمند در پشت صحنه پروژه دارید؛ این دستیار قبل از اجرای هر تابع، یک آبجکت کیف پول تمیز، ایزوله و آماده به کار را روی میز کار شما میگذارد و به محض اتمام فرآیند عیبیابی، محیط را برای سناریوی بعدی پاکسازی میکند. در بخش اول این درس، یاد میگیرید که چطور با استفاده از دکوراتور @pytest.fixture از شر کدهای راهاندازی اضافه خلاص شوید، خوانایی پوشه تست خود را به اوج برسانید و یک زیرساخت مقیاسپذیر برای کدهای مالی خود بسازید. ادیتور خود را باز کنید تا فرآیند بهینهسازی کدهای پایتون را شروع کنیم.
- مدیریت طول عمر فیکسچرها (Fixture Scopes) تا اینجای کار آموختیم که فیکسچرهای pytest چطور با نمونهسازی مجدد، یک محیط آزمایشگاهی کاملاً ایزوله برای ارزیابی کدهای پایتون میسازند. اما در پروژههای بزرگتر، همیشه نیاز نداریم که دیتای ما در هر ثانیه بازنشانی شود. گاهی اوقات این تمیزکاریهای مداوم، سرعت اجرای کل فرآیند ارزیابی نرمافزار را به شدت پایین میآورد. فرض کنید در حال توسعه بخش مدیریت کاربران پروژه هستید و برای شروع تستها، باید اتصال به یک دیتابیس بزرگ یا وبسرویس خارجی برقرار شود. اگر ابزار تست مجبور باشد برای تکتک سناریوها این اتصال سنگین را قطع و وصل کند، زمان اجرای پروژه به شکل چشمگیری تلف میشود. راهکار منطقی این است که یک بار این فرآیند را انجام دهیم و دیتای آمادهشده را در چندین تست مختلف به اشتراک بگذاریم. کلید حل این چالش، درک عمیق مفهوم محدوده اجرای فیکسچر یا همان Fixture Scopes است. ما با تغییر این تنظیمات، به فریمورک دستور میدهیم که طول عمر نمونههای آماده چقدر باشد؛ آیا با پایان هر تابع از بین بروند، یا تا پایان اجرای کل پوشه تست در حافظه سیستم باقی بمانند. در ادامه این درس، با پیادهسازی گامبهگام یک بخش مدیریت کاربران واقعی، یاد میگیرید که چطور تعادل کاملی میان سرعت اجرا و امنیت ایزولهسازی کدهای خود برقرار کنید.
- اشتراکگذاری فیکسچرها و تست خطاها تا اینجای کار یاد گرفتیم که چطور با تنظیم محدوده عمر فیکسچرها، کنترل حافظه و سرعت اجرای تستها را به دست بگیریم. اما با بزرگتر شدن پروژه و افزایش تعداد فایلهای تست، با یک چالش جدید مواجه میشویم. اگر بخواهیم از یک فیکسچر مشترک در چندین فایل مجزا استفاده کنیم، مجبوریم آن کدها را در هر فایل کپی کنیم یا مدام درگیر ایمپورتهای طولانی و پیچیده پایتون شویم؛ روشی که ساختار پروژه را شلوغ و مدیریت آن را سخت میکند. در این درس یاد میگیرید که چطور با استفاده از یک فایل جادویی و مرکزی به نام conftest.py، تمام فیکسچرهای پروژه را یکجا جمع کنید و بدون نیاز به حتی یک خط ایمپورت، آنها را به صورت خودکار در تمام توابع تست به اشتراک بگذارید. این کار فرآیند تمیزکاری کدهای تست را به اوج سادگی و بهرهوری میرساند. اما داستان به همینجا ختم نمیشود. یک سیستم احراز هویت یا مدیریت مالی ایدهآل، علاوه بر بررسی رفتارهای درست، باید بتواند در برابر رفتارهای اشتباه و خطاها هم واکنش درستی نشان دهد. به همین دلیل در بخش دوم این درس، سراغ مدیریت استثناها میرویم و یاد میگیریم که چطور با ابزار pytest.raises تست خطاهای پیشرفته را بنویسیم تا مطمئن شویم سیستم در مواجهه با نام کاربری تکراری یا برداشتهای غیرمجاز، دقیقاً همان خطایی را پرتاب میکند که ما انتظارش را داریم.