igata 0.2.0 → 0.3.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.
data/USAGE.md ADDED
@@ -0,0 +1,441 @@
1
+ # Real-World Usage Scenarios
2
+
3
+ This document describes practical scenarios where Igata helps with test development in real projects.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Scenario 1: TDD for New Features](#scenario-1-tdd-for-new-features)
8
+ - [Scenario 2: Adding Tests to Legacy Code](#scenario-2-adding-tests-to-legacy-code)
9
+ - [Scenario 3: Rails Application Testing](#scenario-3-rails-application-testing)
10
+ - [Scenario 4: Refactoring with Safety Net](#scenario-4-refactoring-with-safety-net)
11
+ - [Scenario 5: Code Review - Test Coverage Check](#scenario-5-code-review---test-coverage-check)
12
+ - [Scenario 6: Team Development - Standardizing Test Skeletons](#scenario-6-team-development---standardizing-test-skeletons)
13
+ - [Scenario 7: LLM-Assisted Test Implementation](#scenario-7-llm-assisted-test-implementation)
14
+
15
+ ---
16
+
17
+ ## Scenario 1: TDD for New Features
18
+
19
+ **Context**: You're implementing a new feature using Test-Driven Development.
20
+
21
+ **Workflow**:
22
+
23
+ 1. Design your class interface
24
+ 2. Generate test skeleton with Igata
25
+ 3. Write test logic based on generated hints
26
+ 4. Implement the feature to pass tests
27
+
28
+ **Example**:
29
+
30
+ ```bash
31
+ # 1. Create class interface
32
+ cat > lib/payment_validator.rb << 'EOF'
33
+ class PaymentValidator
34
+ def initialize(amount, currency)
35
+ @amount = amount
36
+ @currency = currency
37
+ end
38
+
39
+ def valid?
40
+ return false if @amount <= 0
41
+ return false unless supported_currency?
42
+ true
43
+ end
44
+
45
+ def supported_currency?
46
+ @currency == "USD" || @currency == "EUR" || @currency == "JPY"
47
+ end
48
+ end
49
+ EOF
50
+
51
+ # 2. Generate test skeleton
52
+ igata lib/payment_validator.rb > test/payment_validator_test.rb
53
+
54
+ # 3. Review generated hints
55
+ cat test/payment_validator_test.rb
56
+ ```
57
+
58
+ Output shows:
59
+
60
+ ```ruby
61
+ def test_valid?
62
+ # Branches: if (@amount <= 0), unless (supported_currency?)
63
+ # Comparisons: <= (@amount <= 0)
64
+ skip "Not implemented yet"
65
+ end
66
+
67
+ def test_supported_currency?
68
+ # Comparisons: == (@currency == "USD"), == (@currency == "EUR"), == (@currency == "JPY")
69
+ skip "Not implemented yet"
70
+ end
71
+ ```
72
+
73
+ **Benefits**:
74
+ - Branch hints suggest testing both positive and negative amounts
75
+ - Comparison hints suggest boundary value testing (0, negative, positive)
76
+ - Currency comparison hints suggest testing each supported currency plus unsupported ones
77
+
78
+ ---
79
+
80
+ ## Scenario 2: Adding Tests to Legacy Code
81
+
82
+ **Context**: You need to add tests to existing code without tests.
83
+
84
+ **Workflow**:
85
+
86
+ ```bash
87
+ # Generate test skeleton for existing file
88
+ igata lib/legacy/order_processor.rb > test/legacy/order_processor_test.rb
89
+
90
+ # Review complexity from generated comments
91
+ # High number of branches/comparisons → complex method needing refactoring
92
+ ```
93
+
94
+ **Example**:
95
+
96
+ ```ruby
97
+ # Generated test reveals complexity
98
+ def test_process_order
99
+ # Branches: if (order.paid?), if (order.shippable?), case (order.status), if (inventory.available?(order.items)), unless (order.address.valid?)
100
+ # Comparisons: > (order.total > 1000), >= (customer.age >= 18), == (order.priority == "high")
101
+ skip "Not implemented yet"
102
+ end
103
+ ```
104
+
105
+ **Benefits**:
106
+ - Quickly see method complexity from comment density
107
+ - Branch/comparison hints guide edge case testing
108
+ - Identify refactoring opportunities before writing tests
109
+
110
+ ---
111
+
112
+ ## Scenario 3: Rails Application Testing
113
+
114
+ **Context**: Testing ActiveRecord models and service objects.
115
+
116
+ ### ActiveRecord Models
117
+
118
+ ```bash
119
+ # Generate test for User model
120
+ igata app/models/user.rb > test/models/user_test.rb
121
+ ```
122
+
123
+ **Example Model**:
124
+
125
+ ```ruby
126
+ class User < ApplicationRecord
127
+ def adult?
128
+ age >= 18
129
+ end
130
+
131
+ def display_name
132
+ if name.present?
133
+ name
134
+ else
135
+ "Guest #{id}"
136
+ end
137
+ end
138
+
139
+ def subscription_active?
140
+ return false unless subscription_ends_at
141
+ subscription_ends_at > Time.current
142
+ end
143
+ end
144
+ ```
145
+
146
+ **Generated Test**:
147
+
148
+ ```ruby
149
+ def test_adult?
150
+ # Comparisons: >= (age >= 18)
151
+ skip "Not implemented yet"
152
+ end
153
+
154
+ def test_display_name
155
+ # Branches: if (name.present?)
156
+ skip "Not implemented yet"
157
+ end
158
+
159
+ def test_subscription_active?
160
+ # Branches: unless (subscription_ends_at)
161
+ # Comparisons: > (subscription_ends_at > Time.current)
162
+ skip "Not implemented yet"
163
+ end
164
+ ```
165
+
166
+ **Benefits**:
167
+ - Hints suggest testing boundary ages (17, 18, 19)
168
+ - Branch hints suggest testing with/without name
169
+ - Comparison hints suggest testing time boundaries (expired, current, future)
170
+
171
+ ### Service Objects
172
+
173
+ ```bash
174
+ # Generate RSpec test for service object
175
+ igata app/services/subscription_renewal_service.rb -f rspec > spec/services/subscription_renewal_service_spec.rb
176
+
177
+ # Or generate Minitest Spec test (RSpec-style DSL with Minitest)
178
+ igata app/services/subscription_renewal_service.rb -f minitest_spec > test/services/subscription_renewal_service_spec.rb
179
+
180
+ # Or generate traditional Minitest test
181
+ igata app/services/subscription_renewal_service.rb > test/services/subscription_renewal_service_test.rb
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Scenario 4: Refactoring with Safety Net
187
+
188
+ **Context**: You need to refactor complex methods but want test coverage first.
189
+
190
+ **Workflow**:
191
+
192
+ ```bash
193
+ # 1. Generate test for current implementation
194
+ igata lib/report_generator.rb > test/report_generator_test.rb
195
+
196
+ # 2. Implement tests based on current behavior
197
+ # 3. Run tests → all pass
198
+ # 4. Refactor code
199
+ # 5. Run tests → ensure behavior unchanged
200
+ ```
201
+
202
+ **Benefits**:
203
+ - Establish baseline behavior before refactoring
204
+ - Branch/comparison hints ensure edge cases are covered
205
+ - Confidence in refactoring safety
206
+
207
+ ---
208
+
209
+ ## Scenario 5: Code Review - Test Coverage Check
210
+
211
+ **Context**: Reviewing a pull request to ensure adequate test coverage.
212
+
213
+ **Workflow**:
214
+
215
+ ```bash
216
+ # Generate test skeleton for changed files
217
+ igata lib/new_feature.rb > /tmp/expected_tests.rb
218
+
219
+ # Compare with actual tests in PR
220
+ diff /tmp/expected_tests.rb test/new_feature_test.rb
221
+ ```
222
+
223
+ **Review Checklist**:
224
+ - Are all methods from generated skeleton tested?
225
+ - Are branch cases from comments covered?
226
+ - Are boundary values from comparison comments tested?
227
+
228
+ **Benefits**:
229
+ - Objective test coverage assessment
230
+ - Identify missing test cases quickly
231
+ - Standardized review criteria
232
+
233
+ ---
234
+
235
+ ## Scenario 6: Team Development - Standardizing Test Skeletons
236
+
237
+ **Context**: Team wants consistent test structure across projects.
238
+
239
+ **Setup**:
240
+
241
+ ```bash
242
+ # Add to team's coding guidelines
243
+ echo "Generate test skeletons with: igata <file> > test/<file>_test.rb" >> CONTRIBUTING.md
244
+
245
+ # CI check for test existence
246
+ # .github/workflows/ci.yml
247
+ ```
248
+
249
+ ```yaml
250
+ - name: Check test coverage
251
+ run: |
252
+ for file in lib/**/*.rb; do
253
+ test_file="test/$(basename $file .rb)_test.rb"
254
+ if [ ! -f "$test_file" ]; then
255
+ echo "Missing test: $test_file"
256
+ exit 1
257
+ fi
258
+ done
259
+ ```
260
+
261
+ **Benefits**:
262
+ - Consistent test structure across team
263
+ - New members quickly learn testing patterns
264
+ - Automated enforcement of test existence
265
+
266
+ ---
267
+
268
+ ## Scenario 7: LLM-Assisted Test Implementation
269
+
270
+ **Context**: Using GitHub Copilot, Claude, or ChatGPT to accelerate test writing.
271
+
272
+ **Workflow**:
273
+
274
+ ```bash
275
+ # 1. Generate test skeleton with detailed hints
276
+ igata lib/calculator.rb > test/calculator_test.rb
277
+ ```
278
+
279
+ **Generated**:
280
+
281
+ ```ruby
282
+ def test_divide
283
+ # Branches: if (divisor == 0)
284
+ # Comparisons: == (divisor == 0)
285
+ skip "Not implemented yet"
286
+ end
287
+ ```
288
+
289
+ **2. LLM reads comments and suggests**:
290
+
291
+ ```ruby
292
+ def test_divide
293
+ # Branches: if (divisor == 0)
294
+ # Comparisons: == (divisor == 0)
295
+
296
+ # Test normal division
297
+ calc = Calculator.new
298
+ assert_equal 2, calc.divide(4, 2)
299
+
300
+ # Test division by zero (branch hint)
301
+ assert_raises(ZeroDivisionError) { calc.divide(4, 0) }
302
+
303
+ # Test boundary cases (comparison hint)
304
+ assert_equal 0, calc.divide(0, 2)
305
+ assert_equal Float::INFINITY, calc.divide(1.0, 0)
306
+ end
307
+ ```
308
+
309
+ **Benefits**:
310
+ - Comments provide context for LLM suggestions
311
+ - More accurate test generation from LLM
312
+ - Branch/comparison hints guide comprehensive coverage
313
+ - Faster test implementation with AI assistance
314
+
315
+ **LLM Tools Compatible**:
316
+ - GitHub Copilot
317
+ - Claude Code
318
+ - ChatGPT
319
+ - Amazon CodeWhisperer
320
+ - Cody (Sourcegraph)
321
+
322
+ ---
323
+
324
+ ## Integration with Development Tools
325
+
326
+ ### Pre-commit Hook
327
+
328
+ ```bash
329
+ # .git/hooks/pre-commit
330
+ #!/bin/bash
331
+ for file in $(git diff --cached --name-only --diff-filter=AM | grep '^lib/.*\.rb$'); do
332
+ test_file="test/$(basename $file .rb)_test.rb"
333
+ if [ ! -f "$test_file" ]; then
334
+ echo "Generating test skeleton: $test_file"
335
+ igata "$file" > "$test_file"
336
+ git add "$test_file"
337
+ fi
338
+ done
339
+ ```
340
+
341
+ ### Rake Task
342
+
343
+ ```ruby
344
+ # lib/tasks/igata.rake
345
+ namespace :igata do
346
+ desc "Generate missing test files"
347
+ task :generate_missing do
348
+ Dir.glob("lib/**/*.rb").each do |file|
349
+ test_file = file.sub("lib/", "test/").sub(".rb", "_test.rb")
350
+ unless File.exist?(test_file)
351
+ puts "Generating: #{test_file}"
352
+ system("igata #{file} > #{test_file}")
353
+ end
354
+ end
355
+ end
356
+ end
357
+ ```
358
+
359
+ ### Editor Integration (VS Code)
360
+
361
+ ```json
362
+ // .vscode/tasks.json
363
+ {
364
+ "version": "2.0.0",
365
+ "tasks": [
366
+ {
367
+ "label": "Generate Test with Igata",
368
+ "type": "shell",
369
+ "command": "igata ${file} > test/$(basename ${file} .rb)_test.rb",
370
+ "presentation": {
371
+ "reveal": "always"
372
+ }
373
+ }
374
+ ]
375
+ }
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Best Practices
381
+
382
+ ### 1. Use Branch/Comparison Hints as Test Case Guide
383
+
384
+ **Generated hint**:
385
+ ```ruby
386
+ # Branches: if (@age >= 18), unless (@verified)
387
+ # Comparisons: >= (@age >= 18)
388
+ ```
389
+
390
+ **Write tests for**:
391
+ - `@age >= 18` boundary: age = 17, 18, 19
392
+ - `@verified` states: true, false
393
+ - Combinations: (age < 18, verified), (age >= 18, not verified), etc.
394
+
395
+ ### 2. Remove `skip`/`pending` After Implementation
396
+
397
+ ```ruby
398
+ # Before
399
+ def test_adult?
400
+ # Comparisons: >= (@age >= 18)
401
+ skip "Not implemented yet"
402
+ end
403
+
404
+ # After
405
+ def test_adult?
406
+ # Comparisons: >= (@age >= 18)
407
+ user = User.new(name: "Alice", age: 17)
408
+ refute user.adult?
409
+
410
+ user = User.new(name: "Bob", age: 18)
411
+ assert user.adult?
412
+ end
413
+ ```
414
+
415
+ ### 3. Keep Comments for Future Reference
416
+
417
+ Comments help:
418
+ - New team members understand test intent
419
+ - Future refactoring efforts
420
+ - LLM tools provide better suggestions
421
+
422
+ ### 4. Regenerate After Major Refactoring
423
+
424
+ ```bash
425
+ # Compare old and new structure
426
+ igata lib/refactored_class.rb > /tmp/new_tests.rb
427
+ diff test/refactored_class_test.rb /tmp/new_tests.rb
428
+ ```
429
+
430
+ ---
431
+
432
+ ## Summary
433
+
434
+ Igata accelerates test development by:
435
+ - **Generating boilerplate** - Focus on test logic, not structure
436
+ - **Providing hints** - Branch/comparison comments guide edge case testing
437
+ - **Standardizing structure** - Consistent tests across team/projects
438
+ - **Enabling LLM assistance** - Comments provide context for better AI suggestions
439
+ - **Revealing complexity** - Comment density indicates refactoring needs
440
+
441
+ Choose the scenarios that fit your workflow and adapt them to your team's practices.
data/exe/igata CHANGED
@@ -9,8 +9,8 @@ OptionParser.new do |opts|
9
9
  opts.banner = "Usage: igata [options] [file]"
10
10
  opts.version = Igata::VERSION
11
11
 
12
- opts.on("-f", "--formatter FORMATTER", %i[minitest rspec],
13
- "Test framework formatter (minitest, rspec)") do |formatter|
12
+ opts.on("-f", "--formatter FORMATTER", %i[minitest rspec minitest_spec],
13
+ "Test framework formatter (minitest, rspec, minitest_spec)") do |formatter|
14
14
  options[:formatter] = formatter
15
15
  end
16
16
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Igata
4
+ module Extractors
5
+ # Extracts argument information from method definitions using script_lines
6
+ class ArgumentExtractor
7
+ def self.extract(method_info, script_lines)
8
+ new(method_info, script_lines).extract
9
+ end
10
+
11
+ def initialize(method_info, script_lines)
12
+ @method_info = method_info
13
+ @script_lines = script_lines
14
+ end
15
+
16
+ def extract
17
+ # Find the method definition line
18
+ method_line = find_method_line(@method_info.name)
19
+ return nil unless method_line
20
+
21
+ # Parse arguments
22
+ parse_arguments(method_line)
23
+ end
24
+
25
+ private
26
+
27
+ def find_method_line(method_name)
28
+ @script_lines.find do |line|
29
+ line.strip.start_with?("def #{method_name}")
30
+ end
31
+ end
32
+
33
+ def parse_arguments(method_line)
34
+ # Extract arguments part from method definition
35
+ if method_line =~ /def\s+\w+\s*\((.*?)\)/
36
+ args_str = ::Regexp.last_match(1)
37
+ parse_args_string(args_str)
38
+ else
39
+ # Method without parentheses (no arguments)
40
+ Values::ArgumentInfo.new(args: [])
41
+ end
42
+ end
43
+
44
+ def parse_args_string(args_str)
45
+ return Values::ArgumentInfo.new(args: []) if args_str.strip.empty?
46
+
47
+ args = args_str.split(",").map(&:strip).map do |arg|
48
+ parse_single_arg(arg)
49
+ end
50
+
51
+ Values::ArgumentInfo.new(args: args)
52
+ end
53
+
54
+ # rubocop:disable Metrics/MethodLength
55
+ def parse_single_arg(arg)
56
+ case arg
57
+ when /^(\w+)\s*=\s*(.+)$/ # Optional argument: name = "default"
58
+ Values::ArgDetail.new(
59
+ name: ::Regexp.last_match(1),
60
+ type: :optional,
61
+ default: ::Regexp.last_match(2)
62
+ )
63
+ when /^(\w+):\s*(.+)$/ # Keyword argument: verified: false
64
+ Values::ArgDetail.new(
65
+ name: ::Regexp.last_match(1),
66
+ type: :keyword,
67
+ default: ::Regexp.last_match(2)
68
+ )
69
+ when /^(\w+):$/ # Required keyword argument: verified:
70
+ Values::ArgDetail.new(
71
+ name: ::Regexp.last_match(1),
72
+ type: :required_keyword
73
+ )
74
+ when /^\*\*(\w+)$/ # Keyword rest: **kwargs
75
+ Values::ArgDetail.new(
76
+ name: ::Regexp.last_match(1),
77
+ type: :keyrest
78
+ )
79
+ when /^\*(\w+)$/ # Rest: *args
80
+ Values::ArgDetail.new(
81
+ name: ::Regexp.last_match(1),
82
+ type: :rest
83
+ )
84
+ when /^&(\w+)$/ # Block: &block
85
+ Values::ArgDetail.new(
86
+ name: ::Regexp.last_match(1),
87
+ type: :block
88
+ )
89
+ else # Required argument: name
90
+ Values::ArgDetail.new(
91
+ name: arg,
92
+ type: :required
93
+ )
94
+ end
95
+ end
96
+ # rubocop:enable Metrics/MethodLength
97
+ end
98
+ end
99
+ end