عنوان:

‫کشف نیازمندی‌ها و سناریوهای مبهم با اولویت‌دادن به نوشتن تست در GitHub Copilot


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۵/۲۹ ۱۲:۱۱
آدرس: www.dntips.ir
یکی از بزرگ‌ترین دام‌ها در کار با ابزارهای هوش مصنوعی، درخواست مستقیم برای افزودن ویژگی‌ها یا بازنویسی متدها پیش از تثبیت رفتار سیستم است. هنگامی که از Copilot می‌خواهید کدی را بهبود دهد، هوش مصنوعی فرضیات خود را در کد پیاده می‌کند. در مقابل، وادار کردن مدل به نوشتن تست‌های واحد (Unit Tests) پیش از توسعه کد عملیاتی، Copilot را از یک کدنویس ساده به ابزاری برای اعتبارسنجی الزامات و کشف باگ‌های پنهان منطقی تبدیل می‌کند.
تست‌های نرم‌افزار در حقیقت «پرسش‌های اجرایی» هستند؛ درخواست طراحی تست، مدل را وادار می‌کند تمام حالت‌های مرزی (Edge Cases) و ابهامات دامنه کسب‌وکار (Business Domain) را آشکار کند.

بررسی یک سناریوی واقعی: محاسبه تخفیف

متد اولیه زیر را در نظر بگیرید:
public decimal CalculateDiscount(Customer customer, decimal orderTotal)
{
    if (customer.IsPremium && orderTotal >= 1000)
        return orderTotal * 0.20m;

    if (customer.IsPremium)
        return orderTotal * 0.10m;

    return 0;
}
به جای درخواست بهبود فوری این متد، از این پرامپت مهندسی استفاده کنید:
پیش از نوشتن تست‌ها، رفتارهای پیش‌بینی‌شده و حالت‌های مبهم این متد را تحلیل کن. سپس یک مجموعه تست xUnit با کتابخانه FluentAssertions تولید کن.
تمرکز روی مقادیر مرزی و اعتبارسنجی:
  • مقادیر صفر (orderTotal = 0)
  • مقادیر مرزی شرط (orderTotal = 999.99m و orderTotal = 1000m)
  • رفتارهای تعریف‌نشده مانند مقادیر منفی (orderTotal < 0)
  • بررسی حالت ارجاع تهی (customer == null)
  • شفاف‌سازی این نکته که آیا مشتریان عادی (Non-Premium) هیچ‌گاه مشمول تخفیف می‌شوند یا خیر.

خروجی تحلیلی و تست‌های کشف‌کننده باگ

تحلیل Copilot سریعاً ابهامات زیر را آشکار می‌سازد:
  • عدم بررسی customer == null: اگر مشتری پاس داده نشود، متد با خطای NullReferenceException متوقف می‌شود.
  • پذیرش مبالغ منفی: ارسال عدد منفی منجر به بازگرداندن تخفیف منفی و ایجاد باگ مالی می‌شود.
  • نبود تخفیف پلکانی برای مشتریان عادی: رفتار سیستم برای سفارش‌های بسیار بالا توسط مشتریان غیرویژه مشخص نیست.

نمونه پیاده‌سازی تست‌های xUnit برای پوشش کامل رفتارها:
using FluentAssertions;
using Xunit;

public class DiscountCalculatorTests
{
    private readonly DiscountCalculator _sut = new();

    [Theory]
    [InlineData(1000, 200)]    // روی مرز تخفیف ۲۰ درصدی
    [InlineData(1500, 300)]    // بالاتر از مرز
    public void CalculateDiscount_PremiumCustomerWithHighTotal_ReturnsTwentyPercent(
        decimal orderTotal, decimal expectedDiscount)
    {
        var customer = new Customer { IsPremium = true };

        var result = _sut.CalculateDiscount(customer, orderTotal);

        result.Should().Be(expectedDiscount);
    }

    [Theory]
    [InlineData(999.99, 99.999)] // درست زیر مرز تخفیف حداکثری
    [InlineData(100, 10)]
    public void CalculateDiscount_PremiumCustomerWithStandardTotal_ReturnsTenPercent(
        decimal orderTotal, decimal expectedDiscount)
    {
        var customer = new Customer { IsPremium = true };

        var result = _sut.CalculateDiscount(customer, orderTotal);

        result.Should().Be(expectedDiscount);
    }

    [Fact]
    public void CalculateDiscount_NonPremiumCustomer_ReturnsZero()
    {
        var customer = new Customer { IsPremium = false };

        var result = _sut.CalculateDiscount(customer, 1000m);

        result.Should().Be(0);
    }

    [Fact]
    public void CalculateDiscount_NullCustomer_ThrowsArgumentNullException()
    {
        var act = () => _sut.CalculateDiscount(null!, 1000m);

        act.Should().Throw<ArgumentNullException>();
    }

    [Fact]
    public void CalculateDiscount_NegativeOrderTotal_ThrowsArgumentOutOfRangeException()
    {
        var customer = new Customer { IsPremium = true };

        var act = () => _sut.CalculateDiscount(customer, -50m);

        act.Should().Throw<ArgumentOutOfRangeException>();
    }
}

اصلاح کد بر مبنای تست‌های استخراج‌شده

پس از اجرای تست‌ها و شکست مواردی که باگ‌ها را هدف قرار داده بودند (مانند Null و اعداد منفی)، متد اصلی به شکلی امن، تمیز و مبتنی بر امکانات #C مدرن بازنویسی می‌شود:
public decimal CalculateDiscount(Customer customer, decimal orderTotal)
{
    ArgumentNullException.ThrowIfNull(customer);
    ArgumentOutOfRangeException.ThrowIfNegative(orderTotal);

    return customer.IsPremium switch
    {
        true when orderTotal >= 1000m => orderTotal * 0.20m,
        true => orderTotal * 0.10m,
        false => 0m
    };
}

نکات تکمیلی برای فرآیند تست‌نویسی هوشمند

  • دستور اختصاصی /tests در Copilot Chat: با انتخاب متد و ارسال دستور /tests، مدل به صورت خودکار شروع به تولید ساختار اولیه تست می‌کند؛ اضافه کردن قیود مدنظر بلافاصله بعد از این دستور، تست‌ها را به سمت موارد مرزی هدایت خواهد کرد.
  • استفاده از الگوی فیکچرها و داده‌های تست (Test Data Builders / Bogus): از Copilot بخواهید داده‌های پیچیده را با کتابخانه‌هایی مانند Bogus یا متدهای سازنده کمکی (Factory Methods) شبیه‌سازی کند تا تست‌ها از شلوغی کدهای اولیه‌سازی پاک بمانند.
  • تست رفتارهای همزمانی و لغو عملیات: برای متدهای Async، حتماً از مدل بخواهید سناریوی پرتاب شدن OperationCanceledException هنگام لغو CancellationToken را تست کند.

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