I did a presentation in 2018 RubyHACK and will do the same presentation in 2018 TheConf called “Put Git to work: increase the quality of your project, and let git do the boring work for you”, one of the subjects in the presentation is how to use git to run tests only for the changed files before you push anything to the shared git server, and the code is really simple as you can see bellow:
The main idea is before pushing anything, check what files were changed in your branch compared to the same branch on the server, then run the tests for that files.
The code bellow is not perfect, it assumes the following:
- Every file under the app directory has an equivalent in the test directory ended in _test.rb
- if there is a configuration file changed, all tests will be executed
- If there is one test file missing (corresponding to one of the changed files), the push is aborted
- if the tests fail, the push is aborted
Of course, these assumptions are not perfect, there is one “hidden” assumption there, that you have an integration test that will run all tests for every push, but the idea here is to streamline the process and prevent tests not being executed “just because they take a long time”
But lets check the code now:
#!/usr/bin/env ruby branch_name=`git symbolic-ref -q HEAD` branch_name = branch_name.gsub('refs/heads/', '').strip changed_files = `git diff --name-only #{ARGV[0]}/#{branch_name}`.split("\n").map(&:strip).select{|n| n =~ /\.rb$/} #run the file specific test test_files = changed_files.map do |name| if name =~ /^app/ name.gsub('app/', 'test/').gsub(/\.rb$/, '_test.rb') elsif name =~ /^test/ name else nil end end.reject(&:nil?).uniq #abort if there is a test missing unless test_files.reject{ |n| File.exist?(n) }.empty? puts %Q{ Aborting push because there are missing test files: #{test_files.reject{ |n| File.exist?(n) }} } exit 1 end #if there are config files changes, run all tests unless changed_files.select{ |n| n =~ /^config/ }.empty? test_files = '' end system('git stash') result = system("rails test #{test_files.join(' ')}") system('git stash apply') unless result puts %Q{ Aborting push due to failed tests } exit 1 end
the code above should be saved in a .git/hooks/pre-push file for your project, and remember to make that file executable so that it will run before every push automatically.
Of course the test assumptions can be changed to match your project, and a better heuristics to define what tests affect each file would be great, but to have a really good guess we would need a code a lot more complex, and this is already good enough for simpler cases.
This code is the sample from the presentation, the version I’m currently using assumes also the following:
- If there is a controller, view, css or javascript file changed all cucumber tests must be executed
- Gemfile* yarn.lock and package-files.json have the same “run all tests” effect that any configuration file
- The tests are written with rspec, so the test file name changed to directory specs with the extension _spec.rb
Do you like the idea of git taking care of running your tests for you? What do you think of the assumptions used in the script?
please leave your opinion in the comments bellow.