/Unit-Testing-in-Python-Best-Practices

Translation of Unit Testing in Python & Best Practices

MIT LicenseMIT

Unit testing in python best practices

| Note :این ترجمه ی مقاله ای هست که صدرا نوشته

در این مقاله من به شما کمک میکنم تا مفاهیم اولیه Unit Testing در پایتون را فرا بگیرید، و بعد درمورد بست پرکتیس ها صحبت میکنیم که تست هایتان را بسیار قابل فهم تر میکنند. اگر به دنبال جایی برای شروع تست نوشتن برای پروژه های پایتونتان میگردید، اینجا بهترین جاست. بیایید کمی جادو ببینیم.

۱. تست نرم افزار & Unit Test ها

تست نرم افزار بخشی است که ما پروژه امان را تست میکنیم تا مطمئن شویم ارور های درستی را برمیگرداند، مقادیر مورد انتظار را میدهد و... . ما میتوانیم پروژه امان را با چندین روش تست کنیم. برای همین است که چندین راه حل برای تست پروژه ها داریم. یکی از این راه حل ها Unit Testing است.

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

تصور کنید نرم افزاری وجود داشت که تمامی مراکز نرم افزارتان را تست میکرد و گزارشی به شما تحویل میداد! اگر من بودم، همچین محصولی را حتما میخریدم.

ما میدانیم که Unit test ها چه چیزی هستند. هر پروژه ای از مراکز مختلفی ساخته شده. تمامیت بین این مراکز یک سیستم بی عیبی درست میکند که نیازی به روش تست نویسی خودش به نام Integration Testing دارد. بیایید به Unit Test ها برگردیم.

۲. اصلا چرا تست بنویسیم؟

بسیاری از توسعه دهندگان هنگام نوشتن تست ها با مشکل مواجه هستند. مثلا شاید پروژه ی شما بدون تست ها به خوبی کار کند، اما آیا به راحتی قابل نگه داری هستند؟ آیا بقیه ی توسعه دهندگان از کد نویسی در پروژه ی شما خوشحال هستند؟ آیا مشارکت کنندگان جدید مرحله ی آشناییت با پروژه ی شمارا به سرعت رد میکنند ؟

  • پوشش و نگه داری را بیشتر کنید.

بیشتر پروژه ها با وضعیت پوشش دهی تست هایشیان قضاوت میشوند. پوشش دهی بیشتر، توسعه دهندگان خوشحال تر.

  • با تست ها، مشکلات را قبل از پیاده سازی حل کنید.

در برنامه نویسی TDD توسعه دهندگان تلاش میکنند تا قبل از پیاده سازی اصلی، تست بنویسند. آنها قبل از اضافه کردن یک قابلیت به برنامه، آن را تست. و فقط هنگامی که از تست ها سربلند بیرون آمد، مرجش میکنند.

  • تست ها مثل مستندات هستند.

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

کد بیشتر از نوشته شدن خوانده می شود.

۳. تست های خود را ایزوله کنید

زمانی که حرف ایزوله سازی در پایتون میشود، به یاد محیط های مجازی میوفتم. یک جورهایی یکی هستند، اما در اصل ما آنهارا برای تست نوشتن داریم.

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

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

۴. بیایید کمی کد بنویسیم

صحبت کردن بس است، در پایتون، ما چندین ابزار قدرتمند تست نویسی داریم. ابزاری که امروز از آن استفاده میکنیم کتابخانه استاندارد unittest که قابلیت های مختلفی بدون نصب کردن هیچ پکیج یا کتابخانه ی اضافه ای را میدهد. همچنین میتوانید از PyTest هم استفاده کنید. یک فایل test.py باز کنید و پیشنیاز ها را ایمپورت کنید.

import unittest
from math import sqrt,power, pi

توجه داشته باشید که ما چند تابع ریاضی هم ایمپورت کردیم. در تست بعدی، چندین تابع از کتابخانه ی استاندارد ریاضی پایتون را امتحان میکنیم تا ببینیم آیا پاسخ درست را به ما میدهد یا نه.

class TestMathLib(unittest.TestCase):

    def test_if_sqrt_still_works(self):
        self.assertEqual(sqrt(25), 5)

    def to_test_if_pi_still_starts_with_three(self):
        self.assertEqual(pi // 1, 3)

    def test_if_power_still_works(self):
        self.assertEqual(pow(2, 3), 8)

تا الان سه تست نوشتیم. بیایید آنرا اجرا کنیم. در پایان فایل، برای قابل اجرا شدن آن از شل، این خط کد را اضافه میکنیم.

if __name__ == '__main__': unittest.main()

استفاده از دستور python برای اجرای تست ها به هیچ عنوان پیشنهاد نمیشود. چه میشود اگر میخواستید تست های اپلیکیشن خاصی را اجرا کنید؟! آن وقت پایتون دیگر نمیتوانست کمک کند. به راحتی از دستور unittest برای پایداری و قابلیت های بیشتر استفاده کنید.

حالا، ترمینالی باز کنید، دایرکتوری خود را به دایرکتوری تست هایتان عوض کنید و این دستور را اجرا کنید.

python -m unittest

و این نتیجه نهایی است.

..
----------------------------------------------------------------------
Ran 2 tests in 0.010s

OK

به نظر میرسد که به نتیجه ای رسیده ایم! از آنجایی که اجرای unitest بدون هیچ فلگی تمامی تست ها را اجرا میکند، چرا فقط دوتای آن هارا اجرا کرد؟ آن نقطه ها چه میگویند؟!

کتابخانه ی unittest فقط توابع تستی را اجرا میکند که با حرف test شروع شده باشند. این بدین معناست، که در تست ها، تست pi اجرا نشده. برای واضح تر شدن قضیه، حرف test را به ابتدای نام متود اضافه کنید.

...
----------------------------------------------------------------------
Ran 3 tests in 0.010s

OK

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

۵. اصول DAMP و DRY در تست ها

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

۵.۱ عبارات توصیفی و معنی دار (DAMP)

همانطور که قبل تر فهمیدیم، کتابخانه ی unittest تست هایی را که با کلمه ی test شروع نشده اند اجرا نمیکند. این همچنین دلیل خوبی است که نام تابعمان را به عبارت طولانی تری مثل test_if_manipulation_works یا test_user_validation تغییر دهیم. اسمی که نشان دهد تست چه کار میکند. همچنین، مطمئن شوید که نام کلاس تستتان با حرف Test شروع شود چون با بزرگ شدن پروژه، شاید نیاز به کلاس های مقلد داشته باشید که نباید جدا اجرا شوند و اینگونه میتوانید توابع تستتان را جدا کنید.

۵.۲ خودتان را تکرار نکنید (DRY)

تصور کنید که به شیء ای از کلاسی به نام Car نیاز داریم تا متد ها و عملکردهایش را تست کنیم.

class TestCar(unittest.TestCase):

    def test_if_car_can_move_forward(self):
        my_car = Car()
        self.assertEqual(my_car.move('forward'), 'car is moving forward')

    def test_if_car_can_move_backward(self):
        my_car = Car()
        self.assertEqual(my_car.move('backward'), 'car is moving backward')
    ...

توجه کنید که ما داریم شیء ای از کلاس Car را مدام میسازیم. از متود setUp() استفاده کنید تا تعریف کنید چه چیزی برای چند تست نیاز دارید. این متود قبل از هر تستی که دارید اجرا میشود

class TestCar(unittest.TestCase):

    def setUp(self):
      self.my_car= Car()

    def test_if_car_can_move_forward(self):
        self.assertEqual(self.my_car.move('forward'), 'car is moving forward')

    def test_if_car_can_move_backward(self):
        self.assertEqual(self.my_car.move('backward'), 'car is moving backward')
    ...

این شکلی است که میتوانید اصل DRY را در کار با متود setUp() برای تست هایتان ببینید. برای اطلاعات بیشتر درمورد اینها، این لینک را ببینید.

۶. تقلید

تقلید یک راه حل تست نیست. بلکه بخشی از سیستم های تست کردن است. برخی پروژه ها ممکن است مقلد داشته باشند، بعضی هم نه. با تقلید، میتوانید بعضی از نیازمندی هایی که تست هایتان نیاز دارند را تقلید کنید تا بتوانید یک قابلیت پروژه را تست کنید.

برای مثال، تصور کنید تابعی در پروژه خود دارید که درخواست HTTP به یک API میزند و نتیجه را سریالایز میکند. حالا، میخواهید مرحله ی سریالایزیشن را با نوشتن تست، تست کنید. چه میشود اگر سرور در زمان تست کرش کند ؟ تست هایتان حتما دچار مشکل خواهند شد ولی ربطی به قابلیت های پروژتان ندارد، دارد ؟ تقصیر پروژه شما نبوده، تقصیر سرور بوده که باعث رد شدن تست ها شده.

image1

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

در مثال بعدی، ما عملکرد requests.get() را تقلید میکنیم. ما ماژولی به نام discovery.py داریم که تابع رو به رو را داراست.

from requests import get

def get_data(link, index):
    response = get(f'{link}/{index}')
    return response

در فایل tests.py، نیاز داریم تا get_data() را تست کنیم. معمولاً تست من هنگامی که link در دسترس نباشد و سرور باعث این اشکال باشد، رد میشود. اینگونه ما آن عملکرد را تقلید میکنیم.

from unittest import TestCase, main, mock

from discovery import get_data
from requests import Response

succeed_response = Response()
succeed_response.status_code = 200

failed_response = Response()
failed_response.status_code = 404

class DiscoveryTest(TestCase):

    @mock.patch('discovery.get', return_value=succeed_response)
    def test_with_valid_index(self, mock_obj):
        response = get_data('https://google.com', 'search')
        self.assertEqual(response.status_code, 200)

    @mock.patch('discovery.get', return_value=failed_response)
    def test_with_invalid_index(self, moch_obj):
        response = get_data('https://google.com', 'somewhere')
        self.assertEqual(response.status_code, 404)

if __name__ == '__main__': main()

در ابتدا، این پیاده سازی ممکن است کمی گیج کننده به نظر برسد. ما به سادگی داریم get() را از ماژول discovery تقلید میکنیم همانطور که در دکوریتور های پچ میبینید. سپس، مقداری که انتظارش را داریم را تعیین میکنیم.

در پکیج unittest، شما میتوانید از مقلد ها به روش های مختلفی استفاده کنید. میتوانید آن هارا به عنوان دکوریتور ها، Context Manager ها و یا حتی خالصانه در متود های setUp و tearDown از آن ها استفاده کنید. من ترجیح میدهم از دکوریتور ها استفاده کنم چون خوانا تر هستند.

۷. بست پرکتیس ها

ما تا الان درمورد مباحث اصلی صحبت کردیم. در این بخش میخواهیم نگاهی به بست پرکتیس هایی بیندازیم که میتوانند تست هایتان را بهتر و خوانا تر کنند.

  • تست هایتان را سریع کنید.

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

  • تست هایتان را خوانا و ساده طراحی کنید.

همیشه به کسی که میخواهد تست های شما و بهینه سازی هایی که او میخواهد بر اساس تست های شما انجام دهد فکر کنید. تست هایتان ممکن است بسیار ارزشمند تر از داکیومنت های شما برای توسعه دهندگانتان باشد، چون آنها آموزش دیده اند که فنی بیاموزند.

  • از اصول DRY و DAMP در تست هایتان استفاده کنید.

داشتن این متود ها به شما کمک میکند تست های بهتری طراحی کنید. یک تست خوب اسامی خوبی دارد. ساده، ایزوله شده و به خوبی نگه داری شده است.

  • قواعدی برای ذخیره تست های پروژه خود داشته باشید.

در پایتون بست پرکتیس نگه داشتن فایل های تستتان داشتن پکیجی به اسم tests و نگه داشتن تست ها در آنجاست. مطمئن شوید که اسامی فایل های تست هایتان با test_ شروع بشود تا اجازه دهد اجرا کننده های تست سریعتر تست های شما را پیدا کنند. پکیج unittest قابلیت تست پیدا کردنی دارد که همیشه به دنبال کلمه ی کلیدی test میگردد. ساختمان فایل بسیار خوبی، بدین شکل خواهد بود:

project
├── accounting
├── transaction
├── management
├── tests
│   ├── __init__.py
│   ├── test_accounting.py
│   ├── test_management.py
│   └── test_transaction.py
├── README.md
└── app.py
  • تست های خود را کراس پلتفرم (Cross-Platform) کنید.

تست های کراس پلتفرم زمانی به کمک شما میایند که توسعه دادن CI/CD به میان میاید که سیستم های اتوماتیک با تست های شما کار میکنند. داشتن ساختمان ثابتی برای تست هایتان کلید اصلی است. به زبانی واضح تر، شما ساختمان زیبایی از تست ها دارید که میتوانید اجرای برخی تست ها را با چند گزینه یا دستور کنترل و مدیریت کنید.

نتیجه‌ گیری

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