When working with Ruby and RSpec, I wanted to streamline the process of running tests only for files affected by recent changes in Git. Here’s how I achieved it by creating a script that maps modified files to their corresponding RSpec test files.
The Challenge
The goal was to:
- Identify files changed using
git status
. - Map each changed Ruby file to its corresponding test file (
_spec.rb
). - Skip irrelevant files like
.csv
or other non-Ruby files. - Automatically run the identified tests.
The Solution
I created a Ruby script (run_changed_specs.rb
) that dynamically determines the current working directory, filters changed files, and runs only the relevant specs.
Here’s the script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env ruby
# Get the current working directory
current_dir = Dir.pwd
# Get the changed files from `git status`
changed_files = `git -C #{current_dir} status --porcelain`.lines.map { |line| line.strip.split.last }
# Helper function to map a source file to its corresponding spec file
def find_spec_file(file, current_dir)
return file if file.include?("spec") # It's already a spec file
# Only consider Ruby source files
return nil unless file.end_with?(".rb")
# Replace "app/" or other source directory prefixes with "spec/"
spec_file = file.sub("app/", "spec/")
.sub(/\.rb$/, "_spec.rb") # Append `_spec.rb` to match the spec naming convention
# Ensure the spec file exists relative to the current directory
full_spec_path = File.join(current_dir, spec_file)
File.exist?(full_spec_path) ? spec_file : nil
end
# Map changed files to their spec files, filtering out non-Ruby files
spec_files = changed_files.map { |file| find_spec_file(file, current_dir) }.compact
if spec_files.empty?
puts "No matching spec files found for the changed files."
exit 1
end
# Print the spec files to be run
puts "Running specs for the following files:"
spec_files.each { |file| puts "- #{file}" }
# Run the specs from the current directory
system("cd #{current_dir} && rspec #{spec_files.join(' ')}")
How It Works
-
Get Changed Files:
Usesgit status --porcelain
to get a list of modified files. - Filter Relevant Files:
- If the file contains
spec
, it is treated as a test file. - Ruby files (
.rb
) are mapped to their corresponding_spec.rb
files. - Non-Ruby files are ignored.
- If the file contains
- Run the Tests:
All identified spec files are passed torspec
.
Usage
- Save the script as
run_changed_specs.rb
in your project directory. - Make it executable:
1
chmod +x run_changed_specs.rb
- Run the script from any directory in the project:
1
./run_changed_specs.rb
Example Workflow
Given this output from git status
:
1
2
3
modified: app/models/item.rb
modified: spec/app/decorators/style_spec.rb
modified: data/templates/export_template.csv
The script will correctly identify:
1
2
3
Running specs for the following files:
- spec/models/item_spec.rb
- spec/app/decorators/style_spec.rb
And run:
1
rspec spec/models/item_spec.rb spec/app/decorators/style_spec.rb
Takeaways
- Automating testing for changed files can significantly speed up development and testing cycles.
- Mapping source files to their test files ensures that only relevant tests are run.
- Scripts like this can be easily customized to fit specific project structures.
That’s it! Now you can run tests efficiently based on changed files. 🚀