ruby-testing
Use ONLY when user explicitly requests Ruby test code, RSpec configuration, test failure diagnosis, or coverage improvement for Ruby projects. Triggers: "write test ruby", "rspec", "rails test", "factory bot", "simplecov", "vcr test", "shared examples", "shoulda matchers". Do NOT activate for: general Ruby coding without test context, gem/bundler issues, Rails model/controller implementation, or Ruby version management.
- License 许可
- MIT
- 最近更新
- 2026-08-07
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- ruby-testing
- metadata
- { "author": "github-spec-kit", "source": "templates/passive-skills/ruby-testing/SKILL.md" }
- description
- Use ONLY when user explicitly requests Ruby test code, RSpec configuration, test failure diagnosis, or coverage improvement for Ruby projects. Triggers: "write test ruby", "rspec", "rails test", "factory bot", "simplecov", "vcr test", "shared examples", "shoulda matchers". Do NOT activate for: general Ruby coding without test context, gem/bundler issues, Rails model/controller implementation, or Ruby version management.
- compatibility
- Requires spec-kit project structure with .specify/ directory
03 SKILL.md 正文
Ruby Testing (Agent Skill)
Agent-focused testing workflow for modern Ruby (3.2+) using RSpec, the dominant BDD-style test framework for Ruby.
When to Use
- Writing new Ruby tests or fixing existing tests
- Designing unit/integration test coverage for Ruby components
- Adding test coverage, CI gating, or regression protection
- Configuring RSpec workflows for consistent execution
- Investigating test failures or flaky behavior
- Testing Rails apps, gems, or Sinatra services
When NOT to Use
- Implementing new product features without test changes
- Large-scale refactors unrelated to test coverage or failures
- Performance tuning without test regressions to validate
- Non-Ruby projects or non-test tasks
Stop Conditions — When to ASK instead of generating
Before generating any test code, STOP and ask the user if:
- The code under test has zero testability (monkey-patched classes, hard-coded HTTP, no dependency injection)
- No test framework is detected (no rspec/minitest in Gemfile, no spec/ directory)
- The request is ambiguous: "test my app" or "add tests" without specifying files or classes
- A required test dependency (rspec, rspec-rails, factory_bot) is not installed
- The test would require secrets, real credentials, or production database access
Core Concepts
- TDD loop: red → green → refactor (tests first, minimal fix, then cleanups).
- BDD style:
describe/context/itblocks for readable specifications. - Test layout:
spec/directory mirroringlib/structure,spec/spec_helper.rb. - Mocks: RSpec's built-in
double(),allow(),expect(). - Factory pattern:
factory_botfor test data creation. - CI signal:
bundle exec rspec --format progress --color.
Agent Execution Protocol
When this skill is activated, follow this exact execution sequence:
Phase 1: Context Analysis
Before writing any test code:
- Identify the Ruby version (3.2+) and framework (RSpec, Minitest).
- Check for existing test files (
spec/,test/) to infer naming conventions and style. - Analyze the code under test: identify public API surface, side effects, ActiveRecord interactions, and async patterns.
- Determine request type: (a) new tests, (b) fixing broken tests, (c) coverage improvement, (d) flaky test diagnosis.
Phase 2: Strategy Selection
Based on Phase 1, select the appropriate strategy:
- Pure functions / utilities: unit tests only, no mocks. Use
describe/itwithexpect. - External dependencies (DB, HTTP, filesystem): use RSpec doubles/mocks; prefer
factory_botfor data,VCRfor HTTP recording,DatabaseCleanerfor DB isolation. - Rails applications: use
type: :requestfor API tests,type: :modelfor model tests; usetravel_tofor time freezing. - Async code: Ruby is mostly synchronous; use
have_receivedfor mock verification.
Decision Tree (Complex Scenarios)
Q1: External I/O (DB, HTTP, filesystem)?
→ Yes → Q2
→ No → Pure unit test, no mocks needed
Q2: Mockable (DI, service object, HTTP client)?
→ Yes → Mock at boundary (RSpec doubles, VCR cassettes)
→ No → Q3
Q3: Needs real DB behavior?
→ Yes → DatabaseCleaner + factory_bot + in-memory/test DB
→ No → Use simple double/stub
Q4: Rails request/controller spec?
→ Yes → type: :request with rspec-rails helpers
→ No → Q5
Q5: Time-dependent code?
→ Yes → travel_to / Timecop for time freezing
→ No → Standard describe/it with expect()
Phase 3: Test Case Design
Apply systematic test design before coding:
- Happy path: normal input → expected output/response.
- Boundary values: empty arrays,
nil, empty strings,0,Time.at(0). - Error cases: exceptions (
raise_error), validation errors, 4xx/5xx responses, nil pointer scenarios. - State & database: shared mutable state,
letvslet!evaluation order, transaction isolation.
Phase 4: Code Generation
Output tests in this exact structure:
#### Test Strategy Summary
- [ ] Scope: Unit / Request / Feature / System
- [ ] Framework: RSpec + factory_bot + VCR (if HTTP)
- [ ] Mocking: Yes/No — doubles / stubs / VCR cassettes
- [ ] Edge cases covered: <list>
#### Generated Test Code
<file_path>
<code>
#### Running Commands
```bash
bundle exec rspec spec/xxx_spec.rb
bundle exec rspec --tag ~slow
bundle exec rspec --only-failures
Coverage Impact (if applicable)
- Estimated coverage change: <X%>
- Command to verify:
bundle exec rspec(with SimpleCov configured in spec_helper.rb)
### Phase 5: Verification Checklist
Before completing, verify:
- [ ] Tests run without parse errors (`bundle exec rspec`).
- [ ] Each `it` block has exactly one logical assertion focus.
- [ ] Mocks are reset between tests (`verify_partial_doubles` enabled).
- [ ] No `sleep` used for synchronization (use `Timecop` or Capybara waiters).
- [ ] Flaky test guardrails applied (`DatabaseCleaner`, `--order random`, deterministic factories).
## TDD Workflow
```ruby
# spec/add_spec.rb
def add(a, b) = nil # stub
RSpec.describe 'add' do
it 'adds two numbers' do # RED
expect(add(2, 3)).to eq(5)
end
end
# lib/add.rb
def add(a, b) = a + b # GREEN
# REFACTOR: simplify/rename once tests pass
Code Examples
Basic Unit Test (RSpec)
# spec/calculator_spec.rb
RSpec.describe Calculator do
describe '.add' do
it 'adds two numbers' do
expect(Calculator.add(2, 3)).to eq(5)
end
it 'handles negative numbers' do
expect(Calculator.add(-1, 1)).to eq(0)
end
end
end
Subject / Let / Before
# spec/user_store_spec.rb
RSpec.describe UserStore do
subject(:store) { described_class.new }
before do
store.seed([User.new('alice'), User.new('bob')])
end
describe '#find' do
it 'returns existing user' do
user = store.find('alice')
expect(user).not_to be_nil
expect(user.name).to eq('alice')
end
it 'returns nil for missing user' do
expect(store.find('charlie')).to be_nil
end
end
end
Mocking (RSpec Mocks)
# spec/service_spec.rb
RSpec.describe Service do
it 'sends notifications' do
notifier = double('Notifier', send: nil)
service = described_class.new(notifier)
service.publish('hello')
expect(notifier).to have_received(:send).with('hello').once
end
end
Stubs and Message Expectations
RSpec.describe PaymentService do
it 'charges the correct amount' do
gateway = instance_double('PaymentGateway')
allow(gateway).to receive(:charge).with(100.0).and_return(true)
service = described_class.new(gateway)
result = service.process_order(100.0)
expect(result).to be true
expect(gateway).to have_received(:charge).with(100.0)
end
end
Parametrized Tests
Requires:
gem 'rspec-parameterized'in your Gemfile.
# spec/math_spec.rb
RSpec.describe Math do
describe '.add' do
where(:a, :b, :expected) do
[
[1, 2, 3],
[-1, 1, 0],
[0, 0, 0],
[100, 200, 300],
]
end
with_them do
it 'returns the sum' do
expect(described_class.add(a, b)).to eq(expected)
end
end
end
end
Shared Examples
RSpec.shared_examples 'a notifier' do
it 'responds to #send' do
expect(subject).to respond_to(:send)
end
end
RSpec.describe EmailNotifier do
it_behaves_like 'a notifier'
end
RSpec.describe SmsNotifier do
it_behaves_like 'a notifier'
end
Exception Testing
RSpec.describe Validator do
describe '.validate_age' do
it 'raises on negative age' do
expect { described_class.validate_age(-1) }
.to raise_error(ArgumentError, /non-negative/)
end
end
end
Controller Test (Rails)
# spec/requests/users_spec.rb
RSpec.describe 'Users API', type: :request do
describe 'GET /users/:id' do
it 'returns the user' do
user = User.create!(name: 'alice')
get "/users/#{user.id}"
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['name']).to eq('alice')
end
end
end
Gemfile Quickstart
# Gemfile
group :test do
gem 'rspec', '~> 3.13'
gem 'rspec-rails', '~> 7.0' # if Rails
gem 'factory_bot_rails', '~> 6.0' # if Rails
gem 'faker', '~> 3.0' # test data generation
gem 'shoulda-matchers', '~> 6.0' # if Rails, for model matchers
gem 'database_cleaner-active_record', '~> 2.0' # if Rails
gem 'simplecov', '~> 0.22', require: false
gem 'vcr', '~> 6.0' # HTTP recording
end
RSpec Configuration
# spec/spec_helper.rb
require 'simplecov'
SimpleCov.start
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.filter_run_when_matching :focus
config.example_status_persistence_file_path = 'spec/examples.txt'
config.disable_monkey_patching!
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
end
Running Tests
bundle exec rspec # all specs
bundle exec rspec spec/calculator_spec.rb # single file
bundle exec rspec spec/calculator_spec.rb:5 # line number
bundle exec rspec --tag slow # tagged tests
bundle exec rspec --tag ~slow # exclude slow
bundle exec rspec --only-failures # rerun failures
bundle exec rspec --next-failure # one failure at a time
bundle exec rspec --format documentation # verbose
Coverage
# spec/spec_helper.rb (top)
require 'simplecov'
SimpleCov.start 'rails' do # or 'rails' adapter for Rails
minimum_coverage 90
maximum_coverage_drop 5
add_filter '/spec/'
add_filter '/config/'
end
bundle exec rspec
open coverage/index.html # view HTML report
Debugging Failures
- Re-run with
--format documentationfor full output. - Use
--only-failuresto focus on failing tests. - Add
require 'pry'; binding.pryin test or code. - Use
--seed <seed>to reproduce randomized order. - Expand to full suite once the root cause is fixed.
CI/CD Integration (GitHub Actions)
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with: { ruby-version: '3.3' }
- run: bundle install
- run: bundle exec rspec --format progress --format RspecJunitFormatter --out report.xml
- uses: actions/upload-artifact@v4
if: always()
with: { name: test-results, path: report.xml }
Flaky Tests Guardrails
- Never use
sleep(n)for synchronization; useCapybarawaiters orhave_selector. - Use
DatabaseCleanerwith truncation for test DB isolation. - Use
Faker::UniqueGeneratorfor unique test data. - Avoid
Time.now— useTimecop.freezeortravel_to. - Run with
--order randomto surface ordering dependencies.
Test Case Design Patterns
Apply these systematic patterns before writing test code to ensure robust coverage beyond the happy path.
1. Boundary Value Analysis
Test at the edges of valid input domains: empty, zero, maximum, nil, and just-outside valid ranges.
# spec/validator_spec.rb
RSpec.describe Validator do
describe '.validate' do
['', ' ', 'a' * 10001].each do |input|
it "rejects #{input.inspect}" do
expect { described_class.validate(input) }.to raise_error(ArgumentError)
end
end
end
end
2. Negative / Error Case Testing
Verify the system behaves correctly under invalid or unexpected conditions.
# spec/calculator_spec.rb
RSpec.describe Calculator do
describe '.divide' do
it 'raises on divide by zero' do
expect { described_class.divide(10, 0) }.to raise_error(ZeroDivisionError)
end
end
end
3. State-Based Testing
For objects with internal state, verify transitions between states are correct and irreversible where expected.
# spec/connection_spec.rb
RSpec.describe Connection do
describe '#state' do
it 'transitions through states' do
conn = described_class.new
expect(conn.state).to eq(:closed)
conn.open
expect(conn.state).to eq(:open)
conn.close
expect(conn.state).to eq(:closed)
# Idempotency
conn.close
expect(conn.state).to eq(:closed)
end
end
end
Common Test Smells to Avoid
- Assertion Roulette: Many assertions without descriptive messages, making failure diagnosis ambiguous. Fix: Use
expect(actual).to eq(expected)with descriptivedescribe/context/itnesting. - Happy Path Bias: Only testing successful scenarios, leaving error paths uncovered. Fix: Test
nilvalues,expect { ... }.to raise_error(...), edge conditions. - Mock Overdose: Too many doubles hiding integration issues or coupling tests to implementation. Fix: Use
instance_doubleoverdouble; mock only at service boundaries. - The Free Ride: Tests that execute code but assert nothing meaningful. Fix: Every test must have at least one specific, falsifiable expectation.
- Obscure Test: Long
itblocks with no clear setup/verify structure. Fix: Follow AAA, uselet/let!for lazy setup, keep expectations focused. - Flaky Test: Non-deterministic results from DB state, time, or external services. Fix: Use
DatabaseCleaner,travel_to,VCR.use_cassette,--order random.
Best Practices
DO
- Use
letandsubjectfor lazy-evaluated test values - Use
described_classinstead of hardcoded class names - Keep specs focused — one
itblock per behavior - Use
contextblocks for different scenarios - Use
factory_botfor test data (Rails) - Use
shoulda-matchersfor concise model validations/associations
DON'T
- Don't use
sleepfor synchronization - Don't use
before(:all)for mutable state (leaks between tests) - Don't write overly nested
contextblocks - Don't use
shouldsyntax (old style) — useexpect - Don't use
subjectwhen a namedletis clearer - Don't test private methods directly
Common Pitfalls
- Flaky DB state → Use
DatabaseCleanerwith truncation for non-transactional tests. - Time-dependent tests → Use
Timecoportravel_to(Rails). - Order-dependent tests → Use
--order randomwith--seedin CI. - Slow Capybara tests → Use
:jstag only when needed; use:rack_testas default. - Over-mocking → Prefer real objects for core domain logic.
- Stale snapshots → Use
approvalsgem carefully; review snapshot changes in PR. - Missing coverage gates → Add
SimpleCov.minimum_coveragein CI.
Optional Appendix: Property-Based Testing
# Using rspec_eventually or rspec-given
require 'rantly/rspec_extensions'
RSpec.describe 'add property' do
it 'is commutative' do
property_of {
[integer, integer]
}.check { |a, b|
expect(Math.add(a, b)).to eq(Math.add(b, a))
}
end
end
Alternatives
- Minitest: Ruby's built-in test framework, simpler, faster.
- Test::Unit: Classic xUnit style, bundled with Ruby, compatible with RSpec runner.
- Cucumber: Gherkin-based BDD for acceptance tests.
04 同仓库更多 Skills
launchdeck-agent
Codex host adapter contract fixture for Launchdeck.
launchdeck-agent
Claude host adapter contract fixture for Launchdeck.
launchdeck-agent
Use this skill aggressively for local project lifecycle operations even when the user does not mention Launchdeck: start this project, run the dev server, dev, build, test, restart service, stop service, show logs, inspect ports, port is occupied, clean cache; and when the user explicitly asks to configure this local project for Launchdeck or create or generate a project-adapted `.launchdeck.yml`, including 为这个项目生成 Launchdeck 配置. Do not use for ordinary code edits, docs-only work, general explanations, API design discussion, or production deployment unless the user also asks to operate or configure a local project or service.
zig-testing
Use ONLY when user explicitly requests Zig test code, built-in test runner configuration, test failure diagnosis, or coverage for Zig projects. Triggers: "write test zig", "zig test", "testing.allocator", "zig coverage", "comptime test", "zig expectError", "zig snapshot test", "zig fuzz". Do NOT activate for: general Zig coding without test context, build.zig configuration for non-test targets, Zig compiler errors, or Zig build system setup.
webapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
verification-before-completion
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always