minitest-meaningful 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []