aidp 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +89 -0
- data/lib/aidp/cli/models_command.rb +5 -6
- data/lib/aidp/cli.rb +10 -8
- data/lib/aidp/config.rb +54 -0
- data/lib/aidp/debug_mixin.rb +23 -1
- data/lib/aidp/execute/agent_signal_parser.rb +22 -0
- data/lib/aidp/execute/repl_macros.rb +2 -2
- data/lib/aidp/execute/steps.rb +94 -1
- data/lib/aidp/execute/work_loop_runner.rb +209 -17
- data/lib/aidp/execute/workflow_selector.rb +2 -25
- data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
- data/lib/aidp/harness/ai_decision_engine.rb +35 -2
- data/lib/aidp/harness/config_manager.rb +0 -5
- data/lib/aidp/harness/config_schema.rb +8 -0
- data/lib/aidp/harness/configuration.rb +27 -19
- data/lib/aidp/harness/enhanced_runner.rb +1 -4
- data/lib/aidp/harness/error_handler.rb +1 -72
- data/lib/aidp/harness/provider_factory.rb +11 -2
- data/lib/aidp/harness/state_manager.rb +0 -7
- data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
- data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
- data/lib/aidp/harness/ui/progress_display.rb +6 -2
- data/lib/aidp/harness/user_interface.rb +0 -58
- data/lib/aidp/init/runner.rb +7 -2
- data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
- data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
- data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
- data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
- data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
- data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
- data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
- data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
- data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
- data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
- data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
- data/lib/aidp/planning/parsers/document_parser.rb +141 -0
- data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
- data/lib/aidp/provider_manager.rb +8 -32
- data/lib/aidp/providers/aider.rb +264 -0
- data/lib/aidp/providers/anthropic.rb +74 -2
- data/lib/aidp/providers/base.rb +25 -1
- data/lib/aidp/providers/codex.rb +26 -3
- data/lib/aidp/providers/cursor.rb +16 -0
- data/lib/aidp/providers/gemini.rb +13 -0
- data/lib/aidp/providers/github_copilot.rb +17 -0
- data/lib/aidp/providers/kilocode.rb +11 -0
- data/lib/aidp/providers/opencode.rb +11 -0
- data/lib/aidp/setup/wizard.rb +249 -39
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +211 -30
- data/lib/aidp/watch/change_request_processor.rb +128 -14
- data/lib/aidp/watch/ci_fix_processor.rb +103 -37
- data/lib/aidp/watch/ci_log_extractor.rb +258 -0
- data/lib/aidp/watch/github_state_extractor.rb +177 -0
- data/lib/aidp/watch/implementation_verifier.rb +284 -0
- data/lib/aidp/watch/plan_generator.rb +7 -43
- data/lib/aidp/watch/plan_processor.rb +7 -6
- data/lib/aidp/watch/repository_client.rb +245 -17
- data/lib/aidp/watch/review_processor.rb +98 -17
- data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
- data/lib/aidp/watch/runner.rb +181 -29
- data/lib/aidp/watch/state_store.rb +22 -1
- data/lib/aidp/workflows/definitions.rb +147 -0
- data/lib/aidp/workstream_cleanup.rb +245 -0
- data/lib/aidp/worktree.rb +19 -0
- data/templates/aidp.yml.example +57 -0
- data/templates/implementation/generate_tdd_specs.md +213 -0
- data/templates/implementation/iterative_implementation.md +122 -0
- data/templates/planning/agile/analyze_feedback.md +183 -0
- data/templates/planning/agile/generate_iteration_plan.md +179 -0
- data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
- data/templates/planning/agile/generate_marketing_report.md +162 -0
- data/templates/planning/agile/generate_mvp_scope.md +127 -0
- data/templates/planning/agile/generate_user_test_plan.md +143 -0
- data/templates/planning/agile/ingest_feedback.md +174 -0
- data/templates/planning/assemble_project_plan.md +113 -0
- data/templates/planning/assign_personas.md +108 -0
- data/templates/planning/create_tasks.md +52 -6
- data/templates/planning/generate_gantt.md +86 -0
- data/templates/planning/generate_wbs.md +85 -0
- data/templates/planning/initialize_planning_mode.md +70 -0
- data/templates/skills/README.md +2 -2
- data/templates/skills/marketing_strategist/SKILL.md +279 -0
- data/templates/skills/product_manager/SKILL.md +177 -0
- data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
- data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
- data/templates/skills/ux_researcher/SKILL.md +222 -0
- metadata +39 -1
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: ruby_rspec_tdd
|
|
3
|
+
name: Ruby RSpec TDD Implementer
|
|
4
|
+
description: Expert in Test-Driven Development using Ruby and RSpec framework
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
expertise:
|
|
7
|
+
- RSpec test framework and DSL
|
|
8
|
+
- Ruby test patterns and idioms
|
|
9
|
+
- FactoryBot and fixture management
|
|
10
|
+
- Test file organization and naming conventions
|
|
11
|
+
- RSpec matchers and expectations
|
|
12
|
+
- Test doubles and mocking in RSpec
|
|
13
|
+
keywords:
|
|
14
|
+
- rspec
|
|
15
|
+
- ruby
|
|
16
|
+
- tdd
|
|
17
|
+
- testing
|
|
18
|
+
- red-green-refactor
|
|
19
|
+
- factorybot
|
|
20
|
+
when_to_use:
|
|
21
|
+
- Implementing TDD tests in Ruby/RSpec projects
|
|
22
|
+
- Generating RSpec test skeletons and fixtures
|
|
23
|
+
- Applying RSpec best practices
|
|
24
|
+
- Structuring Ruby test suites
|
|
25
|
+
when_not_to_use:
|
|
26
|
+
- Non-Ruby projects (use appropriate language skill)
|
|
27
|
+
- Test analysis (use test_analyzer skill)
|
|
28
|
+
- Non-TDD testing approaches
|
|
29
|
+
compatible_providers:
|
|
30
|
+
- anthropic
|
|
31
|
+
- openai
|
|
32
|
+
- cursor
|
|
33
|
+
- codex
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# Ruby RSpec TDD Implementer
|
|
37
|
+
|
|
38
|
+
You are an expert in **Test-Driven Development using Ruby and RSpec**. Your role is to generate test specifications and skeleton test files following TDD principles with RSpec conventions.
|
|
39
|
+
|
|
40
|
+
## RSpec File Organization
|
|
41
|
+
|
|
42
|
+
### Directory Structure
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
spec/
|
|
46
|
+
├── spec_helper.rb # RSpec configuration
|
|
47
|
+
├── unit/ # Unit tests
|
|
48
|
+
│ └── feature_name_spec.rb
|
|
49
|
+
├── integration/ # Integration tests
|
|
50
|
+
│ └── component_integration_spec.rb
|
|
51
|
+
├── acceptance/ # Acceptance tests
|
|
52
|
+
│ └── user_story_spec.rb
|
|
53
|
+
├── fixtures/ # Test data
|
|
54
|
+
│ └── sample_data.rb
|
|
55
|
+
└── factories/ # FactoryBot factories
|
|
56
|
+
└── model_factory.rb
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### File Naming Convention
|
|
60
|
+
|
|
61
|
+
- Test files end with `_spec.rb`
|
|
62
|
+
- Located in `spec/` directory
|
|
63
|
+
- Mirror source file structure: `lib/foo/bar.rb` → `spec/foo/bar_spec.rb`
|
|
64
|
+
|
|
65
|
+
## RSpec Test Structure
|
|
66
|
+
|
|
67
|
+
### Basic Test Skeleton
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# frozen_string_literal: true
|
|
71
|
+
|
|
72
|
+
require "spec_helper"
|
|
73
|
+
require_relative "../../lib/your_module/feature_name"
|
|
74
|
+
|
|
75
|
+
RSpec.describe YourModule::FeatureName do
|
|
76
|
+
describe "#method_name" do
|
|
77
|
+
context "with valid input" do
|
|
78
|
+
it "returns expected output" do
|
|
79
|
+
# GIVEN: Setup test data
|
|
80
|
+
input = valid_test_data
|
|
81
|
+
|
|
82
|
+
# WHEN: Execute behavior
|
|
83
|
+
result = subject.method_name(input)
|
|
84
|
+
|
|
85
|
+
# THEN: Verify expectations
|
|
86
|
+
expect(result).to eq(expected_output)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context "with invalid input" do
|
|
91
|
+
it "raises appropriate error" do
|
|
92
|
+
expect {
|
|
93
|
+
subject.method_name(invalid_data)
|
|
94
|
+
}.to raise_error(ValidationError)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context "with edge cases" do
|
|
99
|
+
it "handles nil gracefully" do
|
|
100
|
+
expect {
|
|
101
|
+
subject.method_name(nil)
|
|
102
|
+
}.to raise_error(ArgumentError)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "handles empty input" do
|
|
106
|
+
result = subject.method_name({})
|
|
107
|
+
expect(result).to be_nil
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## RSpec DSL Patterns
|
|
115
|
+
|
|
116
|
+
### Describe and Context Blocks
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
RSpec.describe Calculator do
|
|
120
|
+
describe "#add" do # Method being tested
|
|
121
|
+
context "with positive numbers" do # Specific scenario
|
|
122
|
+
it "returns sum" do # Expected behavior
|
|
123
|
+
# test implementation
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
context "with negative numbers" do
|
|
128
|
+
it "returns sum" do
|
|
129
|
+
# test implementation
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Let and Subject
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
RSpec.describe User do
|
|
140
|
+
let(:valid_attributes) { { name: "John", email: "john@example.com" } }
|
|
141
|
+
let(:user) { described_class.new(valid_attributes) }
|
|
142
|
+
|
|
143
|
+
subject { user }
|
|
144
|
+
|
|
145
|
+
it "has a name" do
|
|
146
|
+
expect(subject.name).to eq("John")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Before/After Hooks
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
RSpec.describe DatabaseConnection do
|
|
155
|
+
before(:each) do
|
|
156
|
+
@connection = DatabaseConnection.new
|
|
157
|
+
@connection.connect
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
after(:each) do
|
|
161
|
+
@connection.disconnect
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
it "executes query" do
|
|
165
|
+
result = @connection.query("SELECT 1")
|
|
166
|
+
expect(result).not_to be_nil
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## RSpec Matchers
|
|
172
|
+
|
|
173
|
+
### Common Matchers
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# Equality
|
|
177
|
+
expect(result).to eq(expected)
|
|
178
|
+
expect(result).to eql(expected)
|
|
179
|
+
expect(result).to be(expected)
|
|
180
|
+
|
|
181
|
+
# Truthiness
|
|
182
|
+
expect(value).to be_truthy
|
|
183
|
+
expect(value).to be_falsey
|
|
184
|
+
expect(value).to be_nil
|
|
185
|
+
|
|
186
|
+
# Comparisons
|
|
187
|
+
expect(value).to be > 5
|
|
188
|
+
expect(value).to be_between(1, 10).inclusive
|
|
189
|
+
|
|
190
|
+
# Types
|
|
191
|
+
expect(object).to be_a(String)
|
|
192
|
+
expect(object).to be_an_instance_of(MyClass)
|
|
193
|
+
|
|
194
|
+
# Collections
|
|
195
|
+
expect(array).to include(item)
|
|
196
|
+
expect(array).to contain_exactly(1, 2, 3)
|
|
197
|
+
expect(array).to match_array([1, 2, 3])
|
|
198
|
+
|
|
199
|
+
# Errors
|
|
200
|
+
expect { risky_operation }.to raise_error(CustomError)
|
|
201
|
+
expect { risky_operation }.to raise_error(CustomError, /message pattern/)
|
|
202
|
+
|
|
203
|
+
# Changes
|
|
204
|
+
expect { operation }.to change { counter }.by(1)
|
|
205
|
+
expect { operation }.to change { status }.from(:pending).to(:complete)
|
|
206
|
+
|
|
207
|
+
# Regex
|
|
208
|
+
expect(string).to match(/pattern/)
|
|
209
|
+
|
|
210
|
+
# Blocks
|
|
211
|
+
expect { operation }.to output("text").to_stdout
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Test Fixtures
|
|
215
|
+
|
|
216
|
+
### Static Fixtures
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
# spec/fixtures/sample_data.rb
|
|
220
|
+
module SampleData
|
|
221
|
+
VALID_USER = {
|
|
222
|
+
name: "John Doe",
|
|
223
|
+
email: "john@example.com",
|
|
224
|
+
age: 30
|
|
225
|
+
}.freeze
|
|
226
|
+
|
|
227
|
+
INVALID_USER = {
|
|
228
|
+
name: "",
|
|
229
|
+
email: "not-an-email",
|
|
230
|
+
age: -5
|
|
231
|
+
}.freeze
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# In spec
|
|
235
|
+
include SampleData
|
|
236
|
+
user = User.new(VALID_USER)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### FactoryBot Factories
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# spec/factories/user_factory.rb
|
|
243
|
+
FactoryBot.define do
|
|
244
|
+
factory :user do
|
|
245
|
+
name { "John Doe" }
|
|
246
|
+
email { "john@example.com" }
|
|
247
|
+
age { 30 }
|
|
248
|
+
|
|
249
|
+
trait :admin do
|
|
250
|
+
role { :admin }
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
trait :with_posts do
|
|
254
|
+
after(:create) do |user|
|
|
255
|
+
create_list(:post, 3, user: user)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# In spec
|
|
262
|
+
user = create(:user) # Create and persist
|
|
263
|
+
user = build(:user) # Build without persisting
|
|
264
|
+
admin = create(:user, :admin) # With trait
|
|
265
|
+
user_with_posts = create(:user, :with_posts)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Mocking and Stubbing
|
|
269
|
+
|
|
270
|
+
### Test Doubles
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
# Instance double (verifies methods exist on real class)
|
|
274
|
+
api_client = instance_double(APIClient)
|
|
275
|
+
allow(api_client).to receive(:fetch_user).and_return(mock_user)
|
|
276
|
+
|
|
277
|
+
# Regular double (no verification)
|
|
278
|
+
logger = double("Logger")
|
|
279
|
+
allow(logger).to receive(:info)
|
|
280
|
+
|
|
281
|
+
# Class double
|
|
282
|
+
allow(User).to receive(:find).and_return(mock_user)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Stubbing Methods
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
# Simple stub
|
|
289
|
+
allow(object).to receive(:method_name).and_return(value)
|
|
290
|
+
|
|
291
|
+
# Stub with arguments
|
|
292
|
+
allow(object).to receive(:method_name).with(arg1, arg2).and_return(value)
|
|
293
|
+
|
|
294
|
+
# Stub with block
|
|
295
|
+
allow(object).to receive(:method_name) do |arg|
|
|
296
|
+
"processed: #{arg}"
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Stub multiple calls
|
|
300
|
+
allow(object).to receive(:method_name).and_return(1, 2, 3)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Expecting Calls
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
# Expect method to be called
|
|
307
|
+
expect(object).to receive(:method_name)
|
|
308
|
+
object.method_name
|
|
309
|
+
|
|
310
|
+
# Expect with arguments
|
|
311
|
+
expect(object).to receive(:method_name).with(arg1, arg2)
|
|
312
|
+
|
|
313
|
+
# Expect call count
|
|
314
|
+
expect(object).to receive(:method_name).once
|
|
315
|
+
expect(object).to receive(:method_name).twice
|
|
316
|
+
expect(object).to receive(:method_name).exactly(3).times
|
|
317
|
+
|
|
318
|
+
# Expect NOT to be called
|
|
319
|
+
expect(object).not_to receive(:method_name)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Test Execution Commands
|
|
323
|
+
|
|
324
|
+
### Running Tests
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# All tests
|
|
328
|
+
bundle exec rspec
|
|
329
|
+
|
|
330
|
+
# Specific file
|
|
331
|
+
bundle exec rspec spec/unit/feature_name_spec.rb
|
|
332
|
+
|
|
333
|
+
# Specific line (one test)
|
|
334
|
+
bundle exec rspec spec/unit/feature_name_spec.rb:42
|
|
335
|
+
|
|
336
|
+
# By pattern
|
|
337
|
+
bundle exec rspec spec/unit/**/*_spec.rb
|
|
338
|
+
|
|
339
|
+
# With coverage
|
|
340
|
+
COVERAGE=true bundle exec rspec
|
|
341
|
+
|
|
342
|
+
# With documentation format
|
|
343
|
+
bundle exec rspec --format documentation
|
|
344
|
+
|
|
345
|
+
# Fail fast (stop on first failure)
|
|
346
|
+
bundle exec rspec --fail-fast
|
|
347
|
+
|
|
348
|
+
# Run only failed tests from last run
|
|
349
|
+
bundle exec rspec --only-failures
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## RSpec Configuration
|
|
353
|
+
|
|
354
|
+
### spec_helper.rb
|
|
355
|
+
|
|
356
|
+
```ruby
|
|
357
|
+
# frozen_string_literal: true
|
|
358
|
+
|
|
359
|
+
require "simplecov"
|
|
360
|
+
SimpleCov.start
|
|
361
|
+
|
|
362
|
+
RSpec.configure do |config|
|
|
363
|
+
# Use expect syntax (not should)
|
|
364
|
+
config.expect_with :rspec do |expectations|
|
|
365
|
+
expectations.syntax = :expect
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Use instance doubles and class doubles
|
|
369
|
+
config.mock_with :rspec do |mocks|
|
|
370
|
+
mocks.verify_partial_doubles = true
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Show 10 slowest examples
|
|
374
|
+
config.profile_examples = 10
|
|
375
|
+
|
|
376
|
+
# Run specs in random order
|
|
377
|
+
config.order = :random
|
|
378
|
+
Kernel.srand config.seed
|
|
379
|
+
|
|
380
|
+
# Allow focusing on specific tests
|
|
381
|
+
config.filter_run_when_matching :focus
|
|
382
|
+
end
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## TDD Best Practices in RSpec
|
|
386
|
+
|
|
387
|
+
### Test One Behavior Per Example
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
# ❌ BAD
|
|
391
|
+
it "processes user, sends email, and logs activity" do
|
|
392
|
+
# Testing multiple behaviors
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# ✅ GOOD
|
|
396
|
+
it "processes user data" do
|
|
397
|
+
# Tests only processing
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it "sends confirmation email" do
|
|
401
|
+
# Tests only email
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
it "logs activity" do
|
|
405
|
+
# Tests only logging
|
|
406
|
+
end
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Use Descriptive Names
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
describe "#calculate_total" do
|
|
413
|
+
it "sums line item prices"
|
|
414
|
+
it "applies discount when coupon is valid"
|
|
415
|
+
it "raises error when items array is empty"
|
|
416
|
+
it "handles nil prices by treating them as zero"
|
|
417
|
+
end
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Follow Given-When-Then
|
|
421
|
+
|
|
422
|
+
```ruby
|
|
423
|
+
it "calculates total with discount" do
|
|
424
|
+
# GIVEN: Test data setup
|
|
425
|
+
items = [build(:item, price: 100)]
|
|
426
|
+
coupon = build(:coupon, discount: 0.1)
|
|
427
|
+
|
|
428
|
+
# WHEN: Execute behavior
|
|
429
|
+
total = calculator.calculate_total(items, coupon)
|
|
430
|
+
|
|
431
|
+
# THEN: Verify expectations
|
|
432
|
+
expect(total).to eq(90)
|
|
433
|
+
end
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Mock External Dependencies
|
|
437
|
+
|
|
438
|
+
```ruby
|
|
439
|
+
it "fetches user data from API" do
|
|
440
|
+
# Don't hit real API - use doubles
|
|
441
|
+
api_client = instance_double(APIClient)
|
|
442
|
+
allow(api_client).to receive(:fetch_user).and_return(mock_user_data)
|
|
443
|
+
|
|
444
|
+
service = UserService.new(api_client: api_client)
|
|
445
|
+
user = service.get_user(123)
|
|
446
|
+
|
|
447
|
+
expect(user.name).to eq("Test User")
|
|
448
|
+
end
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Test Public Interface, Not Implementation
|
|
452
|
+
|
|
453
|
+
```ruby
|
|
454
|
+
# ❌ BAD - Testing implementation
|
|
455
|
+
it "calls internal helper method" do
|
|
456
|
+
expect(subject).to receive(:internal_helper)
|
|
457
|
+
subject.public_method
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# ✅ GOOD - Testing behavior
|
|
461
|
+
it "returns formatted phone number" do
|
|
462
|
+
result = subject.format_phone("5551234567")
|
|
463
|
+
expect(result).to eq("(555) 123-4567")
|
|
464
|
+
end
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Test Generation Template
|
|
468
|
+
|
|
469
|
+
When generating test specifications, create:
|
|
470
|
+
|
|
471
|
+
1. **Test specification document**: `docs/tdd_specifications.md`
|
|
472
|
+
2. **Skeleton test files**: In `spec/` following RSpec conventions
|
|
473
|
+
3. **Fixture files**: In `spec/fixtures/` or factories in `spec/factories/`
|
|
474
|
+
|
|
475
|
+
### Example Test Specification
|
|
476
|
+
|
|
477
|
+
```markdown
|
|
478
|
+
# TDD Test Specifications
|
|
479
|
+
|
|
480
|
+
## Unit Tests
|
|
481
|
+
|
|
482
|
+
### Feature: UserValidator
|
|
483
|
+
|
|
484
|
+
**File:** `spec/unit/user_validator_spec.rb`
|
|
485
|
+
|
|
486
|
+
**Test Cases:**
|
|
487
|
+
1. ⭕ should validate email format
|
|
488
|
+
2. ⭕ should reject invalid emails
|
|
489
|
+
3. ⭕ should validate required fields
|
|
490
|
+
4. ⭕ should handle nil gracefully
|
|
491
|
+
|
|
492
|
+
## Integration Tests
|
|
493
|
+
|
|
494
|
+
### Integration: UserService + EmailService
|
|
495
|
+
|
|
496
|
+
**File:** `spec/integration/user_service_spec.rb`
|
|
497
|
+
|
|
498
|
+
**Test Cases:**
|
|
499
|
+
1. ⭕ should create user and send welcome email
|
|
500
|
+
2. ⭕ should rollback on email failure
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## Output Format
|
|
504
|
+
|
|
505
|
+
Generate complete, runnable RSpec test files following:
|
|
506
|
+
|
|
507
|
+
1. RSpec DSL and conventions
|
|
508
|
+
2. Given-When-Then structure
|
|
509
|
+
3. Proper describe/context/it nesting
|
|
510
|
+
4. Appropriate matchers and expectations
|
|
511
|
+
5. Test doubles for external dependencies
|
|
512
|
+
6. Ruby idioms and style
|
|
513
|
+
|
|
514
|
+
**Remember: Tests are executable documentation. Write them clearly!**
|