-
-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #88 from fatkodima/multiple-assertions-cop
Add new `Minitest/MultipleAssertions` cop
- Loading branch information
Showing
8 changed files
with
284 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Minitest | ||
# This cop checks if test cases contain too many assertion calls. | ||
# The maximum allowed assertion calls is configurable. | ||
# | ||
# @example Max: 1 | ||
# # bad | ||
# class FooTest < Minitest::Test | ||
# def test_asserts_twice | ||
# assert_equal(42, do_something) | ||
# assert_empty(array) | ||
# end | ||
# end | ||
# | ||
# # good | ||
# class FooTest < Minitest::Test | ||
# def test_asserts_once | ||
# assert_equal(42, do_something) | ||
# end | ||
# | ||
# def test_another_asserts_once | ||
# assert_empty(array) | ||
# end | ||
# end | ||
# | ||
class MultipleAssertions < Cop | ||
include ConfigurableMax | ||
include MinitestExplorationHelpers | ||
|
||
MSG = 'Test case has too many assertions [%<total>d/%<max>d].' | ||
|
||
def on_class(class_node) | ||
return unless minitest_test_subclass?(class_node) | ||
|
||
test_cases(class_node).each do |node| | ||
assertions_count = assertions_count(node) | ||
|
||
next unless assertions_count > max_assertions | ||
|
||
self.max = assertions_count | ||
|
||
message = format(MSG, total: assertions_count, max: max_assertions) | ||
add_offense(node, location: :name, message: message) | ||
end | ||
end | ||
|
||
private | ||
|
||
def assertions_count(node) | ||
base = assertion?(node) ? 1 : 0 | ||
base + node.each_child_node.sum { |c| assertions_count(c) } | ||
end | ||
|
||
def max_assertions | ||
Integer(cop_config.fetch('Max', 3)) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'set' | ||
|
||
module RuboCop | ||
module Cop | ||
# Helper methods for different explorations against test files and test cases. | ||
module MinitestExplorationHelpers | ||
extend NodePattern::Macros | ||
|
||
ASSERTIONS = %i[ | ||
assert | ||
assert_empty | ||
assert_equal | ||
assert_in_delta | ||
assert_in_epsilon | ||
assert_includes | ||
assert_instance_of | ||
assert_kind_of | ||
assert_match | ||
assert_mock | ||
assert_nil | ||
assert_operator | ||
assert_output | ||
assert_path_exists | ||
assert_predicate | ||
assert_raises | ||
assert_respond_to | ||
assert_same | ||
assert_send | ||
assert_silent | ||
assert_throws | ||
refute | ||
refute_empty | ||
refute_equal | ||
refute_in_delta | ||
refute_in_epsilon | ||
refute_includes | ||
refute_instance_of | ||
refute_kind_of | ||
refute_match | ||
refute_nil | ||
refute_operator | ||
refute_path_exists | ||
refute_predicate | ||
refute_respond_to | ||
refute_same | ||
].to_set.freeze | ||
|
||
private | ||
|
||
def minitest_test_subclass?(class_node) | ||
minitest_test?(class_node.parent_class) | ||
end | ||
|
||
def_node_matcher :minitest_test?, <<~PATTERN | ||
(const (const nil? :Minitest) :Test) | ||
PATTERN | ||
|
||
def test_cases(class_node) | ||
class_def = class_node.body | ||
return [] unless class_def | ||
|
||
def_nodes = | ||
if class_def.def_type? | ||
[class_def] | ||
else | ||
class_def.each_child_node(:def) | ||
end | ||
|
||
def_nodes.select { |c| c.method_name.to_s.start_with?('test_') } | ||
end | ||
|
||
def assertion?(node) | ||
node.send_type? && ASSERTIONS.include?(node.method_name) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'test_helper' | ||
|
||
class MultipleAssertionsTest < Minitest::Test | ||
def setup | ||
configure_max_assertions(1) | ||
end | ||
|
||
def test_registers_offense_when_multiple_expectations | ||
assert_offense(<<~RUBY) | ||
class FooTest < Minitest::Test | ||
def test_asserts_twice | ||
^^^^^^^^^^^^^^^^^^ Test case has too many assertions [2/1]. | ||
assert_equal(foo, bar) | ||
assert_empty(array) | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
def test_checks_only_minitest_test_children | ||
assert_no_offenses(<<~RUBY) | ||
class FooTest | ||
def test_asserts_twice | ||
assert_equal(foo, bar) | ||
assert_empty(array) | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
def test_checks_only_test_case_methods | ||
assert_no_offenses(<<~RUBY) | ||
class FooTest < Minitest::Test | ||
# No 'test_' prefix | ||
def asserts_twice | ||
assert_equal(foo, bar) | ||
assert_empty(array) | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
def test_does_not_register_offense_when_single_assertion | ||
assert_no_offenses(<<~RUBY) | ||
class FooTest < Minitest::Test | ||
def test_asserts_once | ||
assert_equal(foo, bar) | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
def test_generates_a_todo_based_on_the_worst_violation | ||
inspect_source(<<-RUBY, @cop, 'test/foo_test.rb') | ||
class FooTest < Minitest::Test | ||
def test_asserts_once | ||
assert_equal(foo, bar) | ||
assert_equal(baz, bar) | ||
end | ||
def test_asserts_two_times | ||
assert_equal(foo, bar) | ||
assert_equal(baz, bar) | ||
end | ||
end | ||
RUBY | ||
|
||
assert_equal({ 'Max' => 2 }, @cop.config_to_allow_offenses[:exclude_limit]) | ||
end | ||
|
||
private | ||
|
||
def configure_max_assertions(max) | ||
cop_config = RuboCop::Config.new('Minitest/MultipleAssertions' => { | ||
'Max' => max | ||
}) | ||
@cop = RuboCop::Cop::Minitest::MultipleAssertions.new(cop_config) | ||
end | ||
end |