TDD چیست؟

TDD چیست؟

tdd چیست
برنامه نویسی

TDD چیست؟

دربرنامه‌نویسی، کیفیت کد یکی از مهم‌ترین عوامل موفقیت پروژه‌های نرم‌افزاری است. هرچه پروژه بزرگ‌تر می‌شود، حفظ کیفیت و اطمینان از صحت عملکرد کد سخت‌تر می‌گردد. در اینجا است که توسعه مبتنی بر تست یا TDD (Test-Driven Development) به کمک ما می‌آید. باید بدانیم که TDD واقعا پیچیده و پیاده سازی آن در تیم های توسعه دشوار است و در ابتدای پیاده سازی سرعت توسعه را کند میکند. پس اگر قصد دارید با TDD پیش بروید باید آمادگی کافی داشته باشید. فلسک دولوپر توصیه میکند در پروژه های بزرگ از TDD بهره بگیرید. پروژه ای بزرگ است که: 1. وابستگی های زیاد داشته باشد. 2. پشتیبانی طولانی مدت نیاز دارد. 3. صاحب نظران و ذینفعان زیادی در آن دخیل هستند.

پس  در صورتی که روی پروژه های متوسط یا کوچک کار میکنید نیازی ندارید به سمت TDD حرکت کنید. 

اگر شما یک برنامه‌نویس پایتون هستید و می‌خواهید کدهای بهتری بنویسید، کدهایی که:

  • کمتر باگ داشته باشند
  • به راحتی قابل نگهداری و توسعه باشند
  • تست‌های خودکار داشته باشند
  • با اطمینان بیشتری refactor شوند

پس TDD راه‌حلی است که باید یاد بگیرید و به کار ببرید.

نکته مهم: TDD یک متدولوژی توسعه نرم‌افزار است، نه یک ابزار یا فریم‌ورک. این یک شیوه تفکر و نوشتن کد است که کیفیت کار شما را متحول می‌کند.

2. TDD چیست؟

توسعه مبتنی بر تست (Test-Driven Development) یک متدولوژی توسعه نرم‌افزار است که در آن ابتدا تست‌ها نوشته می‌شوند و سپس کد پیاده‌سازی می‌شود تا آن تست‌ها پاس شوند. این رویکرد برعکس روش سنتی است که در آن ابتدا کد نوشته و سپس تست می‌شود.

2.1 تاریخچه مختصر

TDD اولین بار توسط کنت بک (Kent Beck) در دهه 1990 معرفی شد. او این روش را به عنوان بخشی از متدولوژی Extreme Programming (XP) توسعه داد. کنت بک در کتاب معروف خود “Test-Driven Development: By Example” این روش را به صورت جامع توضیح داده است.

2.2 فلسفه اصلی TDD

فلسفه اصلی TDD بر این اصل استوار است که:

“کدی که تست نشده است، کدی است که خراب است – حتی اگر در ظاهر کار کند.”

در TDD، ما با نوشتن تست شروع می‌کنیم. این تست در ابتدا fail می‌شود (چون کدی برای پاس کردن آن وجود ندارد). سپس کد را طوری می‌نویسیم که تست پاس شود. در نهایت کد را بهبود می‌دهیم (refactor) بدون اینکه رفتار آن تغییر کند.

2.3 تفاوت TDD با تست سنتی

جنبه تست سنتی TDD
زمان نوشتن تست بعد از نوشتن کد قبل از نوشتن کد
هدف اثبات درستی کد طراحی و مستندسازی کد
پوشش تست معمولاً کمتر معمولاً بیشتر
طراحی کد بعد از کدنویسی همزمان با تست‌نویسی
بازخورد دیرهنگام سریع و مداوم

3. چرخه Red-Green-Refactor

قلب TDD چرخه سه مرحله‌ای Red-Green-Refactor است. این چرخه بارها و بارها تکرار می‌شود تا کد بهبود یابد.

چرخه TDD

تست نرم افزار

3.1 مرحله Red (قرمز)

1
نوشتن تست که fail می‌شوددر این مرحله، شما یک تست برای قابلیتی که می‌خواهید اضافه کنید می‌نویسید. این تست باید fail شود چون هنوز کدی برای پاس کردن آن وجود ندارد.این مرحله به شما کمک می‌کند تا:

  • قبل از نوشتن کد، درباره رفتار مورد نظر فکر کنید
  • رابطه (API) مناسبی برای کد خود طراحی کنید
  • از نوشتن کد اضافی جلوگیری کنید

3.2 مرحله Green (سبز)

2
نوشتن کد برای پاس کردن تستحالا کد را طوری می‌نویسید که تست پاس شود. در این مرحله:

  • فقط کدی بنویسید که برای پاس شدن تست لازم است
  • نگران تمیزی کد نباشید – بعداً refactor می‌کنید
  • از راه‌حل‌های ساده و سریع استفاده کنید
هشدار: در مرحله Green فقط کدی بنویسید که تست را پاس کند. وسوسه نشوید که قابلیت‌های اضافی اضافه کنید.

3.3 مرحله Refactor (بازسازی)

3
بهبود کد بدون تغییر رفتارحالا که تست پاس می‌شود، وقت آن است که کد را بهبود دهید:

  • کد تکراری را حذف کنید
  • نام‌ها را واضح‌تر کنید
  • کد را سازماندهی کنید
  • از الگوهای طراحی مناسب استفاده کنید

مهم‌ترین نکته این است که در این مرحله نباید رفتار کد تغییر کند. تست‌ها باید همچنان پاس باشند.

3.4 مثال ساده از چرخه TDD

فرض کنید می‌خواهیم تابعی بنویسیم که دو عدد را جمع کند:

# مرحله 1: Red - نوشتن تست که fail می‌شود
def test_add_two_numbers():
    result = add(2, 3)
    assert result == 5  # این تست fail می‌شود چون تابع add وجود ندارد

# مرحله 2: Green - نوشتن کد
def add(a, b):
    return a + b

# مرحله 3: Refactor - بهبود کد (در این مثال ساده، نیازی نیست)

4. چرا باید از TDD استفاده کنیم؟

4.1 مزایای اصلی TDD

4.1.1 کد با کیفیت‌تر

وقتی با تست شروع می‌کنید، مجبورید قبل از نوشتن کد درباره آن فکر کنید. این باعث می‌شود:

  • طراحی بهتری داشته باشید
  • کد تمیزتر و خواناتر بنویسید
  • وابستگی‌ها را کمتر کنید

4.1.2 کاهش باگ

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

  • مشکلات را زودتر شناسایی می‌کنند
  • از ورود باگ‌های جدید جلوگیری می‌کنند
  • رگرسیون را شناسایی می‌کنند

4.1.3 مستندسازی زنده

تست‌ها به عنوان مستندات زنده عمل می‌کنند:

  • نشان می‌دهند که کد چه کاری انجام می‌دهد
  • همیشه به‌روز هستند (چون با کد اجرا می‌شوند)
  • مثال‌های عملی از نحوه استفاده ارائه می‌دهند

4.1.4 اعتماد به نفس بیشتر

با تست‌های خودکار:

  • با اطمینان بیشتری refactor می‌کنید
  • می‌توانید تغییرات بزرگ انجام دهید
  • از شکستن کد نترسید

4.1.5 توسعه سریع‌تر

شاید به نظر برسد که نوشتن تست اول، زمان می‌برد. اما در بلندمدت:

  • زمان کمتری برای دیباگ صرف می‌کنید
  • تست‌های دستی کمتری نیاز دارید
  • باگ‌های کمتری در production دارید

4.2 آمار و ارقام

معیار بدون TDD با TDD
درصد باگ‌های production بالا (15-25%) پایین (5-10%)
زمان دیباگ زیاد کم
هزینه رفع باگ بالا (در production) پایین (در توسعه)
پوشش تست معمولاً کمتر از 50% معمولاً بیش از 80%
تحقیقات نشان می‌دهد: تیم‌هایی که از TDD استفاده می‌کنند، در درازمدت 20-30% زمان کمتری برای نگهداری کد صرف می‌کنند.

5. ابزارهای TDD در پایتون

پایتون یکی از بهترین زبان‌ها برای TDD است. چندین فریم‌ورک قدرتمند برای تست در پایتون وجود دارد.

5.1 pytest

pytest محبوب‌ترین فریم‌ورک تست در پایتون است. ویژگی‌های اصلی:

  • نوشتن تست بسیار ساده و خوانا
  • پشتیبانی از fixtures
  • گزارش‌های واضح و زیبا
  • تست‌های پارامتری
  • جامعه بزرگ و مستندات عالی

نصب pytest:

pip install pytest

یک تست ساده با pytest:

# test_calculator.py
import pytest
from calculator import Calculator

def test_add():
    calc = Calculator()
    assert calc.add(2, 3) == 5

def test_multiply():
    calc = Calculator()
    assert calc.multiply(4, 5) == 20

5.2 unittest

unittest فریم‌ورک تست داخلی پایتون است که از JUnit (جاوا) الهام گرفته شده:

  • نیازی به نصب ندارد (در کتابخانه استاندارد است)
  • ساختار رسمی‌تری دارد
  • برای پروژه‌های بزرگ مناسب است

مثال با unittest:

import unittest

class TestCalculator(unittest.TestCase):
    def setUp(self):
        self.calc = Calculator()
    
    def test_add(self):
        self.assertEqual(self.calc.add(2, 3), 5)
    
    def test_multiply(self):
        self.assertEqual(self.calc.multiply(4, 5), 20)

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

5.3 nose2

یک فریم‌ورک تست که می‌تواند تست‌های pytest و unittest را اجرا کند.

5.4 مقایسه فریم‌ورک‌ها

ویژگی pytest unittest nose2
نصب نیاز دارد نیاز ندارد نیاز دارد
یادگیری آسان متوسط متوسط
خوانایی عالی خوب خوب
محبوبیت بسیار بالا بالا متوسط
پیشنهادی
پیشنهاد: برای شروع، pytest را یاد بگیرید. ساده‌تر است و جامعه بزرگ‌تری دارد.

6. نوشتن اولین تست

بیایید قدم به قدم اولین تست خود را با TDD بنویسیم.

6.1 ساختار پروژه

یک پروژه پایتون با TDD معمولاً این ساختار را دارد:

my_project/
├── src/
│   └── calculator.py
├── tests/
│   ├── __init__.py
│   └── test_calculator.py
└── requirements.txt

6.2 ایجاد فایل‌ها

فایل calculator.py (در ابتدا خالی):

# src/calculator.py

# این فایل را خالی می‌گذاریم چون با TDD شروع می‌کنیم

فایل test_calculator.py:

# tests/test_calculator.py
import sys
import os

# اضافه کردن مسیر src به path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

from calculator import Calculator

def test_calculator_can_be_created():
    """تست می‌کند که Calculator می‌تواند ایجاد شود"""
    calc = Calculator()
    assert calc is not None

اجرای تست (که fail می‌شود):

pytest tests/test_calculator.py -v

خروجی:

========================= test session starts =========================
collected 1 items

tests/test_calculator.py::test_calculator_can_be_created FAILED
========================= FAILURES =========================
________________________ test_calculator_can_be_created ________________________

    from calculator import Calculator
E   ImportError: cannot import name 'Calculator' from 'calculator'

========================= 1 failed in 0.1s =========================</>

6.3 پیاده‌سازی کد حداقلی

 

حالا کد را می‌نویسیم تا تست پاس شود:

 

# src/calculator.py

class Calculator:
    pass

اجرای تست (که پاس می‌شود):

pytest tests/test_calculator.py -v

خروجی:

========================= test session starts =========================
collected 1 items

tests/test_calculator.py::test_calculator_can_be_created PASSED
========================= 1 passed in 0.1s =========================

6.4 refactor

حالا می‌توانیم کد را بهبود دهیم. در این مورد ساده، نیاز به refactor نیست.


7. مثال عملی: ماشین‌حساب

بیایید یک ماشین‌حساب کامل با TDD بسازیم. این مثال نشان می‌دهد که چطور می‌توانید یک پروژه واقعی را با TDD توسعه دهید.

7.1 تعریف نیازمندی‌ها

ماشین‌حساب ما باید این قابلیت‌ها را داشته باشد:

  • جمع (+)
  • تفریق (-)
  • ضرب (*)
  • تقسیم (/)
  • مدیریت تقسیم بر صفر

7.2 شروع با تست‌ها

تست اول: جمع

# tests/test_calculator.py
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

import pytest
from calculator import Calculator

def test_add_two_positive_numbers():
    """جمع دو عدد مثبت"""
    calc = Calculator()
    result = calc.add(5, 3)
    assert result == 8

def test_add_negative_numbers():
    """جمع اعداد منفی"""
    calc = Calculator()
    result = calc.add(-5, -3)
    assert result == -8

def test_add_positive_and_negative():
    """جمع عدد مثبت و منفی"""
    calc = Calculator()
    result = calc.add(5, -3)
    assert result == 2

اجرای تست (fail):

pytest tests/test_calculator.py -v

خروجی:

========================= test session starts =========================
tests/test_calculator.py::test_add_two_positive_numbers FAILED
...
========================= FAILURES =========================
________________________ test_add_two_positive_numbers ________________________

    result = calc.add(5, 3)
E   AttributeError: 'Calculator' object has no attribute 'add'
========================= 3 failed in 0.1s =========================

پیاده‌سازی تابع add:

# src/calculator.py

class Calculator:
    def add(self, a, b):
        return a + b

اجرای تست (pass):

pytest tests/test_calculator.py -v

خروجی:

========================= test session starts =========================
tests/test_calculator.py::test_add_two_positive_numbers PASSED
tests/test_calculator.py::test_add_negative_numbers PASSED
tests/test_calculator.py::test_add_positive_and_negative PASSED
========================= 3 passed in 0.1s =========================

تست دوم: تفریق

# tests/test_calculator.py (ادامه)

def test_subtract_two_numbers():
    """تفریق دو عدد"""
    calc = Calculator()
    assert calc.subtract(10, 4) == 6
    assert calc.subtract(-5, -3) == -2
    assert calc.subtract(5, -3) == 8

اجرای تست (fail):

pytest tests/test_calculator.py::test_subtract_two_numbers -v

خروجی:

FAILED - AttributeError: 'Calculator' object has no attribute 'subtract'

پیاده‌سازی تابع subtract:

# src/calculator.py (کامل)

class Calculator:
    def add(self, a, b):
        return a + b
    
    def subtract(self, a, b):
        return a - b

اجرای تست (pass):

pytest tests/test_calculator.py -v

تست سوم: ضرب

# tests/test_calculator.py (ادامه)

def test_multiply_two_numbers():
    """ضرب دو عدد"""
    calc = Calculator()
    assert calc.multiply(4, 5) == 20
    assert calc.multiply(-3, 4) == -12
    assert calc.multiply(0, 100) == 0

پیاده‌سازی و تست…

تست چهارم: تقسیم

# tests/test_calculator.py (ادامه)

def test_divide_two_numbers():
    """تقسیم دو عدد"""
    calc = Calculator()
    assert calc.divide(10, 2) == 5
    assert calc.divide(15, 3) == 5
    assert calc.divide(-10, 2) == -5

def test_divide_by_zero():
    """تقسیم بر صفر باید خطا بدهد"""
    calc = Calculator()
    with pytest.raises(ZeroDivisionError):
        calc.divide(10, 0)

پیاده‌سازی تابع divide:

# src/calculator.py (کامل)

class Calculator:
    def add(self, a, b):
        return a + b
    
    def subtract(self, a, b):
        return a - b
    
    def multiply(self, a, b):
        return a * b
    
    def divide(self, a, b):
        if b == 0:
            raise ZeroDivisionError("تقسیم بر صفر ممکن نیست")
        return a / b

7.3 کد نهایی

فایل calculator.py:

# src/calculator.py

class Calculator:
    """
    یک ماشین‌حساب ساده با چهار عمل اصلی
    """
    
    def add(self, a, b):
        """جمع دو عدد"""
        return a + b
    
    def subtract(self, a, b):
        """تفریق دو عدد"""
        return a - b
    
    def multiply(self, a, b):
        """ضرب دو عدد"""
        return a * b
    
    def divide(self, a, b):
        """تقسیم دو عدد"""
        if b == 0:
            raise ZeroDivisionError("تقسیم بر صفر ممکن نیست")
        return a / b

فایل test_calculator.py:

# tests/test_calculator.py
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

import pytest
from calculator import Calculator

class TestCalculator:
    """تست‌های ماشین‌حساب"""
    
    def setup_method(self):
        """اجرا قبل از هر تست"""
        self.calc = Calculator()
    
    # ===== تست‌های جمع =====
    
    def test_add_two_positive_numbers(self):
        """جمع دو عدد مثبت"""
        assert self.calc.add(5, 3) == 8
    
    def test_add_negative_numbers(self):
        """جمع اعداد منفی"""
        assert self.calc.add(-5, -3) == -8
    
    def test_add_positive_and_negative(self):
        """جمع عدد مثبت و منفی"""
        assert self.calc.add(5, -3) == 2
    
    def test_add_zero(self):
        """جمع با صفر"""
        assert self.calc.add(5, 0) == 5
        assert self.calc.add(0, 0) == 0
    
    # ===== تست‌های تفریق =====
    
    def test_subtract_two_numbers(self):
        """تفریق دو عدد"""
        assert self.calc.subtract(10, 4) == 6
    
    def test_subtract_negative_result(self):
        """تفریق با نتیجه منفی"""
        assert self.calc.subtract(3, 8) == -5
    
    # ===== تست‌های ضرب =====
    
    def test_multiply_two_numbers(self):
        """ضرب دو عدد"""
        assert self.calc.multiply(4, 5) == 20
    
    def test_multiply_by_zero(self):
        """ضرب در صفر"""
        assert self.calc.multiply(100, 0) == 0
    
    def test_multiply_negative_numbers(self):
        """ضرب اعداد منفی"""
        assert self.calc.multiply(-3, -4) == 12
    
    # ===== تست‌های تقسیم =====
    
    def test_divide_two_numbers(self):
        """تقسیم دو عدد"""
        assert self.calc.divide(10, 2) == 5
    
    def test_divide_by_zero_raises_error(self):
        """تقسیم بر صفر باید خطا بدهد"""
        with pytest.raises(ZeroDivisionError):
            self.calc.divide(10, 0)
    
    def test_divide_negative_numbers(self):
        """تقسیم اعداد منفی"""
        assert self.calc.divide(-10, 2) == -5

7.4 اجرای همه تست‌ها

pytest tests/test_calculator.py -v

خروجی:

========================= test session starts =========================
tests/test_calculator.py::TestCalculator::test_add_two_positive_numbers PASSED
tests/test_calculator.py::TestCalculator::test_add_negative_numbers PASSED
tests/test_calculator.py::TestCalculator::test_add_positive_and_negative PASSED
tests/test_calculator.py::TestCalculator::test_add_zero PASSED
tests/test_calculator.py::TestCalculator::test_subtract_two_numbers PASSED
tests/test_calculator.py::TestCalculator::test_subtract_negative_result PASSED
tests/test_calculator.py::TestCalculator::test_multiply_two_numbers PASSED
tests/test_calculator.py::TestCalculator::test_multiply_by_zero PASSED
tests/test_calculator.py::TestCalculator::test_multiply_negative_numbers PASSED
tests/test_calculator.py::TestCalculator::test_divide_two_numbers PASSED
tests/test_calculator.py::TestCalculator::test_divide_by_zero_raises_error PASSED
tests/test_calculator.py::TestCalculator::test_divide_negative_numbers PASSED
========================= 12 passed in 0.2s =========================
عالی! همه 12 تست پاس شدند. حالا می‌توانیم با اطمینان کد را refactor کنیم یا قابلیت‌های جدید اضافه کنیم.

8. مفاهیم پیشرفته‌تر

8.1 Fixtures

Fixtures به شما اجازه می‌دهند داده‌ها و منابع مشترک را بین تست‌ها به اشتراک بگذارید:

import pytest

@pytest.fixture
def calculator():
    """یک نمونه از ماشین‌حساب برای همه تست‌ها"""
    return Calculator()

def test_add(calculator):
    assert calculator.add(2, 3) == 5

def test_multiply(calculator):
    assert calculator.multiply(4, 5) == 20

8.2 Parametrized Tests

تست‌های پارامتری برای اجرای یک تست با مقادیر مختلف:

import pytest

@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (-1, 1, 0),
    (0, 0, 0),
    (100, 200, 300),
])
def test_add_parametrized(a, b, expected):
    """تست جمع با مقادیر مختلف"""
    calc = Calculator()
    assert calc.add(a, b) == expected

8.3 Mocking

Mocking به شما اجازه می‌دهد وابستگی‌های خارجی را شبیه‌سازی کنید:

from unittest.mock import Mock, patch

def test_send_email():
    """تست ارسال ایمیل با استفاده از mock"""
    # یک mock برای سرویس ایمیل
    email_service = Mock()
    email_service.send.return_value = True
    
    # استفاده از mock در کد
    user = User(email_service=email_service)
    user.send_welcome_email()
    
    # بررسی اینکه متد send فراخوانی شده
    email_service.send.assert_called_once()

8.4 Test Coverage

پوشش تست نشان می‌دهد چند درصد از کد شما توسط تست‌ها پوشش داده شده:

# نصب pytest-cov
pip install pytest-cov

# اجرا با پوشش تست
pytest --cov=src --cov-report=html

# مشاهده گزارش در مرورگر
# htmlcov/index.html

8.5 تست‌های Integration و Unit

نوع تست توضیح مثال
Unit Test تست یک واحد کوچک از کد تست تابع add در ماشین‌حساب
Integration Test تست تعامل بین چند بخش تست ذخیره در دیتابیس
End-to-End Test تست کل سیستم تست فرآیند ثبت‌نام کاربر

8.6 تست‌های Regression

تست‌های رگرسیون از بروز مجدد باگ‌های قبلی جلوگیری می‌کنند:

def test_previous_bug_fixed():
    """
    این تست برای رفع باگ قبلی اضافه شده.
    باگ: در نسخه قبلی، جمع اعداد اعشاری دقت کافی نداشت
    """
    calc = Calculator()
    result = calc.add(0.1, 0.2)
    # باید 0.3 باشد نه 0.30000000000000004
    assert abs(result - 0.3) < 0.0001

9. بهترین شیوه‌ها

9.1 اصول نام‌گذاری تست

نام تست باید به وضوح نشان دهد چه چیزی تست می‌شود:

# ❌ نام‌های بد
def test1():
def test_calc():
def test_function():

# ✅ نام‌های خوب
def test_add_two_positive_numbers_returns_sum():
def test_divide_by_zero_raises_zero_division_error():
def test_user_cannot_register_with_invalid_email():

9.2 ساختار AAA

هر تست باید این ساختار را داشته باشد:

  • Arrange: آماده‌سازی داده‌ها و اشیاء
  • Act: اجرای عملیات مورد نظر
  • Assert: بررسی نتیجه
def test_add_two_numbers(self):
    # Arrange - آماده‌سازی
    calc = Calculator()
    a, b = 5, 3
    
    # Act - اجرا
    result = calc.add(a, b)
    
    # Assert - بررسی
    assert result == 8

9.3 هر تست باید مستقل باشد

# ❌ تست‌های وابسته
def test_create_user():
    user = create_user("ali")
    global user_id = user.id  # وابستگی به متغیر سراسری

def test_update_user():
    update_user(user_id, "ahmad")  # وابسته به تست قبلی

# ✅ تست‌های مستقل
def test_create_user():
    user = create_user("ali")
    assert user.id is not None

def test_update_user():
    user = create_user("ali")
    updated = update_user(user.id, "ahmad")
    assert updated.name == "ahmad"

9.4 تست نباید implementation را تست کند

# ❌ تست implementation
def test_add_uses_plus_operator():
    calc = Calculator()
    # این تست به implementation وابسته است
    assert '+' in inspect.getsource(calc.add)

# ✅ تست رفتار
def test_add_returns_sum():
    calc = Calculator()
    assert calc.add(2, 3) == 5

9.5 از Hardcoded Values پرهیز کنید

# ❌ مقادیر magic
def test_calculate_discount():
    price = calculate_price(100, "SUMMER")
    assert price == 70

# ✅ مقادیر واضح
SUMMER_DISCOUNT = 0.30

def test_calculate_discount():
    price = calculate_price(100, "SUMMER")
    expected_price = 100 * (1 - SUMMER_DISCOUNT)
    assert price == expected_price

9.6 تست‌های سریع

قاعده کلی: هر تست باید در کمتر از یک ثانیه اجرا شود. تست‌های کند باعث می‌شوند توسعه‌دهندگان تست را نادیده بگیرند.

9.7 لیست کامل بهترین شیوه‌ها

  • ✅ هر تست فقط یک چیز را تست کند
  • ✅ نام تست‌ها توصیفی باشد
  • ✅ تست‌ها سریع باشند
  • ✅ تست‌ها مستقل باشند
  • ✅ از fixtures استفاده کنید
  • ✅ تست‌ها را مرتب اجرا کنید
  • ✅ پوشش تست را بالا نگه دارید
  • ✅ تست‌ها را در کنار کد نگه دارید
  • ✅ از mock برای وابستگی‌های خارجی استفاده کنید
  • ✅ تست‌ها را به صورت خودکار اجرا کنید

10. اشتباهات رایج

10.1 نوشتن تست بعد از کد

اشتباه رایج: بسیاری از برنامه‌نویسان اول کد را می‌نویسند و بعد تست را اضافه می‌کنند. این TDD نیست!

در TDD، تست باید قبل از کد نوشته شود. این تفاوت اساسی است.

10.2 تست‌های بزرگ و پیچیده

# ❌ اشتباه: یک تست برای همه چیز
def test_all_calculator_operations():
    calc = Calculator()
    assert calc.add(1, 2) == 3
    assert calc.subtract(5, 3) == 2
    assert calc.multiply(4, 5) == 20
    assert calc.divide(10, 2) == 5
    # این تست خیلی بزرگ است!

# ✅ درست: تست‌های کوچک و متمرکز
def test_add_returns_correct_sum():
    assert Calculator().add(1, 2) == 3

def test_subtract_returns_correct_difference():
    assert Calculator().subtract(5, 3) == 2

10.3 عدم اجرای منظم تست‌ها

تست‌ها باید به صورت مداوم اجرا شوند:

  • قبل از هر commit
  • در CI/CD pipeline
  • قبل از merge کردن

10.4 تست نکردن Edge Cases

# ❌ تست ناقص
def test_divide():
    assert Calculator().divide(10, 2) == 5
    # edge case ها تست نشده!

# ✅ تست کامل
def test_divide():
    assert Calculator().divide(10, 2) == 5
    assert Calculator().divide(0, 5) == 0
    with pytest.raises(ZeroDivisionError):
        Calculator().divide(10, 0)

10.5 تست‌های شکننده (Brittle Tests)

# ❌ تست شکننده - به تغییرات جزئی حساس
def test_user_to_string():
    user = User("Ali", "Rezaei")
    assert str(user) == "User(Ali Rezaei)"  # وابسته به فرمت

# ✅ تست قوی - فقط رفتار مهم را تست می‌کند
def test_user_full_name():
    user = User("Ali", "Rezaei")
    assert user.get_full_name() == "Ali Rezaei"

10.6 جدول اشتباهات رایج

اشتباه راه‌حل
تست بعد از کد اول تست بنویسید
تست‌های بزرگ تست‌ها را کوچک کنید
تست‌های وابسته تست‌ها را مستقل کنید
ندیدن edge cases همه موارد را تست کنید
تست‌های کند تست‌ها را بهینه کنید
تست implementation رفتار را تست کنید

11. نتیجه‌گیری

11.1 خلاصه مطالب

در این مقاله یاد گرفتیم:

  • TDD چیست و چرا مهم است
  • چرخه Red-Green-Refactor چگونه کار می‌کند
  • ابزارهای تست در پایتون (pytest و unittest)
  • نوشتن تست با مثال عملی ماشین‌حساب
  • مفاهیم پیشرفته‌تر مانند fixtures و mocking
  • بهترین شیوه‌ها و اشتباهات رایج

11.2 چرا TDD ارزش یادگیری دارد؟

TDD فقط یک تکنیک تست نیست. این یک شیوه تفکر است که:

  • کد شما را بهتر می‌کند
  • باگ‌ها را کاهش می‌دهد
  • اعتماد به نفس شما را بالا می‌برد
  • مستندات زنده ایجاد می‌کند
  • توسعه تیمی را بهبود می‌دهد

11.3 قدم بعدی چیست؟

برای تسلط بر TDD:

  1. در پروژه بعدی خود از TDD استفاده کنید
  2. pytest را به صورت کامل یاد بگیرید
  3. مفاهیم mocking و fixtures را تمرین کنید
  4. CI/CD را با اجرای خودکار تست‌ها ترکیب کنید
  5. مقاله‌ها و کتاب‌های بیشتری بخوانید

11.4 منابع بیشتر

  • کتاب: “Test-Driven Development: By Example” – Kent Beck
  • مستندات رسمی pytest: docs.pytest.org
  • دوره‌های آنلاین TDD در پایتون
  • مقالات و وبلاگ‌های تخصصی

پیام پایانی:TDD یک مهارت است که با تمرین بهتر می‌شود. در ابتدا ممکن است کند به نظر برسد، اما در بلندمدت زمان و انرژی شما را صرفه‌جویی می‌کند. شروع کنید، اشتباه کنید، یاد بگیرید و بهبود یابید.


پیوست: خلاصه سریع دستورات pytest

دستور توضیح
pytest اجرای همه تست‌ها
pytest -v اجرا با جزئیات بیشتر
pytest -k "test_name" اجرای تست با نام خاص
pytest --collect-only لیست همه تست‌ها
pytest -x توقف در اولین fail
pytest --cov=src پوشش تست
pytest -m "slow" اجرای تست‌های علامت‌گذاری شده

موفق باشید و کدهای بدون باگ بنویسید! 


 

 

دیدگاه خود را اینجا بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

فیلدهای دلخواه برای نمایش را انتخاب کنید. سایر فیلدها مخفی می شود. برای ترتیب دلخواه فیلدها را به محل دلخواه بکشید و رها کنید.
  • عكس
  • شناسه محصول
  • امتیاز
  • قیمت
  • موجودی
  • موجودی
  • افزودن به سبد خرید
  • توضیحات
  • محتوا
  • وزن
  • ابعاد
  • اطلاعات تکمیلی
برای مخفی شدن نوار مقایسه، بیرون از کادر کلیک کنید
مقایسه