Skip to content

Instantly share code, notes, and snippets.

@artemave
Last active February 2, 2022 15:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save artemave/77f801e4dfa9b6c0d344fcacb5501c71 to your computer and use it in GitHub Desktop.
Save artemave/77f801e4dfa9b6c0d344fcacb5501c71 to your computer and use it in GitHub Desktop.

Dropping this into lib/tasks/test_wip.rake will add rails test:diff command. This runs certain tests that relate to your changes. In particular, the following tests are picked:

  • modified* test files
  • test files for modified model/controller/job/mailer/helper files (e.g. if app/models/user.rb has changed, test/models/user_test.rb is picked)
  • test files for controllers/mailers whose views have been modified

*modified file is a file that is either git modifed or appears in git diff master

The quality of the result relies on how well you stick to the rails conventions. E.g., changes in app/models/user.rb won't trigger app/models/account_test.rb, if that's where your user models tests happens to reside.

This works for minitest, but could be easily adapted to rspec.

# Runs tests that changed on current branch
class DiffTests
def self.run(tests)
return if tests.blank?
require 'childprocess'
puts "rails test #{tests.join(' ')}"
p = ChildProcess.build('rails', 'test', *tests)
p.io.inherit!
p.start
p.wait
# rubocop:disable Style/NumericPredicate
exit p.exit_code if p.exit_code > 0
# rubocop:enable Style/NumericPredicate
end
def self.non_committed_changed
`git status --porcelain | grep .rb`
.split("\n")
# skip deleted files
.grep_v(/^ *D /)
.map do |line|
# e.g.
# 'M app/models/user.rb' => 'app/models/user.rb'
# 'R app/models/user.rb -> app/models/user2.rb' => 'app/models/user2.rb'
line.sub(/.*?([^ ]+)$/, '\1')
end
end
def self.committed_changed
`git diff --name-only master... | grep .rb`.split("\n")
end
def self.changed_files
@changed_files ||= committed_changed.concat(non_committed_changed).uniq
end
def self.tests_to_run
changed_test_files = changed_files.select { |file| file =~ %r{test/.+_test\.rb} && File.exist?(file) }
(changed_test_files + test_files_for_changed_ruby_files + test_files_for_changed_erb_files).uniq
end
def self.changed_code_files
changed_files.grep(/^app/)
end
def self.test_files_for_changed_ruby_files
select_existing_files do
changed_code_files.filter_map do |path|
next unless path.match?(/\.rb$/)
path.sub(%r{^app/(.*)\.rb$}, 'test/\1_test.rb')
end
end
end
def self.test_files_for_changed_erb_files
select_existing_files do
changed_code_files.filter_map do |path|
next unless path.match?(/html.erb$/)
if path.include?('_mailer/')
path.sub(%r{app/views/(.+_mailer)/.*$}, 'test/mailers/\1_test.rb')
else # controller
path.sub(%r{app/views/(.+)/[^/]+$}, 'test/controllers/\1_controller_test.rb')
end
end
end
end
def self.select_existing_files
yield.select { |path| File.exist?(path) }
end
end
namespace :test do
task :diff do
regular_tests_to_run, system_tests_to_run = DiffTests.tests_to_run.partition do |path|
path !~ Regexp.new('test/system/')
end
# Run system tests last
DiffTests.run(regular_tests_to_run)
DiffTests.run(system_tests_to_run)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment