Levelwise
فارسی
تست

توسعه رفتارمحور (BDD)

در BDD برنامه‌نویس، تستر و کارشناس کسب‌وکار قبل از نوشتن کد با هم چند مثال واقعی می‌سازند. این مثال‌ها به شکل Given، When، Then نوشته می‌شوند و به تست خودکار تبدیل می‌شوند. نتیجه، مستنداتی است که همیشه با کد هماهنگ است.

بازبینی نشدهبا کمک AI نوشته شدهزمان خواندن: ۱۱ دقیقهمثال ارسال رایگان در فروشگاه اینترنتیکد C# و Reqnroll

نویسنده: bezzad

مشکل: کد درست، ولی چیز اشتباه

کارشناس فروش می‌گوید: «سفارش‌های بالای ۵۰۰ هزار تومان ارسال رایگان دارند.»

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

  1. منظور از «بالای ۵۰۰ هزار» چه بود؟ برنامه‌نویس فکر کرد ۵۰۰ هزار خودش رایگان نیست. کارشناس فکر می‌کرد هست.
  2. مبلغ قبل از تخفیف یا بعد از آن؟ برنامه‌نویس مبلغ قبل از تخفیف را حساب کرد. کارشناس مبلغ بعد از تخفیف را می‌خواست.
  3. تست‌ها چرا خطا را نگرفتند؟ چون تست‌ها برداشت برنامه‌نویس را چک می‌کردند، نه خواسته کسب‌وکار را.

مشکل در کد نبود. مشکل در فهم مشترک بود.

ایده BDD: گفتگو با مثال‌های واقعی

توسعه رفتارمحور (Behavior-Driven Development) از همین مشکل شروع می‌شود. قبل از نوشتن کد، سه نقش با هم یک جلسه کوتاه دارند: کسی که کسب‌وکار را می‌شناسد، کسی که کد می‌نویسد و کسی که تست می‌کند. به این جلسه گاهی «سه دوست» (Three Amigos) می‌گویند.

کارشناس فروشبرنامه‌نویستستریک جلسه کوتاهمثال‌های واقعی۴۹۹ هزار: ارسال پولی۵۰۰ هزار: رایگان.featureGiven When Thenبه زبان کسب‌وکارتست خودکارReqnroll + xUnitروی کد واقعیمهم‌ترین بخش، گفتگو است، نه ابزار
مثال‌های جلسه، همان تست‌های خودکار می‌شوند.

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

  • سبد ۴۹۹ هزار تومانی، بعد از تخفیف: هزینه ارسال ۵۰ هزار تومان.
  • سبد ۵۰۰ هزار تومانی، بعد از تخفیف: ارسال رایگان.
  • سبد ۶۰۰ هزار تومانی با ۲۰ درصد تخفیف، یعنی ۴۸۰ هزار: هزینه ارسال ۵۰ هزار تومان.

همین سه مثال هر دو سوءتفاهم بالا را قبل از نوشتن حتی یک خط کد پیدا می‌کنند.

زبان Given، When، Then

مثال‌ها را با یک شکل ثابت می‌نویسیم. به این شکل Gherkin می‌گوییم:

  1. در این حالت (Given). وضعیت اول سیستم. مثلاً جمع سبد.
  2. وقتی (When). کاری که کاربر انجام می‌دهد. مثلاً پرداخت سبد.
  3. آنگاه (Then). نتیجه‌ای که انتظار داریم. مثلاً هزینه ارسال.

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

Feature: Free shipping
  Orders of 500000 toman or more ship for free.
  The rule uses the total after discount.

  Scenario: Order just below the limit pays shipping
    Given the cart total is 499000 toman
    When the customer checks out
    Then the shipping cost is 50000 toman

  Scenario Outline: Shipping cost by cart total
    Given the cart total is <total> toman
    When the customer checks out
    Then the shipping cost is <shipping> toman

    Examples:
      | total  | shipping |
      | 200000 | 50000    |
      | 500000 | 0        |
      | 480000 | 50000    |

با Scenario Outline یک سناریو را با چند ردیف داده اجرا می‌کنیم. هر ردیف جدول یک مثال است.

وصل کردن سناریو به کد

فایل feature به تنهایی فقط متن است. یک ابزار باید هر خط آن را به یک متد C# وصل کند. در .NET، ابزار رایج امروز Reqnroll است. ابزار قدیمی‌تر SpecFlow در پایان سال ۲۰۲۴ کنار گذاشته شد. ابزار Reqnroll از کد همان پروژه ساخته شده است و مهاجرت به آن ساده است.

سناریو به زبان کسب‌وکارمتدهای کد تستGiven the cart total is 499000 tomanWhen the customer checks outThen the shipping cost is 50000 tomanGivenCartTotal(int total)WhenCustomerChecksOut()ThenShippingCostIs(int expected)آماده کردن، اجرا، بررسی؛ همان سه بخش تست واحد
[Binding]
public sealed class ShippingSteps
{
    private decimal _cartTotal;
    private decimal _shipping;

    [Given("the cart total is {int} toman")]
    public void GivenCartTotal(int total) => _cartTotal = total;

    [When("the customer checks out")]
    public void WhenCustomerChecksOut() =>
        _shipping = ShippingCost.For(_cartTotal, isVip: false);

    [Then("the shipping cost is {int} toman")]
    public void ThenShippingCostIs(int expected) => Assert.Equal(expected, _shipping);
}

کلمه int داخل آکولاد، عدد داخل جمله را پیدا می‌کند و به پارامتر متد می‌دهد. پس یک متد برای همه سناریوهایی کافی است که همین جمله را با عدد دیگر دارند.

اصل BDD ابزار نیست. حتی بدون Reqnroll هم می‌شود BDD انجام داد. بخش اصلی، گفتگو و مثال‌های مشترک است. می‌شود همین مثال‌ها را در یک تست xUnit با اسم‌های روشن نوشت. ابزار فقط وقتی ارزش دارد که آدم‌های غیر برنامه‌نویس واقعاً فایل‌های feature را بخوانند.

سناریوی خوب، سناریوی بد

سناریو باید رفتار را بگوید، نه قدم‌های کلیک کردن را.

بددستوری و وابسته به صفحه

Given I open "/cart"
And I click "#add-item-7"
And I type "2" into "#qty"
When I click "#checkout"
Then I see "50,000" in ".shipping"

اگر طراحی صفحه عوض شود، سناریو می‌شکند. کارشناس فروش هم از آن چیزی نمی‌فهمد.

خوبتوصیفی و به زبان کسب‌وکار

Given the cart total is 499000 toman
When the customer checks out
Then the shipping cost is 50000 toman

فقط قانون را می‌گوید. چطور اجرا شدنش، کار متدهای کد تست است.

مقایسه BDD و TDD

TDD BDD
سؤال اصلی آیا کد درست کار می‌کند؟ آیا چیز درستی می‌سازیم؟
چه کسی می‌نویسد؟ برنامه‌نویس سه نقش با هم
زبان تست کد C# جمله‌های Given، When، Then
اندازه معمولاً یک کلاس یا یک قانون کوچک معمولاً یک قابلیت کامل

این دو رقیب هم نیستند. می‌شود سناریوی BDD را برای یک قابلیت نوشت و داخل آن، هر کلاس را با TDD ساخت.

قانون‌های مهم

  1. اول گفتگو، بعد فایل. اگر برنامه‌نویس به تنهایی فایل feature بنویسد، فقط یک تست با ظاهر متفاوت ساخته است.
  2. هر سناریو یک رفتار. سناریوی طولانی با ده قدم When را به چند سناریو تقسیم کن.
  3. زبان کسب‌وکار، نه زبان فنی. در سناریو اسم جدول، آدرس صفحه و کد HTML نباشد.
  4. همه سناریوها از راه صفحه اجرا نشوند. متدهای کد تست می‌توانند مستقیم کد دامنه یا API را صدا بزنند. این خیلی سریع‌تر و پایدارتر است.
  5. جمله‌ها را یکسان نگه دار. اگر یک جمله را ده شکل مختلف بنویسیم، ده متد تکراری لازم می‌شود.

اشتباه‌های رایج

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

چه وقت BDD؟

مناسب

  • قانون‌های کسب‌وکار پیچیده که سوءتفاهم در آن‌ها گران تمام می‌شود.
  • تیمی که کارشناس کسب‌وکار در دسترس دارد و او واقعاً سناریوها را می‌خواند.
  • حوزه‌هایی مثل بیمه، مالیات، قیمت‌گذاری و پرداخت.

نامناسب

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

خلاصه در شش خط

  1. خیلی از باگ‌ها از سوءتفاهم می‌آیند، نه از کد اشتباه.
  2. در BDD سه نقش قبل از کد با هم مثال‌های دقیق می‌سازند.
  3. مثال‌ها با Given، When، Then نوشته می‌شوند تا همه بتوانند آن‌ها را بخوانند.
  4. در .NET، ابزار Reqnroll هر جمله را به یک متد C# وصل می‌کند.
  5. سناریوی خوب رفتار را می‌گوید، نه کلیک‌ها را.
  6. بخش اصلی BDD گفتگو است. بدون گفتگو، ابزار فقط هزینه اضافه است.