فهرست مطالب

Cucumber به عنوان یکی از ابزارهای قدرتمند در توسعه مبتنی بر رفتار (BDD)، به تیم‌ها کمک می‌کند تا با استفاده از زبانی مشترک و قابل فهم (Gherkin)، نیازمندی‌های نرم‌افزار را به تست‌های خودکار تبدیل کنند. با این حال، با رشد پروژه و افزایش تعداد سناریوها، حفظ سازماندهی، خوانایی و قابلیت نگهداری این تست‌ها به یک چالش تبدیل می‌شود. در این مقاله، به بررسی عمیق بهترین شیوه‌ها برای سازماندهی فایل‌های Feature، تعاریف گام (Step Definitions)، استفاده هوشمندانه از تگ‌ها و مدیریت کارآمد هوک‌ها در Cucumber می‌پردازیم تا بتوانید از پتانسیل کامل این ابزار بهره‌مند شوید.

درک عمیق Cucumber و اجزای کلیدی آن

پیش از ورود به بهترین شیوه‌ها، مروری کوتاه بر اجزای اصلی Cucumber خواهیم داشت:

  • فایل‌های Feature (Feature Files): این فایل‌ها با پسوند .feature، شامل سناریوهایی هستند که رفتار مورد انتظار سیستم را با استفاده از زبان Gherkin توصیف می‌کنند. هر فایل Feature معمولاً یک قابلیت یا ویژگی خاص نرم‌افزار را پوشش می‌دهد.
  • زبان Gherkin: یک زبان خاص دامنه (DSL) است که با استفاده از کلمات کلیدی مانند Feature، Background، Scenario، Given، When، Then، And، But نوشته می‌شود و به افراد فنی و غیرفنی اجازه می‌دهد تا رفتار سیستم را به شکلی واضح درک کنند.
  • تعاریف گام (Step Definitions): اینها قطعه کدهایی هستند (معمولاً در زبان‌هایی مانند Java، Ruby، JavaScript) که هر گام Gherkin را به یک اقدام اجرایی در سیستم تحت تست نگاشت می‌کنند.
  • تگ‌ها (Tags): کلمات کلیدی با پیشوند @ هستند که برای دسته‌بندی، فیلتر کردن و اعمال تنظیمات خاص به سناریوها یا فیچرها استفاده می‌شوند.
  • هوک‌ها (Hooks): بلوک‌های کدی هستند که قبل یا بعد از اجرای سناریوها یا گام‌ها اجرا می‌شوند و برای مدیریت پیش‌شرط‌ها (setup) و پس‌شرط‌ها (teardown) کاربرد دارند.
  • Runner: کلاسی است که مسئولیت اجرای فایل‌های Feature، پیدا کردن تعاریف گام مرتبط و تولید گزارش‌ها را بر عهده دارد.

بهترین شیوه‌ها برای سازماندهی فایل‌های Feature

سازماندهی صحیح فایل‌های Feature اساس یک پروژه BDD موفق است. این امر نه تنها به خوانایی کمک می‌کند، بلکه نگهداری و توسعه تست‌ها را نیز آسان‌تر می‌سازد.

۱. ساختار پوشه منطقی و معنادار

ایجاد یک ساختار پوشه که منعکس‌کننده ساختار ماژول‌ها، قابلیت‌ها یا اپیک‌های (Epics) پروژه شما باشد، بسیار حیاتی است.

  • بر اساس قابلیت (Feature-based): هر قابلیت اصلی سیستم، پوشه مخصوص به خود را دارد. این رویکرد معمولاً برای اکثر پروژه‌ها مناسب است.
features/
├── login/
│   ├── login_successful.feature
│   └── login_invalid_credentials.feature
├── product_search/
│   ├── search_by_keyword.feature
│   └── filter_search_results.feature
└── shopping_cart/
    ├── add_to_cart.feature
    └── checkout.feature
  • بر اساس اپیک یا جریان کاربر (Epic/User Journey-based): اگر پروژه شما بسیار بزرگ است یا جریان‌های کاربری پیچیده‌ای دارد، این ساختار می‌تواند مفید باشد.

۲. نام‌گذاری معنادار فایل‌ها و سناریوها

  • نام فایل Feature: باید به وضوح قابلیتی را که توصیف می‌کند، نشان دهد (مثلاً user_authentication.feature). از حروف کوچک و آندرلاین استفاده کنید.
  • عنوان Feature: در داخل فایل، خط اول با کلمه کلیدی Feature: شروع می‌شود و باید توصیفی کوتاه و جامع از قابلیت باشد.
  • عنوان Scenario: هر سناریو باید یک رفتار یا مسیر خاص از قابلیت را توصیف کند. از نام‌های گویا استفاده کنید که هدف سناریو را مشخص کنند.

۳. استفاده از Background برای گام‌های تکراری

اگر چندین سناریو در یک فایل Feature گام‌های اولیه مشترکی دارند (مثلاً ورود کاربر به سیستم)، از بلوک Background برای تعریف این گام‌ها استفاده کنید. این کار از تکرار جلوگیری کرده و خوانایی را افزایش می‌دهد.

Feature: Manage user profile

  Background:
    Given the user is logged in as "testuser"

  Scenario: Update profile information
    When the user navigates to the profile page
    And the user updates their email to "newemail@example.com"
    Then the profile should be updated successfully

  Scenario: Change password
    When the user navigates to the security settings
    And the user changes their password
    Then the password should be updated successfully

۴. استفاده از Scenario Outline برای تست‌های مبتنی بر داده

زمانی که نیاز دارید یک سناریو را با مجموعه‌های مختلفی از داده‌ها اجرا کنید، Scenario Outline به همراه Examples بسیار کارآمد است. این روش خوانایی را حفظ کرده و از ایجاد سناریوهای تکراری با داده‌های متفاوت جلوگیری می‌کند.

Scenario Outline: Login with different user types
  Given the user is on the login page
  When the user enters "<username>" and "<password>"
  And clicks the login button
  Then the user should be "<status>"

  Examples:
    | username   | password   | status         |
    | "standard" | "pass123"  | "successful"   |
    | "locked"   | "pass456"  | "locked_out"   |
    | "invalid"  | "wrong"    | "unsuccessful" |

۵. نوشتن سناریوهای اتمی و مستقل

هر سناریو باید یک واحد تست مستقل باشد و به نتایج سناریوهای دیگر وابسته نباشد. این امر اجرای موازی تست‌ها را تسهیل کرده و تحلیل خطاها را ساده‌تر می‌کند.

۶. تمرکز بر “چه” نه “چگونه” در Gherkin

زبان Gherkin باید رفتار مورد انتظار سیستم را از دیدگاه کاربر توصیف کند، نه جزئیات پیاده‌سازی آن. از عباراتی که به عناصر UI خاص یا فراخوانی متدهای خاص اشاره دارند، اجتناب کنید.

  • بد: When the user clicks the button with id "submit_button"
  • خوب: When the user submits the registration form

بهترین شیوه‌ها برای نوشتن و سازماندهی تعاریف گام (Step Definitions)

تعاریف گام قلب اجرایی تست‌های Cucumber هستند. نوشتن کدهای تمیز، قابل استفاده مجدد و قابل نگهداری در این بخش بسیار مهم است.

۱. اصل DRY (Don’t Repeat Yourself)

از تکرار کد در تعاریف گام خودداری کنید. گام‌های مشابه را با استفاده از پارامترها و عبارات باقاعده (Regular Expressions) عمومی‌سازی کنید.

۲. استفاده بهینه از عبارات باقاعده (Regular Expressions)

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

  • مثال: Given the user “John Doe” is logged in
// Java Example
@Given("^the user \"([^\"]*)\" is logged in$")
public void theUserIsLoggedIn(String userName) {
    // ... login logic for userName
}

۳. یکپارچگی و عدم وابستگی شدید به جزئیات پیاده‌سازی (استفاده از Page Object Model)

تعاریف گام نباید مستقیماً با عناصر UI یا جزئیات فنی پیاده‌سازی درگیر شوند. الگوی Page Object Model (POM) یک روش عالی برای جداسازی منطق تست از جزئیات صفحات وب است. تعاریف گام، متدهای موجود در Page Objects را فراخوانی می‌کنند. این کار باعث می‌شود در صورت تغییر UI، فقط نیاز به به‌روزرسانی Page Objects باشد و تعاریف گام دست نخورده باقی بمانند.

۴. سازماندهی فایل‌های Step Definition

  • بر اساس قابلیت: مشابه فایل‌های Feature، می‌توانید تعاریف گام را نیز در فایل‌هایی متناظر با قابلیت‌های سیستم سازماندهی کنید (مثلاً LoginSteps.java، ProductSearchSteps.java).
  • بر اساس گروهی از گام‌های مرتبط: اگر گام‌هایی دارید که در چندین قابلیت استفاده می‌شوند (مثلاً گام‌های عمومی ناوبری)، می‌توانید آنها را در یک فایل جداگانه مانند CommonSteps.java قرار دهید.

۵. نام‌گذاری واضح متدها

نام متدهایی که تعاریف گام را پیاده‌سازی می‌کنند باید با متن گام Gherkin تا حد امکان هم‌خوانی داشته باشد تا ردیابی و درک کد آسان‌تر شود.

۶. تزریق وابستگی (Dependency Injection)

برای مدیریت وضعیت و اشتراک‌گذاری داده‌ها بین گام‌های مختلف در یک سناریو (مثلاً شیء کاربر لاگین شده یا داده‌های تست)، از مکانیزم‌های تزریق وابستگی (مانند PicoContainer، Spring، یا Context Injection در Cucumber-JVM) استفاده کنید. این کار از استفاده از متغیرهای استاتیک یا الگوهای Singleton که می‌توانند منجر به مشکلات در اجرای موازی شوند، جلوگیری می‌کند.

استفاده هوشمندانه از تگ‌ها (Tags)

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

۱. دسته‌بندی سناریوها

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

  • سطح تست: @smoke@regression@sanity
  • قابلیت: @login@search@payment (می‌تواند مکمل ساختار پوشه باشد)
  • وضعیت: @wip (Work In Progress – برای سناریوهای در حال توسعه)، @defect_123 (مرتبط با یک باگ خاص)
  • اولویت: @high_priority@medium_priority

۲. اجرای انتخابی تست‌ها

با استفاده از تگ‌ها، می‌توانید مجموعه‌ای خاص از تست‌ها را برای اجرا انتخاب کنید. این امر در چرخه‌های CI/CD یا هنگام نیاز به اجرای سریع مجموعه‌ای از تست‌های حیاتی بسیار مفید است.

  • مثال (خط فرمان): mvn test -Dcucumber.filter.tags="@smoke and not @wip" این دستور سناریوهایی را اجرا می‌کند که تگ @smoke دارند و تگ @wip ندارند.

۳. تگ‌های شرطی و نشانگر

  • می‌توانید از تگ‌ها برای فعال یا غیرفعال کردن هوک‌های خاص استفاده کنید (هوک‌های تگ‌دار).
  • تگ‌ها می‌توانند به عنوان نشانگرهایی برای ابزارهای گزارش‌گیری یا تحلیل تست عمل کنند.

۴. عدم استفاده بیش از حد و پیچیده کردن تگ‌ها

در حالی که تگ‌ها مفید هستند، استفاده بیش از حد و ایجاد یک سیستم تگ‌گذاری بسیار پیچیده می‌تواند منجر به سردرگمی شود. سادگی و وضوح را حفظ کنید.

مدیریت کارآمد هوک‌ها (Hooks)

هوک‌ها به شما اجازه می‌دهند تا کدهایی را قبل یا بعد از هر سناریو یا حتی هر گام اجرا کنید. این برای مدیریت پیش‌شرط‌ها (مانند راه‌اندازی مرورگر، اتصال به دیتابیس) و پس‌شرط‌ها (مانند بستن مرورگر، پاک‌سازی داده‌ها) ضروری است.

۱. انواع هوک‌ها

  • @Before: قبل از هر سناریو اجرا می‌شود.
  • @After: بعد از هر سناریو اجرا می‌شود، حتی اگر سناریو با شکست مواجه شود.
  • @BeforeStep: قبل از هر گام اجرا می‌شود.
  • @AfterStep: بعد از هر گام اجرا می‌شود.

۲. هوک‌های تگ‌دار (Tagged Hooks)

می‌توانید هوک‌ها را طوری تنظیم کنید که فقط برای سناریوهایی با تگ‌های خاص اجرا شوند. این کار انعطاف‌پذیری زیادی را فراهم می‌کند.

// Java Example
@Before("@database_setup")
public void setupDatabase() {
    // Code to setup database for scenarios tagged with @database_setup
}

@After("@browser_session")
public void closeBrowser() {
    // Code to close browser after scenarios tagged with @browser_session
}

۳. محدود کردن منطق در هوک‌ها

هوک‌ها باید تا حد امکان سبک و سریع باشند. منطق اصلی تست باید در تعاریف گام باشد. هوک‌ها عمدتاً برای مدیریت وضعیت و محیط تست هستند.

۴. مدیریت وضعیت و داده‌های مشترک با احتیاط

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

۵. جلوگیری از تداخل هوک‌ها

اگر چندین هوک @Before یا @After دارید، می‌توانید با استفاده از پارامتر order ترتیب اجرای آنها را مشخص کنید. هوک با order کمتر، زودتر اجرا می‌شود.

// Java Example
@Before(order = 1)
public void initialSetup() { /* ... */ }

@Before(order = 0)
public void highestPrioritySetup() { /* ... هذا سینفذ أولاً ... */ }

نکات پیشرفته و الگوهای تکمیلی

  • World Object (یا Context Object): در برخی پیاده‌سازی‌های Cucumber (مانند Cucumber.js یا با استفاده از کتابخانه‌های تزریق وابستگی در JVM)، یک شیء “World” برای هر سناریو ایجاد می‌شود. این شیء می‌تواند برای نگهداری و اشتراک‌گذاری وضعیت بین گام‌های یک سناریو استفاده شود بدون آلوده کردن فضای نام سراسری یا استفاده از متغیرهای استاتیک.
  • گزارش‌گیری جامع: از قابلیت‌های گزارش‌گیری Cucumber (مانند گزارش‌های HTML، JSON) و ادغام آنها با ابزارهای گزارش‌گیری پیشرفته‌تر (مانند ExtentReports، Allure) برای داشتن دید کامل از نتایج تست‌ها استفاده کنید.
  • ادغام با CI/CD: تست‌های Cucumber خود را در پایپ‌لاین‌های یکپارچه‌سازی و تحویل مداوم (CI/CD) مانند Jenkins، GitLab CI، GitHub Actions ادغام کنید تا از اجرای خودکار و منظم آنها اطمینان حاصل کنید.
  • بازبینی منظم کد تست: کد تست، مانند کد اصلی برنامه، نیازمند بازبینی و بهبود مستمر است. به طور منظم فایل‌های Feature، تعاریف گام، و ساختار پروژه خود را بازبینی کنید تا از رعایت بهترین شیوه‌ها و حفظ کیفیت اطمینان حاصل نمایید.

نتیجه‌گیری

پیاده‌سازی موفق Cucumber در یک پروژه نرم‌افزاری فراتر از نوشتن صرف سناریوهای Gherkin است. سازماندهی منطقی فایل‌های Feature، نوشتن تعاریف گام تمیز و قابل استفاده مجدد، استفاده هوشمندانه از تگ‌ها برای مدیریت اجرا، و به‌کارگیری مؤثر هوک‌ها برای مدیریت پیش‌نیازها و پس‌نیازها، همگی از ارکان اساسی برای ساخت مجموعه‌ای از تست‌های BDD پایدار، قابل نگهداری و کارآمد هستند. با به‌کارگیری این بهترین شیوه‌ها، تیم شما می‌تواند از مزایای کامل توسعه مبتنی بر رفتار بهره‌مند شده و کیفیت نرم‌افزار خود را به طور قابل توجهی ارتقا دهد. به یاد داشته باشید که این شیوه‌ها راهنما هستند و ممکن است نیاز باشد آنها را متناسب با نیازها و پیچیدگی پروژه خود تطبیق دهید.

سوالات متداول

چگونه از تکرار کد در Step Definitions جلوگیری کنیم؟

با استفاده از پارامترها در گام‌های Gherkin و عبارات باقاعده انعطاف‌پذیر در تعاریف گام، می‌توانید گام‌های مشابه را عمومی‌سازی کنید. همچنین، منطق مشترک را به متدهای کمکی جداگانه منتقل کرده و از تعاریف گام مختلف آنها را فراخوانی کنید. الگوی Page Object Model نیز به کاهش تکرار در تعامل با UI کمک شایانی می‌کند.

بهترین ساختار پوشه برای فایل‌های Feature چیست؟

بهترین ساختار معمولاً بر اساس قابلیت‌های اصلی (features) یا ماژول‌های برنامه است. برای مثال، پوشه‌هایی مانند features/authentication، features/product_management و غیره. این کار به یافتن سریع فیچرهای مرتبط و درک دامنه تست‌ها کمک می‌کند. در پروژه‌های بسیار بزرگ، می‌توان از یک سطح دسته‌بندی بالاتر مانند اپیک‌ها نیز استفاده کرد.

تگ‌ها در Cucumber چه کاربردی دارند و چگونه به اجرای تست‌ها کمک می‌کنند؟

تگ‌ها (مانند @smoke@regression@wip) برای دسته‌بندی و فیلتر کردن سناریوها و فیچرها استفاده می‌شوند. این امکان را فراهم می‌کنند که بتوانید به صورت انتخابی مجموعه‌ای از تست‌ها را اجرا کنید (مثلاً فقط تست‌های دود یا تست‌های مربوط به یک قابلیت خاص). همچنین می‌توان از آنها برای اعمال هوک‌های شرطی (tagged hooks) استفاده کرد.

تفاوت اصلی بین Background و هوک‌های @Before در چیست؟

Background بخشی از زبان Gherkin است و گام‌های آن برای خواننده فایل Feature (شامل افراد غیرفنی) قابل مشاهده است. این گام‌ها پیش‌شرط‌های مشترک چندین سناریو در یک فایل Feature را توصیف می‌کنند. هوک @Before یک قطعه کد برنامه‌نویسی است که قبل از هر سناریو (با یا بدون تگ خاص) اجرا می‌شود و معمولاً برای تنظیمات فنی و آماده‌سازی محیط تست استفاده می‌شود که لزوماً برای خواننده فایل Feature مهم نیست (مانند راه‌اندازی درایور وب، پاک‌سازی دیتابیس).

چگونه می‌توان خوانایی سناریوهای Gherkin را افزایش داد؟

از زبان طبیعی و قابل فهم برای کسب‌وکار استفاده کنید. بر “چه” چیزی تست می‌شود تمرکز کنید نه “چگونه”. سناریوها را کوتاه، متمرکز و مستقل نگه دارید. از نام‌گذاری واضح برای Feature ها و Scenario ها استفاده کنید. از Scenario Outline برای داده‌های مختلف و از Background برای گام‌های مشترک بهره ببرید تا از تکرار جلوگیری شود.

بیشتر بخوانید:

دیدگاهتان را بنویسید