A comprehensive test automation framework demonstrating BDD (Behavior-Driven Development) using Cucumber with SpecFlow in C# .NET. This project showcases professional-grade test automation practices with complete coding examples and best practices guide.
This repository contains a complete SpecFlow test automation suite demonstrating best practices for:
- Behavior-Driven Development (BDD) with Gherkin syntax
- Test Automation patterns and practices
- C# .NET implementation of test steps
- Scalable test framework architecture
The project is structured into learning sections (Section 4 through Section 15), each covering different aspects of Cucumber and SpecFlow automation.
Cucumber-Automation-CSharp-NET-Specflow/
├── README.md # This file
├── Section 4/ # Initial BDD concepts
│ ├── Features/ # .feature files
│ ├── StepDefinitions/ # Step definition files
│ └── Hooks/ # Before/After hooks
├── Section 5/ # Test structure & organization
├── Section 6/ # Advanced step definitions
├── Section 7/ # Data-driven testing
├── Section 8/ # Hooks & test lifecycle
├── Section 9/ # Advanced Gherkin scenarios
├── Section 10/ # Integration testing
├── Section 11/ # Performance testing
├── Section 12/ # Error handling & reporting
├── Section 13/ # Parallel execution
├── Section 14/ # CI/CD integration
└── Section 15/ # Best practices & patterns
✅ Behavior-Driven Development (BDD)
- Human-readable Gherkin feature files
- Clear Given-When-Then scenario structure
- Executable specifications
✅ Professional Test Framework
- Organized step definitions in C#
- Reusable test components
- Page Object Model support
- Data-driven testing capabilities
✅ Comprehensive Coverage
- Unit test automation
- Integration test examples
- End-to-end test scenarios
- API testing demonstrations
✅ Advanced Testing Patterns
- Before/After hooks for test setup and teardown
- Scenario context management
- Tag-based test execution
- Parallel test execution
✅ Reporting & Logging
- Built-in SpecFlow reporting
- Test execution tracking
- Detailed test result documentation
✅ Scalable Architecture
- Modular step definitions
- Feature-based organization
- Easy maintenance and expansion
- Code reusability patterns
- Windows 10/11 or macOS or Linux
- .NET Framework 4.7.2 or higher, OR .NET Core 3.1+, OR .NET 5+
- Visual Studio 2019/2022 or Visual Studio Code
- NuGet Package Manager
- Git (for version control)
- .NET SDK (latest stable version recommended)
- C# 8.0 or higher
| Language | Usage | Percentage |
|---|---|---|
| C# | Step Definitions, Test Logic, Framework | 80.9% |
| Gherkin | Feature Files, Test Scenarios | 19.1% |
- SpecFlow - BDD framework for .NET
- Cucumber - BDD standard & Gherkin language
- NUnit/MSTest/xUnit - Unit testing frameworks
- Selenium WebDriver (optional) - Web automation
- RestSharp (optional) - API testing
SpecFlow
SpecFlow.NUnit (or MSTest/xUnit)
Selenium.WebDriver (for web automation)
RestSharp (for API testing)
NLog (for logging)
- Feature files structure
- Scenario definitions
- Scenario Outline with Examples
- Background setup
- Preconditions (Given)
- User actions (When)
- Expected outcomes (Then)
- Supporting clauses (And, But)
- Regular expressions in step bindings
- Step parameters and tables
- Multiline text handling
- Step definition reusability
[Before]hooks for setup[After]hooks for teardown- Test context sharing
- Scenario context usage
- Scenario Outlines
- Example tables
- Multiple iteration scenarios
- Feature file grouping
- Tag-based organization
- Scenario filtering
- Continuous Integration setup
- Test execution pipelines
- Automated test runs
- Report generation
- Page Object Model with Gherkin
- Custom step definitions
- Error handling in automation
- Performance testing scenarios
-
Open the Solution
File → Open → Project/Solution Select the .sln file -
Restore NuGet Packages
Tools → NuGet Package Manager → Manage NuGet Packages for Solution Click "Restore" -
Build Solution
Build → Build Solution (Ctrl+Shift+B)
# Restore packages
dotnet restore
# Build project
dotnet build
# Run tests
dotnet test# Install SpecFlow
Install-Package SpecFlow
# Install test framework (choose one)
Install-Package SpecFlow.NUnit
Install-Package SpecFlow.MSTest
Install-Package SpecFlow.xUnit
# Restore all packages
Update-Package -Reinstall- Open Test Explorer (Test → Windows → Test Explorer)
- Click Run All Tests
- View results in the Test Explorer window
dotnet testdotnet test --filter "Category=SmokeTests"# Run only scenarios tagged with @smoke
dotnet test --filter "Tags=smoke"# SpecFlow HTML report
dotnet test -- --logger:"html;LogFileName=TestResults.html"git clone https://github.com/BrianGator/Cucumber-Automation-CSharp-NET-Specflow.git
cd Cucumber-Automation-CSharp-NET-SpecflowStart with Section 4 for fundamentals and progress through sections sequentially:
- Each section builds on previous concepts
- Contains example feature files (
.feature) - Includes corresponding step definitions (
.cs)
Each section contains .feature files with Gherkin scenarios:
Feature: Example Feature
Description of what this feature does
Scenario: Example scenario
Given a precondition exists
When I perform an action
Then I should see the expected resultExamine C# step definition files to understand:
- How to bind Gherkin steps to code
- Best practices for test implementation
- Data handling and assertions
Execute tests through your IDE or command line
| Section | Topic | Focus |
|---|---|---|
| Section 4 | BDD Fundamentals | Introduction to Gherkin & SpecFlow |
| Section 5 | Test Structure | Organizing features and step definitions |
| Section 6 | Advanced Steps | Complex step definitions and validations |
| Section 7 | Data-Driven Tests | Scenario Outlines and example tables |
| Section 8 | Hooks & Lifecycle | Before/After hooks and context management |
| Section 9 | Advanced Scenarios | Complex Gherkin patterns |
| Section 10 | Integration Tests | Multi-component testing |
| Section 11 | Performance | Load and performance testing |
| Section 12 | Error Handling | Exception handling and reporting |
| Section 13 | Parallel Execution | Running tests in parallel |
| Section 14 | CI/CD Integration | GitHub Actions, Azure Pipelines setup |
| Section 15 | Best Practices | Patterns, anti-patterns, and recommendations |
Introduction to Gherkin syntax and basic SpecFlow setup. Learn the Given-When-Then pattern.
Organizing features and step definitions following best practices.
Complex step definitions with parameters, tables, and multiline arguments.
Using Scenario Outlines and example tables for parametrized testing.
Managing test setup, teardown, and scenario context.
Complex Gherkin patterns and scenario composition.
Multi-component and integration testing scenarios.
Performance benchmarking and load testing patterns.
Exception handling, logging, and error reporting strategies.
Running tests in parallel for faster execution.
GitHub Actions and Azure Pipelines integration examples.
Comprehensive patterns, anti-patterns, and recommendations.
This section provides comprehensive coding examples and best practices for SpecFlow and Cucumber development in C# .NET.
Feature: User Login
As a user
I want to log in to the application
So that I can access my account
Background:
Given the login page is displayed
And the user database is initialized
Scenario: Successful login with valid credentials
Given I have entered username "john.doe" and password "SecurePass123"
When I click the login button
Then I should be redirected to the dashboard
And I should see a welcome message for "john.doe"
Scenario: Failed login with invalid credentials
Given I have entered username "invalid.user" and password "WrongPassword"
When I click the login button
Then I should remain on the login page
And I should see an error message "Invalid credentials"Key Points:
- Use Background for common setup steps
- Keep scenario titles descriptive and concise
- Use Given-When-Then structure consistently
- Include both positive and negative scenarios
Feature: Order Processing
Scenario Outline: Process different types of orders
Given I have a shopping cart with value "<cartValue>"
When I apply coupon code "<couponCode>"
And I proceed to checkout
Then the final amount should be "<finalAmount>"
And the discount applied should be "<discountPercent>"
Examples:
| cartValue | couponCode | finalAmount | discountPercent |
| $100.00 | SAVE10 | $90.00 | 10% |
| $250.00 | SAVE25 | $187.50 | 25% |
| $50.00 | SAVE5 | $47.50 | 5% |
| $1000.00 | VIP50 | $500.00 | 50% |Key Points:
- Use Scenario Outline for testing multiple scenarios with different data
- Define Examples tables with clear header names
- Parameters are enclosed in angle brackets
<parameterName> - Each row executes as a separate test
Feature: Inventory Management
Scenario: Add multiple items to inventory
Given the inventory system is started
When I add the following items:
| ItemName | Quantity | Price |
| Laptop | 5 | 999.99 |
| Monitor | 10 | 299.99 |
| Keyboard | 25 | 79.99 |
| Mouse | 50 | 29.99 |
Then the total inventory value should be calculated correctly
And each item should be stored in the database
Scenario: Validate item specifications
Given I have items with the following details:
| ItemCode | Name | Category | InStock |
| LAP001 | MacBook | Computers | true |
| MON001 | Dell U27 | Monitors | true |
| KBD001 | Logitech | Peripherals | false |
Then all items should be properly categorizedKey Points:
- Use Data Tables for complex input data
- Headers become column identifiers
- Useful for bulk operations and validations
- Improves readability compared to long steps
using TechTalk.SpecFlow;
using NUnit.Framework;
[Binding]
public class UserLoginSteps
{
private string _username;
private string _password;
private string _actualError;
private bool _loginSuccessful;
[Given(@"I have entered username ""([^""]*)"" and password ""([^""]*)""")]
public void GivenIHaveEnteredUsernameAndPassword(string username, string password)
{
_username = username;
_password = password;
// Store credentials for login attempt
}
[When(@"I click the login button")]
public void WhenIClickTheLoginButton()
{
// Simulate login
_loginSuccessful = ValidateCredentials(_username, _password);
if (!_loginSuccessful)
{
_actualError = "Invalid credentials";
}
}
[Then(@"I should be redirected to the dashboard")]
public void ThenIShouldBeRedirectedToTheDashboard()
{
Assert.IsTrue(_loginSuccessful, "Login was not successful");
}
[Then(@"I should see an error message ""([^""]*)""")]
public void ThenIShouldSeeAnErrorMessage(string expectedError)
{
Assert.AreEqual(expectedError, _actualError, "Error message does not match");
}
// Helper method
private bool ValidateCredentials(string username, string password)
{
return username == "john.doe" && password == "SecurePass123";
}
}Key Points:
- Use [Binding] attribute on step definition classes
- Use regex patterns in
[Given],[When],[Then]attributes - Extract parameters from Gherkin steps using capture groups
- Keep step definitions focused and testable
using TechTalk.SpecFlow;
using NUnit.Framework;
[Binding]
public class InventorySteps
{
private List<InventoryItem> _addedItems;
[When(@"I add the following items:")]
public void WhenIAddTheFollowingItems(Table table)
{
_addedItems = new List<InventoryItem>();
// Convert table rows to objects
foreach (var row in table.Rows)
{
var item = new InventoryItem
{
ItemName = row["ItemName"],
Quantity = int.Parse(row["Quantity"]),
Price = decimal.Parse(row["Price"])
};
_addedItems.Add(item);
// Add to inventory system
AddToInventory(item);
}
}
[Then(@"the total inventory value should be calculated correctly")]
public void ThenTheTotalInventoryValueShouldBeCalculatedCorrectly()
{
decimal totalValue = _addedItems.Sum(x => x.Quantity * x.Price);
decimal expectedTotal = (5 * 999.99m) + (10 * 299.99m) + (25 * 79.99m) + (50 * 29.99m);
Assert.AreEqual(expectedTotal, totalValue, 0.01m, "Total inventory value is incorrect");
}
private void AddToInventory(InventoryItem item)
{
// Implementation to add item to inventory
}
}
public class InventoryItem
{
public string ItemName { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}Key Points:
- Use Table parameter type to receive Gherkin data tables
- Access columns using row["ColumnName"]
- Convert string values to appropriate types
- Iterate through rows for bulk operations
using TechTalk.SpecFlow;
using NUnit.Framework;
[Binding]
public class DocumentSteps
{
private string _documentContent;
private string _documentTitle;
[Given(@"I create a document with title ""([^""]*)""")]
public void GivenICreateADocumentWithTitle(string title)
{
_documentTitle = title;
_documentContent = string.Empty;
}
[When(@"I add the following content:")]
public void WhenIAddTheFollowingContent(string content)
{
_documentContent = content;
// content can be multiline from the feature file
}
[Then(@"the document should contain:")]
public void ThenTheDocumentShouldContain(string expectedContent)
{
Assert.That(_documentContent, Does.Contain(expectedContent),
"Document does not contain expected content");
}
[Then(@"the word count should be (\d+)")]
public void ThenTheWordCountShouldBe(int expectedWordCount)
{
int actualWordCount = _documentContent.Split(new[] { ' ', '\n', '\r' },
System.StringSplitOptions.RemoveEmptyEntries).Length;
Assert.AreEqual(expectedWordCount, actualWordCount,
"Word count does not match");
}
}Usage in Feature File:
Scenario: Create document with multiline content
Given I create a document with title "My Report"
When I add the following content:
"""
This is a comprehensive report
with multiple paragraphs.
It contains detailed information
about the project status.
"""
Then the document should contain:
"""
comprehensive report
"""
And the word count should be 20Key Points:
- Use multiline strings (triple quotes) for long text
- Use (\d+) regex for numeric parameters
- Use [^""]* regex for text without quotes
- Support complex data scenarios
using TechTalk.SpecFlow;
using NUnit.Framework;
[Binding]
public class Hooks
{
private TestContext _testContext;
// Runs before each scenario
[Before]
public void BeforeScenario(ScenarioContext scenarioContext)
{
_testContext = new TestContext();
_testContext.Initialize();
// Log scenario start
Console.WriteLine($"Starting scenario: {scenarioContext.ScenarioInfo.Title}");
}
// Runs after each scenario
[After]
public void AfterScenario(ScenarioContext scenarioContext)
{
// Cleanup
_testContext.Cleanup();
// Log test result
Console.WriteLine($"Scenario result: {scenarioContext.TestError?.Message ?? "PASSED"}");
}
// Hook with tags
[Before("@database")]
public void BeforeDatabaseTests()
{
// Initialize database connection
Console.WriteLine("Setting up database connection");
}
[After("@database")]
public void AfterDatabaseTests()
{
// Close database connection
Console.WriteLine("Closing database connection");
}
// Hook with order - runs first
[Before(Order = 1)]
public void FirstBeforeHook()
{
Console.WriteLine("This runs first");
}
// Hook with order - runs second
[Before(Order = 2)]
public void SecondBeforeHook()
{
Console.WriteLine("This runs second");
}
}
public class TestContext
{
public void Initialize()
{
// Initialize test resources
}
public void Cleanup()
{
// Release resources
}
}using TechTalk.SpecFlow;
[Binding]
public class UserContextSteps
{
private readonly ScenarioContext _scenarioContext;
// Inject ScenarioContext through constructor
public UserContextSteps(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[Given(@"I have a user with email ""([^""]*)""")]
public void GivenIHaveAUserWithEmail(string email)
{
var user = new User { Email = email };
// Store user in context for access in other steps
_scenarioContext["CurrentUser"] = user;
}
[When(@"I update the user profile")]
public void WhenIUpdateTheUserProfile()
{
// Retrieve user from context
var user = _scenarioContext["CurrentUser"] as User;
if (user != null)
{
user.LastUpdated = DateTime.Now;
_scenarioContext["CurrentUser"] = user;
}
}
[Then(@"the user should have an updated profile")]
public void ThenTheUserShouldHaveAnUpdatedProfile()
{
var user = _scenarioContext["CurrentUser"] as User;
Assert.IsNotNull(user?.LastUpdated, "User profile was not updated");
}
}
public class User
{
public string Email { get; set; }
public DateTime? LastUpdated { get; set; }
}Key Points:
- Use ScenarioContext to share data between steps
- Inject context through constructor
- Access with scenarioContext[key] syntax
- Store complex objects for scenario-wide access
Feature: Payment Processing
Scenario Outline: Calculate shipping costs based on location and weight
Given I have a package weighing "<weight>" pounds
And the destination is "<location>"
When I calculate shipping cost
Then the cost should be "$<shippingCost>"
Examples: Domestic Shipping
| weight | location | shippingCost |
| 5 | New York | 15.99 |
| 10 | California | 22.99 |
| 2 | Texas | 10.99 |
Examples: International Shipping
| weight | location | shippingCost |
| 5 | Canada | 35.99 |
| 10 | Mexico | 42.99 |
| 2 | UK | 55.99 |Key Points:
- Use multiple Examples blocks for different test scenarios
- Label examples with descriptive names
- Each examples block runs separately with same scenario
- Improves test readability and maintenance
using TechTalk.SpecFlow;
using NUnit.Framework;
[Binding]
public class ShippingSteps
{
private decimal _packageWeight;
private string _destination;
private decimal _calculatedCost;
[Given(@"I have a package weighing ""([^""]*)"" pounds")]
public void GivenIHaveAPackageWeighing(string weight)
{
_packageWeight = decimal.Parse(weight);
}
[Given(@"the destination is ""([^""]*)""")]
public void GivenTheDestinationIs(string location)
{
_destination = location;
}
[When(@"I calculate shipping cost")]
public void WhenICalculateShippingCost()
{
_calculatedCost = CalculateShipping(_packageWeight, _destination);
}
[Then(@"the cost should be ""([^""]*)""")]
public void ThenTheCostShouldBe(string expectedCost)
{
decimal expected = decimal.Parse(expectedCost.Replace("$", ""));
Assert.AreEqual(expected, _calculatedCost, 0.01m,
$"Shipping cost mismatch for {_destination}");
}
private decimal CalculateShipping(decimal weight, string location)
{
// Shipping rate calculation logic
decimal baseRate = location.Contains("International") ? 30m : 10m;
return baseRate + (weight * 2);
}
}// LoginPage.cs
public class LoginPage
{
private readonly IWebDriver _driver;
public LoginPage(IWebDriver driver)
{
_driver = driver;
}
// Page Elements
public IWebElement UsernameField => _driver.FindElement(By.Id("username"));
public IWebElement PasswordField => _driver.FindElement(By.Id("password"));
public IWebElement LoginButton => _driver.FindElement(By.Id("login-btn"));
public IWebElement ErrorMessage => _driver.FindElement(By.ClassName("error-message"));
// Page Methods
public void EnterUsername(string username)
{
UsernameField.Clear();
UsernameField.SendKeys(username);
}
public void EnterPassword(string password)
{
PasswordField.Clear();
PasswordField.SendKeys(password);
}
public void ClickLogin()
{
LoginButton.Click();
}
public bool IsErrorDisplayed()
{
return ErrorMessage.Displayed;
}
public string GetErrorText()
{
return ErrorMessage.Text;
}
}
// LoginSteps.cs using Page Object
[Binding]
public class LoginStepsWithPOM
{
private readonly ScenarioContext _scenarioContext;
private LoginPage _loginPage;
public LoginStepsWithPOM(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[Given(@"I am on the login page")]
public void GivenIAmOnTheLoginPage()
{
var driver = _scenarioContext["WebDriver"] as IWebDriver;
_loginPage = new LoginPage(driver);
}
[Given(@"I have entered valid credentials")]
public void GivenIHaveEnteredValidCredentials()
{
_loginPage.EnterUsername("testuser");
_loginPage.EnterPassword("password123");
}
[When(@"I click login")]
public void WhenIClickLogin()
{
_loginPage.ClickLogin();
}
}using TechTalk.SpecFlow;
[Binding]
public class TestDataHooks
{
private readonly ScenarioContext _scenarioContext;
private DatabaseConnection _dbConnection;
public TestDataHooks(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[Before("@requiresTestData")]
public void SetupTestData()
{
_dbConnection = new DatabaseConnection();
// Create test users
var testUser = new User
{
Id = Guid.NewGuid(),
Email = "test@example.com",
Username = "testuser"
};
_dbConnection.InsertUser(testUser);
// Store in context for use in steps
_scenarioContext["TestUser"] = testUser;
}
[After("@requiresTestData")]
public void CleanupTestData()
{
// Delete test data
var testUser = _scenarioContext["TestUser"] as User;
if (testUser != null)
{
_dbConnection.DeleteUser(testUser.Id);
}
_dbConnection.Dispose();
}
}
public class DatabaseConnection : IDisposable
{
public void InsertUser(User user) { /* implementation */ }
public void DeleteUser(Guid userId) { /* implementation */ }
public void Dispose() { /* cleanup */ }
}// BAD - Implementation details
Scenario: Bad Implementation
Given I click on the element with id "login-button-xyz-123"
When I wait 5 seconds
Then I should see the value in input field with class "form-input-primary"
// GOOD - Business language
Scenario: Good Business Language
Given I am on the login page
When I click the login button
Then I should see the dashboard// BAD - Duplicate steps
[Given("I navigate to the home page")]
public void Step1() { Navigate("home"); }
[Given("I navigate to home")]
public void Step2() { Navigate("home"); }
// GOOD - Reusable step
[Given(@"I navigate to the (home|dashboard|profile) page")]
public void NavigateToPage(string page)
{
Navigate(page);
}// BAD - Hardcoded data
[Given("I have a user")]
public void HardcodedUser()
{
CreateUser("john.doe", "password123", "john@example.com");
}
// GOOD - Parameterized data
[Given(@"I have a user with email ""([^""]*)""")]
public void ParametrizedUser(string email)
{
CreateUser("testuser", "password123", email);
}// BAD - Multiple assertions in one step
[Then("everything should be correct")]
public void BadAssertions()
{
Assert.AreEqual(expected1, actual1);
Assert.AreEqual(expected2, actual2);
Assert.AreEqual(expected3, actual3);
}
// GOOD - One assertion per step
[Then("the username should be displayed")]
public void UsernameDisplayed()
{
Assert.AreEqual("john.doe", GetDisplayedUsername());
}
[Then("the email should be verified")]
public void EmailVerified()
{
Assert.IsTrue(IsEmailVerified());
}-
Keep feature files focused and readable
Scenario: Single, well-defined behavior Given clear preconditions When one action is performed Then verify specific outcome
-
Use descriptive scenario names
Scenario: User can successfully login with valid credentials ✓ Better: Specific behavior ✗ Worse: Scenario 1
-
Reuse step definitions across scenarios
- Use regex patterns for parameterization
- Avoid creating similar steps with different names
-
Maintain a clear folder structure
- Organize by feature or domain
- Keep related steps together
-
Use tags for organization and filtering
@smoke @critical @web Scenario: Important feature
-
Keep steps independent and atomic
- Each step should be self-contained
- Avoid inter-step dependencies
-
Document complex step logic
// Calculate compound interest with specified rate [When(@"I calculate interest at (\d+)% for (\d+) years")] public void CalculateInterest(decimal rate, int years) { // Implementation with comments }
-
Hardcode test data in step definitions
- Use parameters from feature files
- Use external data sources
-
Create overly complex Gherkin scenarios
- Keep scenarios focused
- One behavior per scenario
-
Mix multiple concerns in a single step
- Login + verify + check email = bad
- Each step does one thing
-
Ignore test execution failures
- Investigate root causes
- Fix flaky tests
-
Create duplicate step definitions
- Use regex to generalize
- Maintain one source of truth
-
Use implementation details in feature files
- Focus on business behavior
- Hide technical details
// Exact text in quotes
[Given(@"I enter ""([^""]*)""")]
public void EnterText(string text) { }
// Numbers
[Given(@"I have (\d+) items")]
public void HaveItems(int count) { }
// Decimal numbers
[Given(@"the price is £([\d.]+)")]
public void SetPrice(decimal price) { }
// Optional text
[Given(@"I am (.+?)")]
public void SetStatus(string status) { }
// Multiple alternatives
[Given(@"I navigate to (home|dashboard|profile)")]
public void Navigate(string page) { }[When(@"I process the following:")]
public void ProcessTable(Table table)
{
// Method 1: Direct row iteration
foreach (var row in table.Rows)
{
string name = row["Name"];
int age = int.Parse(row["Age"]);
}
// Method 2: Convert to objects
var items = table.CreateSet<TestItem>();
foreach (var item in items)
{
// Work with strongly-typed objects
}
// Method 3: Key-value dictionary
foreach (var row in table.Rows)
{
var dict = row.Keys.ToDictionary(k => k, k => row[k]);
}
}[Binding]
public class ContextExample
{
private readonly ScenarioContext _context;
public ContextExample(ScenarioContext context)
{
_context = context;
}
[Given("I store a value")]
public void StoreValue()
{
_context["key"] = "value";
}
[Then("I retrieve the value")]
public void RetrieveValue()
{
var value = _context.Get<string>("key");
Assert.AreEqual("value", value);
}
[Then("I check if key exists")]
public void CheckKey()
{
bool exists = _context.ContainsKey("key");
}
}[When(@"I perform an operation that might fail")]
public void PerformOperation()
{
try
{
// Perform operation
DoSomething();
}
catch (Exception ex)
{
_scenarioContext["LastException"] = ex;
// Don't re-throw; let Then step verify
}
}
[Then(@"I should have caught an exception of type ""([^""]*)""")]
public void VerifyException(string exceptionType)
{
var exception = _scenarioContext.Get<Exception>("LastException");
Assert.IsNotNull(exception, "No exception was caught");
Assert.AreEqual(exceptionType, exception.GetType().Name);
}# Run only smoke tests
dotnet test --filter "Category=smoke"
# Run tests with multiple tags (AND logic)
dotnet test --filter "Category=smoke&Category=critical"
# Run excluding tests with certain tags
dotnet test --filter "Category!=slow"
# Using environment configuration
dotnet test -- TestRunParameters:Parameter(name="tags", value="@smoke")- SpecFlow Official Documentation
- Cucumber Gherkin Reference
- C# Automation Best Practices
- BDD Principles
- SpecFlow GitHub Repository
- Selenium WebDriver Documentation
- Feature files use business language, not technical implementation
- Each scenario has a single, clear behavior to test
- Step definitions are DRY (Don't Repeat Yourself)
- Tests are independent and can run in any order
- Proper use of hooks for setup and teardown
- Test data is not hardcoded in step definitions
- Meaningful assertion messages for debugging
- Tags are used consistently for test categorization
- All tests pass consistently (no flaky tests)
- Code follows C# naming conventions and style guidelines
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
For questions or issues:
- Review the relevant section in the repository
- Check SpecFlow documentation
- Refer to inline code comments and examples
- Review the Cucumber BDD guide
This project is open source and available for educational purposes.
Happy Testing! 🚀
Last Updated: 2026-05-30 Framework: SpecFlow for .NET Languages: C# & Gherkin