بررسی Unit Testing در پایتون

ترجمه و تالیف : ارسطو عباسی
تاریخ انتشار : 26 بهمن 98
خواندن در 4 دقیقه
دسته بندی ها : پایتون

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

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

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

شروع به کار

بهترین راهکار برای درک کردن تست واحد این است که آن را با یک مثال ساده یاد بگیریم. برای انجام چنین کاری ما یک فایل جدید پایتونی با نام name_function.py خواهیم ساخت و در آن یک تابع ساده برای دریافت نام و نام خانوادگی و در نهایت برگشت دادن آن‌ها پیاده‌سازی خواهیم کرد:

#Generate a formatted full name

def formatted_name(first_name, last_name):
   full_name = first_name + ' ' + last_name
   return full_name.title()

تابع formatted_name دو ورودی نام و نام‌خانوادگی را دریافت کرده و با استفاده از یک متغیر آن‌ها را در کنار همدیگر قرار می‌دهد. همچنین از آنجایی که متد title را برای خروجی آورده‌ایم تمام حروف اول این کلمات بزرگ خواهند بود. 

برای آنکه مطمئن شوید این تابع به درستی کار می‌کند کافی‌ست یک فایل جدید را با نام names.py ایجاد کرده و به صورت ساده نام‌های متفاوتی را از کاربران دریافت کنیم:

from name_function import formatted_name

print("Please enter the first and last names or enter x to E[x]it.")

while True:
   first_name = input("Please enter the first name: ")

   if first_name == "x":
       print("Good bye.")
       break

   last_name = input("Please enter the last name: ")
   if last_name == "x":
       print("Good bye.")
       break

   result = formatted_name(first_name, last_name)
   print("Formatted name is: " + result + ".")

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

تست واحد و Test Case

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

Test Case مجموعه‌ای از تست‌های واحد است که در کنار هم این موضوع که یک تابع دقیقاً همانگونه که انتظار می‌رود کار می‌کند را به اثبات می‌رسانند. این کار با دادن ورودی‌های مختلف به تابع و بررسی آن از طُرُق مختلف صورت می‌گیرد.

عبور موفقیت آمیز از یک تست

در اینجا می‌توانید یک سناریو معمولی برای نوشتن تست‌ها را مشاهده کنید.

ابتدا یک فایل برای تست را ایجاد کنید. بعد از آن ماژول unittest را به پروژه اضافه نمایید. یک کلاس را برای Test ایجاد کرده و از ماژول unittest.TestCase ارث بری کنید. در نهایت یک مجموعه از متدها را بنویسید که در آن تمام حالت‌های رفتاری تابع را بررسی کند.

خروجی تمام این کارها به صورت زیر خواهد بود:

import unittest

from name_function import formatted_name

class NamesTestCase(unittest.TestCase):
   def test_first_last_name(self):
       result = formatted_name("pete", "seeger")
       self.assertEqual(result, "Pete Seeger")

کلاس NamesTestCase که در کدهای بالا ایجاد شده است شامل یک متد به نام test_first_last_name می‌شود. این متد قرار است بخشی از تابع formatted_name() را تست کند. حال به عنوان یک نکته مهم باید بگویم که اگر کلاس اصلی یک متد از TestCase ارث‌بری کرده باشد در صورتی که ابتدای نام متد test_ باشد آن متد به صورت خودکار اجرا خواهد شد.

حال در داخل متد تست test_first_last_name ما تابعی که قصد تست کردن آن را داریم فراخوانی خواهیم کرد و آن را در متغیر result قرار می‌دهیم. در مثال بالا ما ورودی‌های pete و seeger را به تابع داده و آن‌ها را در متغیر result ذخیره می‌کنیم. 

در خط پایانی ما از متد assert استفاده خواهیم کرد. با استفاده از این متد می‌توانیم مطمئن شویم که نتیجه بدست آمده با مقداری که در نظر داشته‌ایم آیا همخوانی دارد یا خیر. ورودی‌های این متد ابتدا خروجی خواهد بود که تابع ایجاد می‌کند و ورودی دوم مقداری است که ما انتظار آن را داریم.

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

خروجی:

Ran 1 test in 0.001s

OK

شکست در تست

برای آنکه به شما نشان دهیم که در صورت شکست تست چه اتفاقی خواهد افتاد من قصد دارم که یک پارامتر دیگر را به تابع formatted_name() اضافه کنم. بنابراین ساختار کدهای من به صورت زیر خواهد بود:

#Generate a formatted full name including a middle name

def formatted_name(first_name, last_name, middle_name):
   full_name = first_name + ' ' + middle_name + ' ' + last_name
   return full_name.title()

همانطور که مشاهده می‌کنید ما یک پارامتر جدید با نام middle_name را به تابع اضافه کرده‌ایم، اما توجه داشته باشید که این تغییر در قسمت تست اعمال نشده بنابراین اگر حال تست را اجرا کنیم از آنجایی که ورودی middle_name اضافه نشده خروجی زیر در اجرای test_name_function به شما نشان داده خواهد شد:

Error
Traceback (most recent call last):

File “test_name_function.py”, line 7, in test_first_last_name
    result = formatted_name(“pete”, “seeger”)

TypeError: formatted_name() missing 1 required positional argument: ‘middle_name’

Ran 1 test in 0.002s
FAILED (errors=1)

در این خروجی می‌توانید به صورت کامل جزئیات مربوط به خطا و دلیل شکست تست را مشاهده بکنید.

زمانی که تست به شکست انجامید چه کاری انجام دهیم؟

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

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

برای انجام چنین کاری تنها به یکسری شرط ساده نیاز داریم. برای اینکار کدهای زیر را به ساختار اصلی تابع formatted_name اضافه کنید:

def formatted_name(first_name, last_name, middle_name=''):
   if len(middle_name) > 0:
       full_name = first_name + ' ' + middle_name + ' ' + last_name
   else:
       full_name = first_name + ' ' + last_name
   return full_name.title()

حال برای آنکه مطمئن شوید همه چیز به درستی کار می‌کند یک بار دیگر تست را اجرا کنید. نتیجه حال حاضر باید به صورت زیر باشد:

Ran 1 test in 0.001s
OK

افزودن تست جدید

برای نوشتن تست‌های اضافه می‌توانید یک متد دیگر را به کلاس NamesTestCase اضافه کنید. برای انجام چنین کاری می‌توانید به صورت زیر عمل کنید:

import unittest
from name_function import formatted_name

class NamesTestCase(unittest.TestCase):

    def test_first_last_name(self):
        result = formatted_name("pete", "seeger")
        self.assertEqual(result, "Pete Seeger")

    def test_first_last_middle_name(self):
        result = formatted_name("raymond", "reddington", "red")
        self.assertEqual(result, "Raymond Red Reddington")

در کدهای بالا ما middle_name را در معرض تست قرار داده‌ایم.

بعد از آنکه این کدها را اجرا کردید انتظار می‌رود که خروجی زیر به شما نشان داده شود:

Ran 2 tests in 0.001s
OK

همانطور که مشاهده می‌کنید ۲ تست اجرا شده‌اند و در نهایت تست‌ها نتیجه موفقیت آمیزی داشته‌اند.

در پایان

تست واحد یا Unit Testing یکی از فرایندهای بسیار مهم در روند برنامه‌نویسی و توسعه نرم‌افزار است. البته باید خوشحال بود از این بابت که ابزارهای بسیار زیادی برای زبان‌های مختلف در جهت توسعه چنین‌ تست‌هایی بوجود آمده‌‌اند. اگر قصد یادگیری موضوعات بیشتری در این ارتباط را دارید پیشنهاد می‌کنم دوره آموزشی «Unit Test در جاوااسکریپت» را مشاهده نمایید.

منبع

گردآوری و تالیف ارسطو عباسی
آفلاین
user-avatar

من ارسطو‌ام :) کافی نیست؟! :)

دیدگاه‌ها و پرسش‌ها

برای ارسال نظر لازم است ابتدا وارد سایت شوید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید