Testing Patterns
Effective testing strategies and common pitfalls to avoid.
Happy Path First
Start with Success
Always begin with the expected success case:
gherkin
Scenario: Successful purchase
Given a customer with valid payment
When they purchase a product
Then the order should be confirmedThen Add Edge Cases
gherkin
Scenario: Purchase with insufficient funds
Given a customer with insufficient funds
When they try to purchase
Then purchase should fail
And customer should see error message
Scenario: Purchase with invalid card
Given a customer with expired card
When they try to purchase
Then purchase should fail
And customer should be asked to update paymentBoundary Testing
Test Limits
gherkin
Scenario Outline: Password validation
When I enter password "<password>"
Then validation should <result>
Examples: Length boundaries
| password | result |
| 1234567 | fail | # Below minimum (7)
| 12345678 | pass | # At minimum (8)
| 123456789 | pass | # Above minimum (9)Test Transitions
gherkin
Scenario Outline: Discount tiers
Given a customer with <orders> previous orders
Then they should be in <tier> tier
Examples:
| orders | tier |
| 0 | bronze |
| 4 | bronze | # Just below silver
| 5 | silver | # Exactly at silver
| 6 | silver | # Just above threshold
| 19 | silver | # Just below gold
| 20 | gold | # Exactly at goldCommon Pitfalls
Avoid Implementation Details
gherkin
# Avoid: Exposes implementation
Given a record in users table with id=1
When I send POST to /api/orders
# Prefer: Business language
Given a user exists
When the user places an orderDon't Over-Specify
gherkin
# Avoid: Too specific
Given a user "John" with email "john@test.com" and role "user"
and created at "2024-01-01" and verified at "2024-01-02"
# Prefer: Only what matters
Given a verified user existsDon't Test Implementation
gherkin
# Avoid: Testing how it works
Given UserService is configured
When UserService.createUser() is called
Then database insert should occur
# Prefer: Testing what it does
Given I am on registration page
When I register with valid details
Then I should be logged inAvoid Conditional Logic in Steps
php
// Avoid: Conditional in step
#[When('the user {action} the item')]
public function userAction(string $action, User $user, Item $item): void
{
if ($action === 'buys') {
$user->buy($item);
} elseif ($action === 'returns') {
$user->return($item);
}
}
// Prefer: Separate steps
#[When('the user buys the item')]
public function buyItem(User $user, Item $item): void
{
$user->buy($item);
}
#[When('the user returns the item')]
public function returnItem(User $user, Item $item): void
{
$user->return($item);
}State Management
Don't Share State Between Scenarios
php
// Avoid: Class-level state
class CartSteps
{
private static Cart $cart; // Shared across scenarios!
#[Given('an empty cart')]
public function emptyCart(): void
{
self::$cart = new Cart();
}
}
// Prefer: Return for injection
class CartSteps
{
#[Given('an empty cart')]
public function emptyCart(): Cart
{
return new Cart();
}
}Clean Up After Scenarios
php
#[Given('a temporary file exists')]
public function tempFile(): string
{
$path = storage_path('temp/test-file.txt');
file_put_contents($path, 'test');
// Register cleanup
afterEach(fn () => @unlink($path));
return $path;
}Error Scenarios
Test Error Messages
gherkin
Scenario: Login with wrong password
Given a user with email "john@test.com"
When I login with email "john@test.com" and password "wrong"
Then I should see error "Invalid credentials"
And I should not be logged inTest Error Recovery
gherkin
Scenario: Retry after payment failure
Given a customer with temporarily unavailable payment
When they try to purchase
Then they should see "Payment failed, please try again"
When they try to purchase again
Then the order should be confirmedDocumentation in Tests
Self-Documenting Scenarios
gherkin
Feature: Credit Application Processing
Agricultural credit applications are evaluated based on
the farmer's land holdings. The minimum land requirement
is 5 hectares per 10,000 TL of requested credit.
Scenario: Application approved with sufficient land
# A farmer with 50 hectares can apply for up to 100,000 TL
Given a farmer with 50 hectares of land
When they apply for 100000 TL credit
Then the application should be approvedDocument Complex Steps
php
/**
* Calculates eligibility based on land-to-credit ratio.
*
* Business rule: 5 hectares per 10,000 TL
* Example: 50 hectares → eligible for 100,000 TL
*/
#[When('the farmer applies for {amount} TL credit')]
public function apply(Farmer $farmer, int $amount): CreditApplication
{
return CreditService::evaluate($farmer, $amount);
}Anti-Pattern Summary
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Implementation details | Brittle tests, hard to read | Use business language |
| Over-specification | Unnecessary complexity | Only include relevant data |
| Dependent scenarios | Can't run in isolation | Each scenario is independent |
| Shared mutable state | Race conditions, flaky tests | Return objects for injection |
| Giant scenarios | Hard to debug, unclear intent | One behavior per scenario |
| Conditional steps | Complex, hard to maintain | Separate specific steps |