🔧 chore: fix skill naming and gitea actions compatibility (related to #2)
Some checks failed
CI/CD Pipeline / CI Pipeline (push) Failing after 7m12s
Some checks failed
CI/CD Pipeline / CI Pipeline (push) Failing after 7m12s
This commit is contained in:
398
.vibe/skills/bdd-testing/SKILL.md
Normal file
398
.vibe/skills/bdd-testing/SKILL.md
Normal file
@@ -0,0 +1,398 @@
|
||||
---
|
||||
name: bdd-testing
|
||||
description: Behavior-Driven Development testing for DanceLessonsCoach using Godog. Use when creating or running BDD tests, implementing new features with BDD, or validating API endpoints through Gherkin scenarios.
|
||||
license: MIT
|
||||
metadata:
|
||||
author: DanceLessonsCoach Team
|
||||
version: "1.0.0"
|
||||
based-on: pkg/bdd implementation
|
||||
---
|
||||
|
||||
# BDD Testing for DanceLessonsCoach
|
||||
|
||||
Behavior-Driven Development testing framework using Godog for the DanceLessonsCoach project. This skill provides comprehensive guidance for creating, running, and maintaining BDD tests that validate API endpoints and system behavior.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Black Box Testing Principles
|
||||
- **External API Only**: Tests interact only through public HTTP endpoints
|
||||
- **No Internal Access**: No direct access to database, services, or internal components
|
||||
- **Real HTTP Requests**: Actual network calls to verify system behavior
|
||||
- **Isolation**: Each scenario runs with fresh client instances
|
||||
|
||||
### Hybrid In-Process Testing
|
||||
- **Real Server Code**: Uses actual server implementation running in-test process
|
||||
- **Fixed Port**: Test server runs on port 9191
|
||||
- **No External Processes**: Avoids complex process management
|
||||
- **Graceful Shutdown**: Proper server lifecycle management
|
||||
|
||||
## Commands
|
||||
|
||||
### Run BDD Tests
|
||||
|
||||
```bash
|
||||
go test ./features/...
|
||||
```
|
||||
|
||||
Runs all BDD tests in the features directory using Godog test runner.
|
||||
|
||||
**Arguments:**
|
||||
- None (uses standard Go test infrastructure)
|
||||
|
||||
### Validate BDD Tests
|
||||
|
||||
```bash
|
||||
./scripts/run-bdd-tests.sh
|
||||
```
|
||||
|
||||
Validates BDD tests and fails if any undefined, pending, or skipped steps are found.
|
||||
|
||||
**Arguments:**
|
||||
- None
|
||||
|
||||
### Create New Feature
|
||||
|
||||
```bash
|
||||
# Create new feature file
|
||||
touch features/<feature_name>.feature
|
||||
|
||||
# Add Gherkin scenarios
|
||||
# Implement step definitions in pkg/bdd/steps/
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
- `feature_name` - Name of the feature (e.g., "greet", "health")
|
||||
|
||||
## Workflows
|
||||
|
||||
### Implementing a New BDD Feature
|
||||
|
||||
1. **Create Feature File**: Define scenarios in Gherkin syntax
|
||||
2. **Implement Steps**: Add step definitions following Godog's exact patterns
|
||||
3. **Run Tests**: Execute and debug scenarios
|
||||
4. **Validate**: Ensure no undefined/pending steps
|
||||
5. **Document**: Add feature documentation
|
||||
|
||||
### Debugging BDD Tests
|
||||
|
||||
1. **Check Step Patterns**: Ensure steps match Godog's exact regex patterns
|
||||
2. **Verify Server**: Confirm test server is running on port 9191
|
||||
3. **Inspect Responses**: Check actual vs expected API responses
|
||||
4. **Review Logs**: Examine test output for undefined steps
|
||||
5. **Validate JSON**: Ensure proper JSON escaping in feature files
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Creating a Greet Feature
|
||||
|
||||
```gherkin
|
||||
# features/greet.feature
|
||||
Feature: Greet Service
|
||||
The greet service should return appropriate greetings
|
||||
|
||||
Scenario: Default greeting
|
||||
Given the server is running
|
||||
When I request the default greeting
|
||||
Then the response should be "{\"message\":\"Hello world!\"}"
|
||||
|
||||
Scenario: Personalized greeting
|
||||
Given the server is running
|
||||
When I request a greeting for "John"
|
||||
Then the response should be "{\"message\":\"Hello John!\"}"
|
||||
```
|
||||
|
||||
### Creating a Health Feature
|
||||
|
||||
```gherkin
|
||||
# features/health.feature
|
||||
Feature: Health Endpoint
|
||||
The health endpoint should indicate server status
|
||||
|
||||
Scenario: Health check returns healthy status
|
||||
Given the server is running
|
||||
When I request the health endpoint
|
||||
Then the response should be "{\"status\":\"healthy\"}"
|
||||
```
|
||||
|
||||
### Implementing Step Definitions
|
||||
|
||||
```go
|
||||
// pkg/bdd/steps/steps.go
|
||||
func (sc *StepContext) theServerIsRunning() error {
|
||||
// Actually verify the server is running by checking the readiness endpoint
|
||||
return sc.client.Request("GET", "/api/ready", nil)
|
||||
}
|
||||
|
||||
func (sc *StepContext) iRequestAGreetingFor(name string) error {
|
||||
return sc.client.Request("GET", fmt.Sprintf("/api/v1/greet/%s", name), nil)
|
||||
}
|
||||
|
||||
func (sc *StepContext) iRequestTheDefaultGreeting() error {
|
||||
return sc.client.Request("GET", "/api/v1/greet/", nil)
|
||||
}
|
||||
|
||||
func (sc *StepContext) iRequestTheHealthEndpoint() error {
|
||||
return sc.client.Request("GET", "/api/health", nil)
|
||||
}
|
||||
|
||||
func (sc *StepContext) theResponseShouldBe(arg1, arg2 string) error {
|
||||
// The regex captures the full JSON from the feature file, including quotes
|
||||
// We need to extract just the key and value without the surrounding quotes and backslashes
|
||||
|
||||
// Remove the surrounding quotes and backslashes
|
||||
cleanArg1 := strings.Trim(arg1, `"\`)
|
||||
cleanArg2 := strings.Trim(arg2, `"\`)
|
||||
|
||||
// Build the expected JSON string
|
||||
expected := fmt.Sprintf(`{"%s":"%s"}`, cleanArg1, cleanArg2)
|
||||
|
||||
return sc.client.ExpectResponseBody(expected)
|
||||
}
|
||||
```
|
||||
|
||||
### Registering Steps
|
||||
|
||||
```go
|
||||
// pkg/bdd/steps/steps.go
|
||||
func InitializeAllSteps(ctx *godog.ScenarioContext, client *testserver.Client) {
|
||||
sc := NewStepContext(client)
|
||||
|
||||
// Use Godog's EXACT regex patterns and parameter names
|
||||
ctx.Step(`^I request a greeting for "([^"]*)"$`, sc.iRequestAGreetingFor)
|
||||
ctx.Step(`^I request the default greeting$`, sc.iRequestTheDefaultGreeting)
|
||||
ctx.Step(`^I request the health endpoint$`, sc.iRequestTheHealthEndpoint)
|
||||
ctx.Step(`^the response should be "{\"([^"]*)\":\"([^"]*)"}"$`, sc.theResponseShouldBe)
|
||||
ctx.Step(`^the server is running$`, sc.theServerIsRunning)
|
||||
}
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
### Step Pattern Matching
|
||||
- **Use Godog's Exact Patterns**: Step regex must match Godog's suggestions precisely
|
||||
- **Use Exact Parameter Names**: Godog expects `arg1, arg2`, not descriptive names
|
||||
- **Avoid Undefined Warnings**: Even small deviations cause "undefined step" warnings
|
||||
- **Test Patterns First**: Use `godog.ErrPending` to verify patterns work before implementing logic
|
||||
- **Don't Over-Optimize Regex**: Use the patterns Godog provides, even if they seem verbose
|
||||
|
||||
### Critical Requirements from Validated Implementation
|
||||
|
||||
1. **Godog has very specific requirements** for step pattern matching:
|
||||
- Use the **exact regex pattern** that Godog suggests in error messages
|
||||
- Use the **exact parameter names** that Godog suggests (`arg1, arg2`, etc.)
|
||||
- Match the feature file syntax **exactly** including quotes and JSON formatting
|
||||
|
||||
2. **The "undefined" warnings are not a Godog bug** - they occur when step definitions don't match Godog's expected patterns exactly:
|
||||
- Using different regex patterns than what Godog suggests
|
||||
- Using descriptive parameter names instead of `arg1, arg2`
|
||||
- Not escaping quotes properly in JSON patterns
|
||||
- Trying to be "clever" with regex optimization
|
||||
|
||||
3. **Solution**: Always use the exact pattern and parameter names that Godog suggests in its error messages.
|
||||
|
||||
### JSON Escaping
|
||||
- **Feature Files**: Use double backslashes for quotes: `"{\\"message\\":\\"Hello\\"}"`
|
||||
- **Step Implementation**: Trim surrounding quotes and backslashes from captured groups
|
||||
- **Response Validation**: Trim trailing newlines from JSON responses
|
||||
|
||||
### Server Verification
|
||||
- **Actual HTTP Requests**: `theServerIsRunning` must make real HTTP call to `/api/ready`
|
||||
- **No Mocking**: Black box testing requires real server verification
|
||||
- **Port Conflicts**: Test server runs on fixed port 9191
|
||||
|
||||
### Context Handling
|
||||
- **ScenarioContext vs Context**: Steps receive `*godog.ScenarioContext`, not `context.Context`
|
||||
- **Client Access**: Store client in StepContext struct for step access
|
||||
- **Fresh Instances**: Each scenario gets new client instance
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Step Definition Patterns
|
||||
|
||||
```go
|
||||
// ✅ DO: Use Godog's exact regex patterns and parameter names
|
||||
ctx.Step(`^I request a greeting for "([^"]*)"$`, sc.iRequestAGreetingFor)
|
||||
ctx.Step(`^the response should be "{\"([^"]*)\":\"([^"]*)"}"$`, sc.theResponseShouldBe)
|
||||
|
||||
// ❌ DON'T: Use different parameter names or patterns
|
||||
ctx.Step(`^I request greeting "(.*)"$`, sc.iRequestAGreetingFor) // Wrong pattern
|
||||
ctx.Step(`^the response should be "{\"message\":\"([^"]*)"}"$`, sc.theResponseShouldBe) // Wrong pattern
|
||||
```
|
||||
|
||||
### Validated Step Definition Strategy
|
||||
|
||||
1. **First eliminate "undefined" warnings** by using Godog's exact suggested patterns
|
||||
2. **Return `godog.ErrPending`** initially to confirm pattern matching works
|
||||
3. **Then implement actual validation** logic
|
||||
4. **One pattern per step type** - Use generic patterns to cover similar steps
|
||||
|
||||
### Response Validation
|
||||
|
||||
```go
|
||||
// ✅ DO: Trim newlines and properly unescape JSON
|
||||
func (c *Client) ExpectResponseBody(expected string) error {
|
||||
actual := strings.TrimSuffix(string(c.lastBody), "\n")
|
||||
if actual != expected {
|
||||
return fmt.Errorf("expected %q, got %q", expected, actual)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ❌ DON'T: Assume exact string matching without cleanup
|
||||
func (c *Client) ExpectResponseBody(expected string) error {
|
||||
if string(c.lastBody) != expected { // May fail due to newlines
|
||||
return fmt.Errorf("mismatch")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Server Management
|
||||
|
||||
```go
|
||||
// ✅ DO: Use hybrid in-process testing
|
||||
func (s *Server) Start() error {
|
||||
// Start real server in same process
|
||||
go func() {
|
||||
if err := s.httpServer.ListenAndServe(); err != nil {
|
||||
log.Error().Err(err).Msg("Test server failed")
|
||||
}
|
||||
}()
|
||||
return s.waitForServerReady()
|
||||
}
|
||||
|
||||
// ❌ DON'T: Use external process management
|
||||
func startServer() {
|
||||
// Avoid: cmd := exec.Command("go", "run", "./cmd/server")
|
||||
}
|
||||
```
|
||||
|
||||
### Response Validation
|
||||
|
||||
```go
|
||||
// ✅ DO: Trim newlines and properly unescape JSON
|
||||
func (c *Client) ExpectResponseBody(expected string) error {
|
||||
actual := strings.TrimSuffix(string(c.lastBody), "\n")
|
||||
if actual != expected {
|
||||
return fmt.Errorf("expected %q, got %q", expected, actual)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ❌ DON'T: Assume exact string matching without cleanup
|
||||
func (c *Client) ExpectResponseBody(expected string) error {
|
||||
if string(c.lastBody) != expected { // May fail due to newlines
|
||||
return fmt.Errorf("mismatch")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Progressive Disclosure
|
||||
|
||||
### Core Instructions (SKILL.md)
|
||||
- BDD testing fundamentals
|
||||
- Common workflows and patterns
|
||||
- Gotchas and best practices
|
||||
- Basic troubleshooting
|
||||
|
||||
### Detailed Reference (references/)
|
||||
- **GODOG_PATTERNS.md**: Advanced step pattern examples
|
||||
- **TEST_SERVER.md**: Test server implementation details
|
||||
- **DEBUGGING.md**: Advanced debugging techniques
|
||||
- **EXAMPLES.md**: Complete feature examples
|
||||
|
||||
### Scripts (scripts/)
|
||||
- **run-bdd-tests.sh**: Test validation and execution
|
||||
- **debug-steps.sh**: Step pattern debugging
|
||||
- **generate-stubs.sh**: Step definition stub generation
|
||||
|
||||
## Validation
|
||||
|
||||
### Test Validation Script
|
||||
|
||||
```bash
|
||||
# scripts/run-bdd-tests.sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Running BDD tests..."
|
||||
go test ./features/... -v
|
||||
|
||||
# Fail if any undefined/pending/skipped steps
|
||||
echo "Validating test results..."
|
||||
if go test ./features/... 2>&1 | grep -q "undefined\|pending\|skipped"; then
|
||||
echo "ERROR: Found undefined, pending, or skipped steps"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ All BDD tests passed with no undefined steps"
|
||||
```
|
||||
|
||||
### Common Validation Issues
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| Undefined steps | Step pattern doesn't match Godog's exact regex | Use Godog's suggested pattern |
|
||||
| JSON mismatch | Trailing newlines or improper escaping | Trim newlines, properly unescape JSON |
|
||||
| Server not running | Test server failed to start | Check port 9191, verify server logs |
|
||||
| Context errors | Wrong context type passed to steps | Use `*godog.ScenarioContext`, not `context.Context` |
|
||||
|
||||
## References
|
||||
|
||||
- [Godog Documentation](https://github.com/cucumber/godog)
|
||||
- [Gherkin Reference](https://cucumber.io/docs/gherkin/)
|
||||
- [BDD Best Practices](references/BDD_BEST_PRACTICES.md)
|
||||
- [Test Server Implementation](references/TEST_SERVER.md)
|
||||
- [Debugging Guide](references/DEBUGGING.md)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Undefined Step" Warnings
|
||||
|
||||
**Symptoms:** Tests pass but show "undefined step" warnings
|
||||
|
||||
**Cause:** Step regex doesn't match Godog's exact pattern suggestions
|
||||
|
||||
**Solution:**
|
||||
1. Run `godog --format=progress` to see suggested patterns
|
||||
2. Update step registration to use exact patterns
|
||||
3. Ensure function names match step descriptions
|
||||
|
||||
### JSON Comparison Failures
|
||||
|
||||
**Symptoms:** Response validation fails despite correct JSON
|
||||
|
||||
**Cause:** Trailing newlines or improper escaping in feature files
|
||||
|
||||
**Solution:**
|
||||
1. Trim newlines: `strings.TrimSuffix(response, "\n")`
|
||||
2. Properly escape JSON in feature files: `"{\\"key\\":\\"value\\"}"`
|
||||
3. Trim quotes in step implementation: `strings.Trim(arg, `"\`)`
|
||||
|
||||
### Server Connection Errors
|
||||
|
||||
**Symptoms:** "connection refused" or server not responding
|
||||
|
||||
**Cause:** Test server not running or port conflict
|
||||
|
||||
**Solution:**
|
||||
1. Verify server on port 9191: `curl http://localhost:9191/api/ready`
|
||||
2. Check server logs for startup errors
|
||||
3. Ensure no other process using port 9191
|
||||
|
||||
### Context Type Mismatches
|
||||
|
||||
**Symptoms:** Compilation errors about context types
|
||||
|
||||
**Cause:** Passing wrong context type to step functions
|
||||
|
||||
**Solution:**
|
||||
1. Store `*godog.ScenarioContext` in StepContext
|
||||
2. Use stored context for step registration
|
||||
3. Access client through StepContext struct
|
||||
|
||||
## Assets
|
||||
|
||||
- **feature-template.feature**: Gherkin feature file template
|
||||
- **step-template.go**: Go step definition template
|
||||
- **test-server-template.go**: Test server implementation template
|
||||
- **validation-script.sh**: Test validation script template
|
||||
Reference in New Issue
Block a user