minitest-meaningful 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0537b67234cc8e5b97f42ac7117068392946f88ca607b2b7ff6aa0fff95b7f1f
4
+ data.tar.gz: 49534eacac48a362598ca178ee5b91da40ab41da44f3ab132aecbdbfeb8d78c7
5
+ SHA512:
6
+ metadata.gz: 4248b5451112ca9ff2dd6e96d22404b8b0aa8b4cd23ebf11f6596e365587c1737210ec33680029c3c1b2520d87aa7002f2df71ec00c4f786ea55e0a1f143a4ba
7
+ data.tar.gz: b777b67082ea2a943b3c01d8fbb9b3760af8c2fdafe00cdec7ce209e0d7f8ab47e94c81c8cea663da0756b77d6f848937364ce9f7604ea63100ea70f8e8d06d8
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Minitest::Meaningful
2
+
3
+ This is a proof-of-concept Minitest plugin to help avoid writing completely meaningless tests. A meaningless test is one that always passes because of incorrect test setup. This plugin helps ensure tests aren't meaningless, by trying to make them fail.
4
+
5
+ For example, in a Rails app we might have a test that looks like this:
6
+
7
+ ```rb
8
+ test "#visible_comments excludes hidden comments" do
9
+ post = create(:post)
10
+ comment = create(:comment, status: "hidden")
11
+
12
+ refute_includes post.visible_comments, comment
13
+ end
14
+ ```
15
+
16
+ The `comment` record is completely unrelated to the `post` record. It doesn't matter what the `status` is, nor does it really matter what the `#visible_comments` implementation looks like, there's no reason this comment would ever be returned. It always passes, it's a false positive test.
17
+
18
+ This plugin adds an `important!` annotation to wrap the most important test setup variable. The test runner can then run the test both with and without evaluating that block. If the test passes when that `important!` block is not evaluated, then we know it is not meaningful.
19
+
20
+ ```rb
21
+ test "#visible_comments excludes hidden comments" do
22
+ post = create(:post)
23
+ comment = create(:comment)
24
+ important! { comment.update!(status: "hidden") }
25
+
26
+ refute_includes post.visible_comments, comment
27
+ end
28
+ ```
29
+
30
+ ## Installation
31
+
32
+ Install the gem and add to the application's Gemfile by executing:
33
+
34
+ $ bundle add minitest-meaningful
35
+
36
+ If bundler is not being used to manage dependencies, install the gem by executing:
37
+
38
+ $ gem install minitest-meaningful
39
+
40
+ ## Usage
41
+
42
+ Include the `Minitest::Meaningful` module in your test:
43
+
44
+ ```rb
45
+ class ExampleTest > Minitest::Test
46
+ include Minitest::Meaningful
47
+ end
48
+ ```
49
+
50
+ Annotate the most important test setup variable:
51
+
52
+ ```rb
53
+ def test_visible_comments_excludes_hidden_comments
54
+ post = create(:post)
55
+ comment = create(:comment)
56
+ important! { comment.update!(status: "hidden") }
57
+
58
+ refute_includes post.visible_comments, comment
59
+ end
60
+ ```
61
+
62
+ Assert the test should fail:
63
+
64
+ ```rb
65
+ assert_meaningful :test_visible_comments_excludes_hidden_comments
66
+ ```
67
+
68
+ Run the test with the `--meaningful` flag:
69
+
70
+ ```sh
71
+ $ ruby example_test.rb --meaningful
72
+ ```
73
+
74
+ ## Contributing
75
+
76
+ This is definitely a hacky proof-of-concept. If you can think of a better way to integrate this into Minitest test suites, or a way to more robustly identify false-positive tests, please let me know! Also, I hate the name—please suggest something better!
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest
4
+ module Meaningful
5
+ VERSION = "0.3.0"
6
+ end
7
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "meaningful/version"
4
+
5
+ module Minitest
6
+ module Meaningful
7
+ class << self
8
+ attr_accessor :enabled
9
+ end
10
+
11
+ MultipleAnnotationsError = Class.new(StandardError)
12
+
13
+ module ClassMethods
14
+ def runnable_methods
15
+ if Meaningful.enabled
16
+ meaningful_methods.keys
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def meaningful_methods
23
+ @meaningful_methods ||= {}
24
+ end
25
+
26
+ def assert_meaningful(*names)
27
+ names.each do |name|
28
+ meaningful_methods[name.to_s] = caller
29
+ end
30
+ end
31
+ end
32
+
33
+ def self.included(base)
34
+ base.extend(ClassMethods)
35
+ end
36
+
37
+
38
+ def run
39
+ @should_fail = Meaningful.enabled
40
+
41
+ super
42
+
43
+ if @should_fail
44
+ if !@important_backtrace
45
+ exception = Minitest::Assertion.new("Expected test to have `important!` setup.")
46
+ exception.set_backtrace(self.class.meaningful_methods[name.to_s])
47
+ self.failures << exception
48
+ elsif !@did_fail
49
+ exception = Minitest::Assertion.new("Expected test to fail without `important!` setup.")
50
+ exception.set_backtrace(@important_backtrace)
51
+ self.failures << exception
52
+ end
53
+ end
54
+
55
+ Result.from(self)
56
+ rescue MultipleAnnotationsError
57
+ exception = Minitest::Assertion.new("Test uses `important!` more than once.")
58
+ exception.set_backtrace(@important_backtrace)
59
+ self.failures << exception
60
+
61
+ Result.from(self)
62
+ end
63
+
64
+ def capture_exceptions
65
+ return super unless @should_fail
66
+
67
+ yield
68
+ rescue *Minitest::Test::PASSTHROUGH_EXCEPTIONS
69
+ raise
70
+ rescue Assertion => e
71
+ @did_fail = true
72
+ end
73
+
74
+ def important!
75
+ if @should_fail
76
+ if @important_backtrace
77
+ raise MultipleAnnotationsError
78
+ else
79
+ @important_backtrace = caller
80
+ end
81
+ else
82
+ unless self.class.meaningful_methods.keys.include?(name.to_s)
83
+ exception = Minitest::Assertion.new("Test uses `important!` without `assert_meaningful`.")
84
+ raise exception
85
+ end
86
+
87
+ yield
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,13 @@
1
+ require "minitest/meaningful"
2
+
3
+ module Minitest
4
+ def self.plugin_meaningful_options(opts, options)
5
+ opts.on "--meaningful", "Expect tests not to pass with important test setup removed." do
6
+ options[:meaningful] = true
7
+ end
8
+ end
9
+
10
+ def self.plugin_meaningful_init(options)
11
+ Meaningful.enabled = options.fetch(:meaningful, false)
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minitest-meaningful
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Marshall
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ description:
28
+ email:
29
+ - thomas@thomasmarshall.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - Rakefile
36
+ - lib/minitest/meaningful.rb
37
+ - lib/minitest/meaningful/version.rb
38
+ - lib/minitest/meaningful_plugin.rb
39
+ homepage: https://github.com/thomasmarshall/minitest-meaningful
40
+ licenses: []
41
+ metadata:
42
+ homepage_uri: https://github.com/thomasmarshall/minitest-meaningful
43
+ source_code_uri: https://github.com/thomasmarshall/minitest-meaningful
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.6.0
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.5.3
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: A minitest plugin to prevent false positive tests.
63
+ test_files: []