TDD چیست؟
TDD چیست؟
دربرنامهنویسی، کیفیت کد یکی از مهمترین عوامل موفقیت پروژههای نرمافزاری است. هرچه پروژه بزرگتر میشود، حفظ کیفیت و اطمینان از صحت عملکرد کد سختتر میگردد. در اینجا است که توسعه مبتنی بر تست یا TDD (Test-Driven Development) به کمک ما میآید. باید بدانیم که TDD واقعا پیچیده و پیاده سازی آن در تیم های توسعه دشوار است و در ابتدای پیاده سازی سرعت توسعه را کند میکند. پس اگر قصد دارید با TDD پیش بروید باید آمادگی کافی داشته باشید. فلسک دولوپر توصیه میکند در پروژه های بزرگ از TDD بهره بگیرید. پروژه ای بزرگ است که: 1. وابستگی های زیاد داشته باشد. 2. پشتیبانی طولانی مدت نیاز دارد. 3. صاحب نظران و ذینفعان زیادی در آن دخیل هستند.
پس در صورتی که روی پروژه های متوسط یا کوچک کار میکنید نیازی ندارید به سمت TDD حرکت کنید.
اگر شما یک برنامهنویس پایتون هستید و میخواهید کدهای بهتری بنویسید، کدهایی که:
- کمتر باگ داشته باشند
- به راحتی قابل نگهداری و توسعه باشند
- تستهای خودکار داشته باشند
- با اطمینان بیشتری refactor شوند
پس 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 (قرمز)
نوشتن تست که fail میشوددر این مرحله، شما یک تست برای قابلیتی که میخواهید اضافه کنید مینویسید. این تست باید fail شود چون هنوز کدی برای پاس کردن آن وجود ندارد.این مرحله به شما کمک میکند تا:
- قبل از نوشتن کد، درباره رفتار مورد نظر فکر کنید
- رابطه (API) مناسبی برای کد خود طراحی کنید
- از نوشتن کد اضافی جلوگیری کنید
3.2 مرحله Green (سبز)
نوشتن کد برای پاس کردن تستحالا کد را طوری مینویسید که تست پاس شود. در این مرحله:
- فقط کدی بنویسید که برای پاس شدن تست لازم است
- نگران تمیزی کد نباشید – بعداً refactor میکنید
- از راهحلهای ساده و سریع استفاده کنید
3.3 مرحله Refactor (بازسازی)
بهبود کد بدون تغییر رفتارحالا که تست پاس میشود، وقت آن است که کد را بهبود دهید:
- کد تکراری را حذف کنید
- نامها را واضحتر کنید
- کد را سازماندهی کنید
- از الگوهای طراحی مناسب استفاده کنید
مهمترین نکته این است که در این مرحله نباید رفتار کد تغییر کند. تستها باید همچنان پاس باشند.
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% |
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 |
|---|---|---|---|
| نصب | نیاز دارد | نیاز ندارد | نیاز دارد |
| یادگیری | آسان | متوسط | متوسط |
| خوانایی | عالی | خوب | خوب |
| محبوبیت | بسیار بالا | بالا | متوسط |
| پیشنهادی | ✅ | ✅ | ➖ |
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 =========================
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، تست باید قبل از کد نوشته شود. این تفاوت اساسی است.
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:
- در پروژه بعدی خود از TDD استفاده کنید
- pytest را به صورت کامل یاد بگیرید
- مفاهیم mocking و fixtures را تمرین کنید
- CI/CD را با اجرای خودکار تستها ترکیب کنید
- مقالهها و کتابهای بیشتری بخوانید
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" |
اجرای تستهای علامتگذاری شده |
موفق باشید و کدهای بدون باگ بنویسید!