Go Programming

تسلط بر تست‌نویسی در Go: قدرت تست‌های جدول‌محور

تست‌نویسی تنها یک تیک در چک‌لیست چرخه توسعه نیست؛ بلکه ستون فقرات نرم‌افزار قابل اعتماد است. در اکوسیستم Go، تست‌نویسی جایگاه اول را دارد. پکیج testing در کتابخانه استاندارد تعبیه شده و توسعه‌دهندگان را به نوشتن تست در کنار کد تشویق می‌کند. با این حال، وقتی کد شما بزرگ می‌شود، نگهداری این تست‌ها اگر به درستی ساختاردهی نشده باشند، می‌تواند به یک کار خسته‌کننده تبدیل شود. اینجاست که تست‌های جدول‌محور درخشش خود را نشان می‌دهند و الگویی را ارائه می‌دهند که خوانایی، قابلیت نگهداری و جامعیت را متعادل می‌کند.

فلسفه پشت تست‌نویسی در Go

رویکرد Go به تست‌نویسی عمدتاً ساده است. برخلاف سایر زبان‌هایی که به فریم‌ورک‌های پیچیده یا دکوراتورها متکی هستند، Go از یک قرارداد نام‌گذاری ساده استفاده می‌کند: فایل‌هایی که با _test.go ختم می‌شوند حاوی تست‌ها هستند و توابعی که با Test شروع می‌شوند توسط دستور go test اجرا می‌شوند. این سادگی کدهای تکراری (boilerplate) را کاهش می‌دهد اما انضباط در نحوه سازماندهی موارد تست را طلب می‌کند.

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

تست‌های جدول‌محور چیستند؟

تست‌های جدول‌محور شامل تعریف یک اسلایس از ساختارها (structs) است، که در آن هر ساختار نماینده یک مورد تست خاص است. این «جدول» تمام داده‌های لازم برای اجرای یک تست را شامل می‌شود: پارامترهای ورودی، نتیجه مورد انتظار و یک انتظار خطا اختیاری. سپس تابع تست از طریق این اسلایس حلقه می‌زند و منطق را برای هر مورد اجرا می‌کند. این الگو اصول DRY (خودت را تکرار نکن) را ترویج می‌دهد و افزودن موارد تست جدید را بدون بازنویسی کدهای تکراری بسیار آسان می‌سازد.

پیاده‌سازی تست‌های جدول‌محور: یک مثال عملی

بیایید یک تابع کمکی ساده را در نظر بگیریم که قیمت تخفیف‌خورده یک کالا را بر اساس درصد محاسبه می‌کند. بدون تست‌های جدول‌محور، ممکن است توابع جداگانه‌ای مانند TestDiscountTenPercent، TestDiscountZeroPercent و غیره بنویسید. در اینجا نحوه بازنویسی این کد با رویکرد جدول‌محور آمده است.

اول، ساختار موردی تست خود را تعریف کنید:

package main

import (
    "testing"
)

// DiscountCase یک مورد تست واحد را تعریف می‌کند
type DiscountCase struct {
    name       string
    price      float64
    discount   float64
    expected   float64
    expectError bool
}

// TestCalculateDiscount تابع تست اصلی است
func TestCalculateDiscount(t *testing.T) {
    // جدول موارد تست را تعریف کنید
    cases := []DiscountCase{
        {
            name:      "تخفیف استاندارد ۱۰٪",
            price:     100.0,
            discount:  0.10,
            expected:  90.0,
        },
        {
            name:      "تخفیف صفر",
            price:     50.0,
            discount:  0.0,
            expected:  50.0,
        },
        {
            name:      "تخفیف کامل",
            price:     200.0,
            discount:  1.0,
            expected:  0.0,
        },
        {
            name:        "تخفیف منفی نامعتبر",
            price:       100.0,
            discount:    -0.1,
            expectError: true,
        },
    }

    // روی جدول حلقه بزنید
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            // منطق مورد آزمایش را اجرا کنید
            result, err := CalculateDiscount(tc.price, tc.discount)

            // مدیریت خطاها
            if tc.expectError {
                if err == nil {
                    t.Errorf("خطا مورد انتظار بود اما هیچ خطایی دریافت نشد")
                }
                return
            }
            if err != nil {
                t.Fatalf("خطای غیرمنتظره: %v", err)
            }

            // بررسی نتیجه
            if result != tc.expected {
                t.Errorf("دریافت شد %f، خواسته شد %f", result, tc.expected)
            }
        })
    }
}

با استفاده از t.Run، Go برای هر مورد زیرتست‌هایی (subtests) ایجاد می‌کند. این موضوع حیاتی است زیرا به تست‌ران اجازه می‌دهد دقیقاً مشخص کند کدام مورد شکست خورده است و فیلد name را از ساختار شما نمایش دهد. این امر هنگام اشکال‌زدایی، زمینه‌ای فوری فراهم می‌کند که بسیار برتر از پیام‌های شکست عمومی است.

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

  • نام‌های توصیفی: همیشه یک فیلد name منحصر‌به‌فرد و توصیفی در ساختار مورد تست خود ارائه دهید. این کار گزارش‌های CI/CD را قابل خواندن می‌کند.
  • مورد‌های مرزی: از رویکرد جدول‌محور برای افزودن آسان موارد مرزی مانند مقادیر صفر، اعداد منفی یا حداکثر محدودیت‌های float استفاده کنید.
  • اجرای موازی: اگر تست‌های شما مستقل هستند، می‌توانید داخل زیرتست t.Parallel() را فراخوانی کنید تا سرعت اجرا را افزایش دهید، به‌ویژه در مجموعه‌های تست بزرگ.
  • سادگی را حفظ کنید: از قرار دادن منطق پیچیده در داخل تابع تست خودداری کنید. تابع تست باید فقط فراخوانی را هماهنگ کند و نتیجه را بررسی نماید. منطق راه‌اندازی یا کمکی را به توابع جداگانه منتقل کنید.

نتیجه‌گیری

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

Share: