Description
From a comment I wrote in #235
This PR is for you:
If anyone wants to help put together a 'standard response' doc, that would be great. See e.g. the one nokogiri uses https://github.com/sparklemotion/nokogiri/blob/master/STANDARD_RESPONSES.md
Where coverage comes from
So, first, background on how simplecov gets coverage.
SimpleCov uses the stdlib Coverage module.
The Coverage module, when active, tracks when code is evaluated for the first time (i.e. when loaded or run )
SimpleCov starts and stops tracking coverage (via Coverage) when you run SimpleCov.start
and SimpleCov.result
, respectively.
Thus, when you run SimpleCov.start
, load/require a file/files, then run SimpleCov.result
(which by default runs via an exit hook when your code finishes running), you will see a certain amount of 'code coverage'. That is, simply loading or requiring a Ruby file will result in some 'code coverage'. If you were to load all files in your app, then run SimpleCov.start, then run your tests, you would only capture the additional coverage for code that is executed for the first time. Likewise, if you ran SimpleCov.start, then loaded all files in your app, then ran the tests, even non-tested files would show 'coverage' as they will have code executed simple by being loaded/required.
Where SimpleCov.start
should happen
As described above, as early as possible; it must be before any app code is run.
When you require 'simplecov'
, the .simplecov
file, if present, will be executed.
Thus, if you, perhaps through Bundle.require
require simplecov, but do not have a .simplecov
file, coverage will not start until you explicitly run SimpleCov.start
. In other words, by using a .simplecov
file, you ensure that SimpleCov will start as soon as it is required.
The .simplecov
https://github.com/colszowka/simplecov/blob/master/lib/simplecov/defaults.rb#L85
# Autoload config from ~/.simplecov if present
# Autoload config from .simplecov if present
Common Problems
- Running Rails tests using
rake
often gives erroneous coverage because the app is loaded beforeSimpleCov.start
is run in a test helper. - Coverage lost when running in parallel
Rails and Railties
Vanilla Rails will evaluate all Railties before initializing the Rails app and its initializers. Thus, a Railtie is a reasonable way to hook into Rails initialization lifecycle to start SimpleCov before app code is run.
Right now, require 'simplecov/railtie'
loads the file
module SimpleCov
class Railtie < ::Rails::Railtie
rake_tasks do
load 'simplecov/railties/tasks.rake'
end
end
end
What should now be obvious is that this railtie does not require 'simplecov'
nor does it call SimpleCov.start
. @envygeeks is correct that missing the require is really a bug. (but checking the Rails env isn't necessary, because presumably you are only requiring simplecov in test or whenever you want to run it.)
require 'simplecov'
module SimpleCov
class Railtie < ::Rails::Railtie
rake_tasks do
load 'simplecov/railties/tasks.rake'
end
end
end
and to get around where you put the SimpleCov.start
in your code is why I consider using a .simplecov
file a best practice.
Parallel testing
Using .simplecov rather than separately requiring SimpleCov multiple times is recommended if you are merging multiple test frameworks like Cucumber and RSpec that rely on each other, as invoking SimpleCov multiple times can cause coverage information to be lost.
You'll also want to use multiple 'command_names' to differentiate reports being merged in together.
See https://github.com/colszowka/simplecov/blob/master/lib/simplecov/command_guesser.rb#L19 that sets an unset command_name
from the env if ENV['PARALLEL_TEST_GROUPS'] && ENV['TEST_ENV_NUMBER']
, or from the command that ran the tests, or from any defined constants. to be sure, you may want to set it yourself, e.g. command_name "rails_app_#{$$}" # $$ is the processid
And then add something like merge_timeout 3600 # 1 hour should cover how long it takes the tests to run
Example .simplecov
# https://github.com/colszowka/simplecov#using-simplecov-for-centralized-config
# Maybe put some conditional here not to execute the code below unless ENV['COVERAGE'] == 'true'
SimpleCov.start do
# see https://github.com/colszowka/simplecov/blob/master/lib/simplecov/defaults.rb
load_profile 'rails' # load_adapter < 0.8
coverage_dir 'tmp/coverage'
# Use multiple 'command_names' to differentiate reports being merged in together
command_name "rails_app_#{$$}" # $$ is the processid
merge_timeout 3600 # 1 hour
add_group "Jobs", "app/jobs"
add_group "Middleware", "app/middleware"
add_group "Serializers", "app/serializers"
add_group "Engines", "engines"
add_group "Views", "app/views"
add_group "Long files" do |src_file|
src_file.lines.count > 100
end
class MaxLinesFilter < SimpleCov::Filter
def matches?(source_file)
source_file.lines.count < filter_argument
end
end
add_group "Short files", MaxLinesFilter.new(5)
# Exclude these paths from analysis
add_filter 'lib/plugins'
add_filter 'vendor'
add_filter 'bundle'
end
or even define your own profile that is backwards compatible
if SimpleCov.respond_to?(:profiles)
SimpleCov.profiles
else
SimpleCov.adapters
end.define 'my_app' do
if defined?(load_profile)
load_profile 'test_frameworks'
else
load_adapter 'test_frameworks'
end
coverage_dir 'tmp/coverage'
# Use multiple 'command_names' to differentiate reports being merged in together
command_name "rails_app_#{$$}" # $$ is the processid
merge_timeout 3600 # 1 hour
end
if ENV['COVERAGE'] == 'true'
SimpleCov.start 'my_app'
if ENV['CODECLIMATE_REPO_TOKEN']
begin
require "codeclimate-test-reporter"
# We run in parallel, so output results to disk via TO_FILE
# and send at end, like tddium config
# https://github.com/codeclimate/ruby-test-reporter/blob/master/lib/code_climate/test_reporter/formatter.rb#L18
ENV['TO_FILE'] = 'true'
SimpleCov.formatters = [
SimpleCov::Formatter::HTMLFormatter,
CodeClimate::TestReporter::Formatter
]
rescue LoadError
STDERR.puts "Could not loade CodeClimate SimpleCov Reporter"
end
end
end
SimpleCov.at_exit do
File.open(File.join(SimpleCov.coverage_path, 'coverage_percent.txt'), 'w') do |f|
f.write SimpleCov.result.covered_percent
end
SimpleCov.result.format!
end