mutiny 0.2.8 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ffb1518ee7685be979e2c8126897e4adc1c1968a
4
- data.tar.gz: 8e7c06989301a0b963b975e66a377ed7292fa15e
3
+ metadata.gz: c8022ed2607c22424b82450df4d6677b0cca31f7
4
+ data.tar.gz: 5ae8a0cbc564142fd95b3dbea091c9de2743fe66
5
5
  SHA512:
6
- metadata.gz: 317e070449d82430923f27b231360bc3e881189e17dd6ebef48dcea0e5e4a69dbfc3a5921ead07f4de0492ff8899a374b9e119df9a41a082a80932ec72d4efc3
7
- data.tar.gz: 32d348132b09838e11cbde3e189b605cc4bf1fb45b8e956f4856411fdc5ef62a9d1342808eac796d75341df7cdb182fe1b08eb0c72385faecdd337c9ed79348f
6
+ metadata.gz: 260df403d7bd1d04c1516f08098792f31920ef4b15a6f98eafb1e97d79f7fb71090f84d766b003bd52798555a5de86aaef9519e769d7da7976da24ae6daeb7ad
7
+ data.tar.gz: f4e6fbfe0cc8a16a4d1055234babbae6f0f381dc4a4a1d332c1127d589a2d19cd4732c9c6dba48752fafc2cacc872468c07896bbd2889a5447aa906117c8c0ac
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mutiny (0.2.8)
4
+ mutiny (0.3.0)
5
5
  gli (~> 2.13.0)
6
- metamorpher (~> 0.2.5)
6
+ metamorpher (~> 0.2.6)
7
7
  parser (~> 2.2.2)
8
8
  unparser (~> 0.2.4)
9
9
 
@@ -51,7 +51,7 @@ GEM
51
51
  json (1.8.3)
52
52
  memoizable (0.4.2)
53
53
  thread_safe (~> 0.3, >= 0.3.1)
54
- metamorpher (0.2.5)
54
+ metamorpher (0.2.6)
55
55
  attributable (~> 0.1.0)
56
56
  parser (~> 2.2.2)
57
57
  unparser (~> 0.2.4)
@@ -1,5 +1,11 @@
1
1
  # Release History
2
2
 
3
+ ## v0.3.0 (18 March 2016)
4
+ * Implement more sophisticated test filtering, which works well with targeted tests (e.g., "List#empty should have size 0") and with generic tests (e.g., "Users should be able to log out").
5
+ * Capture more forms of stillborn mutant, including those arising from instances of `ScriptError`.
6
+ * Fix bug in which mutation would be repeated if there was more than one subject in a single source file.
7
+ * Fix bugs in mutations of hash literals and array literals.
8
+
3
9
  ## v0.2.8 (22 February 2016)
4
10
  * Prevent insertion operators from incorrectly mutating private and protected keywords.
5
11
  * Fix bugs in mutating overlapping ASTs (by upgrading to metamorpher v0.2.5).
@@ -4,8 +4,12 @@ module Mutiny
4
4
  module Analysis
5
5
  class Analyser
6
6
  class Default < self
7
+ def before_all(mutant_set)
8
+ @subject_set = Subjects::SubjectSet.new(mutant_set.subjects)
9
+ end
10
+
7
11
  def select_tests(mutant)
8
- integration.tests.for(mutant.subject) # TODO: is this correctly minimal?
12
+ integration.tests.filterable(@subject_set).for(mutant.subject)
9
13
  end
10
14
  end
11
15
  end
@@ -7,8 +7,6 @@ module Mutiny
7
7
  # This code originally based on Markus Schirp's implementation of Mutant::Integration::Rspec
8
8
  # https://github.com/mbj/mutant/blob/master/lib/mutant/integration/rspec.rb
9
9
  class Parser
10
- EXPRESSION_DELIMITER = " "
11
-
12
10
  def initialize(context = Context.new)
13
11
  @world = context.world
14
12
  end
@@ -26,9 +24,9 @@ module Mutiny
26
24
  def parse_example(example)
27
25
  metadata = example.metadata
28
26
  location = metadata.fetch(:location)
29
- expression = metadata.fetch(:full_description).split(EXPRESSION_DELIMITER, 2).first
27
+ name = metadata.fetch(:full_description)
30
28
 
31
- Test.new(location: location, expression: expression, example: example)
29
+ Test.new(location: location, name: name, example: example)
32
30
  end
33
31
  end
34
32
  end
@@ -6,7 +6,7 @@ module Mutiny
6
6
  def run
7
7
  report "Checking..."
8
8
 
9
- if test_set.empty?
9
+ if relevant_test_set.empty?
10
10
  report_invalid
11
11
 
12
12
  elsif test_run.passed?
@@ -25,7 +25,7 @@ module Mutiny
25
25
  end
26
26
 
27
27
  def report_warning
28
- report " At least one relevant test found (#{test_set.size} in total)"
28
+ report " At least one relevant test found (#{relevant_test_set.size} in total)"
29
29
  report " Not all relevant tests passed. The failing tests are:\n"
30
30
 
31
31
  failed_test_locations.each do |location|
@@ -37,7 +37,7 @@ module Mutiny
37
37
  end
38
38
 
39
39
  def report_valid
40
- report " At least one relevant test found (#{test_set.size} in total)"
40
+ report " At least one relevant test found (#{relevant_test_set.size} in total)"
41
41
  report " All relevant tests passed"
42
42
  report "Looks good!"
43
43
  end
@@ -50,12 +50,16 @@ module Mutiny
50
50
  test_run.failed_tests.locations
51
51
  end
52
52
 
53
- def test_set
54
- @test_set ||= configuration.integration.tests.for_all(environment.subjects)
53
+ def relevant_test_set
54
+ @test_set ||= complete_test_set.for_all(environment.subjects)
55
+ end
56
+
57
+ def complete_test_set
58
+ @complete_test_set ||= configuration.integration.tests.filterable(environment.subjects)
55
59
  end
56
60
 
57
61
  def test_run
58
- @test_run ||= configuration.integration.run(test_set)
62
+ @test_run ||= configuration.integration.run(relevant_test_set)
59
63
  end
60
64
  end
61
65
  end
@@ -27,7 +27,7 @@ module Mutiny
27
27
  # rubocop:disable Eval
28
28
  eval(code, TOPLEVEL_BINDING)
29
29
  # rubocop:enable Eval
30
- rescue
30
+ rescue StandardError, ScriptError
31
31
  @stillborn = true
32
32
  end
33
33
 
@@ -12,7 +12,7 @@ module Mutiny
12
12
  end
13
13
 
14
14
  def subjects
15
- SubjectSet.new(modules.select(&:relevant?).map(&:to_subject))
15
+ SubjectSet.new(modules.select(&:relevant?).map(&:to_subject)).per_file
16
16
  end
17
17
 
18
18
  private
@@ -3,7 +3,8 @@ module Mutiny
3
3
  class SubjectSet
4
4
  include Enumerable
5
5
  extend Forwardable
6
- def_delegators :@subjects, :each, :product
6
+ attr_reader :subjects
7
+ def_delegators :@subjects, :each, :product, :hash
7
8
 
8
9
  def initialize(subjects)
9
10
  @subjects = subjects
@@ -12,6 +13,23 @@ module Mutiny
12
13
  def names
13
14
  @names ||= map(&:name).sort
14
15
  end
16
+
17
+ def [](index)
18
+ subjects.detect { |s| s.name == index }
19
+ end
20
+
21
+ # Returns a new SubjectSet which contains only one subject per source file
22
+ # For source files that contain more than one subject (i.e., Ruby module),
23
+ # the subjects are ordered by name alphabetically and only the first is used
24
+ def per_file
25
+ self.class.new(group_by(&:path).values.map { |subjects| subjects.sort_by(&:name).first })
26
+ end
27
+
28
+ def eql?(other)
29
+ is_a?(other.class) && other.subjects == subjects
30
+ end
31
+
32
+ alias_method "==", "eql?"
15
33
  end
16
34
  end
17
35
  end
@@ -1,11 +1,21 @@
1
1
  module Mutiny
2
2
  module Tests
3
3
  class Test
4
- attr_reader :location, :expression
4
+ attr_reader :location, :name
5
5
 
6
- def initialize(location: nil, expression:)
6
+ def initialize(location: nil, name:)
7
7
  @location = location
8
- @expression = expression
8
+ @name = name
9
+ end
10
+
11
+ def eql?(other)
12
+ other.location == location && other.name == name
13
+ end
14
+
15
+ alias_method "==", "eql?"
16
+
17
+ def hash
18
+ [location, name].hash
9
19
  end
10
20
  end
11
21
  end
@@ -1,4 +1,6 @@
1
1
  require "forwardable"
2
+ require_relative "test_set/filterable"
3
+ require_relative "test_set/filter/default"
2
4
 
3
5
  module Mutiny
4
6
  module Tests
@@ -18,31 +20,38 @@ module Mutiny
18
20
  tests.map(&:location)
19
21
  end
20
22
 
21
- def for_all(subject_set)
22
- subset { |test| subject_set.names.include?(test.expression) }
23
- end
24
-
25
- def for(subject)
26
- subset { |test| subject.name == test.expression }
27
- end
28
-
29
23
  def subset(&block)
30
- self.class.new(tests.select(&block))
24
+ derive(tests.select(&block))
31
25
  end
32
26
 
33
27
  def take(n)
34
- self.class.new(tests.take(n))
28
+ derive(tests.take(n))
35
29
  end
36
30
 
37
31
  def eql?(other)
38
32
  is_a?(other.class) && other.tests == tests
39
33
  end
40
34
 
35
+ def filterable(subjects, filtering_strategy: Filter::Default)
36
+ extend(Filterable)
37
+ self.filter = filtering_strategy.new(subject_names: subjects.names)
38
+ self
39
+ end
40
+
41
41
  alias_method "==", "eql?"
42
42
 
43
43
  protected
44
44
 
45
45
  attr_reader :tests
46
+
47
+ def derive(tests)
48
+ self.class.new(tests).tap do |derived|
49
+ if respond_to?(:filter)
50
+ derived.extend(Filterable)
51
+ derived.filter = filter
52
+ end
53
+ end
54
+ end
46
55
  end
47
56
  end
48
57
  end
@@ -0,0 +1,19 @@
1
+ module Mutiny
2
+ module Tests
3
+ class TestSet
4
+ class Filter
5
+ def initialize(subject_names:)
6
+ @subject_names = subject_names
7
+ end
8
+
9
+ def related?(subject_name:, test_name:)
10
+ fail "Subclasses must implement Filter#related? for #{subject_name}, #{test_name}"
11
+ end
12
+
13
+ protected
14
+
15
+ attr_reader :subject_names
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "../filter"
2
+
3
+ module Mutiny
4
+ module Tests
5
+ class TestSet
6
+ class Filter
7
+ class Default < self
8
+ def related?(subject_name:, test_name:)
9
+ general?(test_name) || specific_to?(test_name, subject_name)
10
+ end
11
+
12
+ private
13
+
14
+ # Returns true if the test is applicable to no specific subject
15
+ # (e.g., is an end-to-end test)
16
+ def general?(test_name)
17
+ subject_names.none? { |subject_name| test_name.start_with?(subject_name) }
18
+ end
19
+
20
+ # Returns true if the test is specific to the given subject
21
+ # (e.g., is a unit test for the specified subject)
22
+ def specific_to?(test_name, subject_name)
23
+ test_name.start_with?(subject_name)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ require "forwardable"
2
+ require_relative "filter/default"
3
+
4
+ module Mutiny
5
+ module Tests
6
+ class TestSet
7
+ module Filterable
8
+ attr_accessor :filter
9
+
10
+ def for_all(subject_set)
11
+ subset { |test| subject_set.any? { |subject| related?(subject, test) } }
12
+ end
13
+
14
+ def for(subject)
15
+ subset { |test| related?(subject, test) }
16
+ end
17
+
18
+ private
19
+
20
+ def related?(subject, test)
21
+ filter.related?(subject_name: subject.name, test_name: test.name)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Mutiny
2
- VERSION = "0.2.8"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.add_runtime_dependency "parser", "~> 2.2.2"
22
22
  spec.add_runtime_dependency "unparser", "~> 0.2.4"
23
23
  spec.add_runtime_dependency "gli", "~> 2.13.0"
24
- spec.add_runtime_dependency "metamorpher", "~> 0.2.5"
24
+ spec.add_runtime_dependency "metamorpher", "~> 0.2.6"
25
25
 
26
26
  spec.add_development_dependency "bundler", "~> 1.11"
27
27
  spec.add_development_dependency "rake", "~> 10.4.2"
@@ -0,0 +1,27 @@
1
+ module Mutiny
2
+ module Subjects
3
+ describe SubjectSet do
4
+ context "per_file" do
5
+ it "should contain only the alphabetically first subject when more than one in a file" do
6
+ first = Subject.new(name: "Bar", path: "main.rb")
7
+ second = Subject.new(name: "Foo", path: "main.rb")
8
+ nested = Subject.new(name: "Foo::Bar", path: "main.rb")
9
+
10
+ all_subjects = SubjectSet.new([first, second, nested])
11
+
12
+ expect(all_subjects.per_file).to eq(SubjectSet.new([first]))
13
+ end
14
+
15
+ it "should contain all subjects when there are one-per-file" do
16
+ first = Subject.new(name: "Bar", path: "bar.rb")
17
+ second = Subject.new(name: "Foo", path: "foo.rb")
18
+ nested = Subject.new(name: "Foo::Bar", path: "foo/bar.rb")
19
+
20
+ all_subjects = SubjectSet.new([first, second, nested])
21
+
22
+ expect(all_subjects.per_file).to eq(all_subjects)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ module Mutiny
2
+ module Tests
3
+ class TestSet
4
+ class Filter
5
+ describe Default do
6
+ subject { Default.new(subject_names: %w(Max Min)) }
7
+
8
+ it "should return true when the test and subject have the same name" do
9
+ expect(subject.related?(subject_name: "Max", test_name: "Max")).to be_truthy
10
+ end
11
+
12
+ it "should return true when the test is for a specific method in the subject" do
13
+ expect(subject.related?(subject_name: "Max", test_name: "Max.run")).to be_truthy
14
+ end
15
+
16
+ it "should return true when the test is for a specific class method in the subject" do
17
+ expect(subject.related?(subject_name: "Max", test_name: "Max#run")).to be_truthy
18
+ end
19
+
20
+ it "should return false when the test and subject have different names" do
21
+ expect(subject.related?(subject_name: "Min", test_name: "Max")).to be_falsey
22
+ end
23
+
24
+ it "should return true when the test does not begin with a known subject" do
25
+ expect(subject.related?(subject_name: "Max", test_name: "system test")).to be_truthy
26
+ expect(subject.related?(subject_name: "Min", test_name: "system test")).to be_truthy
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,61 @@
1
+ module Mutiny
2
+ module Tests
3
+ class TestSet
4
+ describe Filterable do
5
+ context "for" do
6
+ let(:subjects) { subject_set_for("Max", "Min") }
7
+ let(:test_set) { test_set_for("Min", "Min#run", subjects: subjects) }
8
+
9
+ it "should return only those tests that are relevant to the subject" do
10
+ expected = test_set_for("Min", "Min#run", subjects: subjects)
11
+ expect(test_set.for(subjects["Min"])).to eq(expected)
12
+ end
13
+
14
+ it "should return no tests if none are relevant to the subject" do
15
+ expect(test_set.for(subjects["Max"])).to eq(TestSet.empty)
16
+ end
17
+ end
18
+
19
+ context "for all" do
20
+ let(:subjects) { subject_set_for("Max", "Min") }
21
+ let(:test_set) { test_set_for("Min", "Min#run", subjects: subjects) }
22
+
23
+ it "should remove irrelevant tests" do
24
+ expected = test_set.subset { |t| t.name != "Subtract" }
25
+
26
+ expect(test_set.for_all(subjects)).to eq(expected)
27
+ end
28
+
29
+ it "should return all relevant tests" do
30
+ expected = test_set.subset { |t| t.name.start_with?("Min") }
31
+
32
+ expect(test_set.for_all(subjects)).to eq(expected)
33
+ end
34
+
35
+ it "should return no tests when there are no tests" do
36
+ expect(TestSet.empty.filterable(subjects).for_all(subjects)).to eq(TestSet.empty)
37
+ end
38
+ end
39
+
40
+ def subject_set_for(*names)
41
+ Subjects::SubjectSet.new(names.map { |n| Subjects::Subject.new(name: n) })
42
+ end
43
+
44
+ def test_set_for(*expressions, subjects:)
45
+ TestSet.new(tests_for(*expressions))
46
+ .filterable(subjects, filtering_strategy: DummyStrategy)
47
+ end
48
+
49
+ def tests_for(*names)
50
+ names.map { |name| Test.new(name: name) }
51
+ end
52
+
53
+ class DummyStrategy < Filter
54
+ def related?(subject_name:, test_name:)
55
+ test_name.start_with?(subject_name)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ module Mutiny
2
+ module Tests
3
+ describe TestSet do
4
+ let(:tests) { %w(test1 test2 test3) }
5
+ subject { TestSet.new(tests) }
6
+
7
+ context "take" do
8
+ it "(n) should return TestSet containing first n tests" do
9
+ expect(subject.take(2)).to eq(TestSet.new(tests.take(2)))
10
+ end
11
+
12
+ it "(0) should return an empty TestSet" do
13
+ expect(subject.take(0)).to eq(TestSet.empty)
14
+ end
15
+
16
+ it "(n) of an empty TestSet should return an empty TestSet" do
17
+ expect(TestSet.empty.take(2)).to eq(TestSet.empty)
18
+ end
19
+ end
20
+
21
+ context "subset" do
22
+ it "should return TestSet containing tests that match condition" do
23
+ subset = subject.subset { |t| t.end_with? "2" }
24
+ expected = TestSet.new(tests.select { |t| t.end_with? "2" })
25
+
26
+ expect(subset).to eq(expected)
27
+ end
28
+
29
+ it "should return empty TestSet when no tests match condition" do
30
+ subset = subject.subset { |_| false }
31
+
32
+ expect(subset).to eq(TestSet.empty)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutiny
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis Rose
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-12 00:00:00.000000000 Z
11
+ date: 2016-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.2.5
61
+ version: 0.2.6
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.2.5
68
+ version: 0.2.6
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -248,6 +248,9 @@ files:
248
248
  - lib/mutiny/tests/test.rb
249
249
  - lib/mutiny/tests/test_run.rb
250
250
  - lib/mutiny/tests/test_set.rb
251
+ - lib/mutiny/tests/test_set/filter.rb
252
+ - lib/mutiny/tests/test_set/filter/default.rb
253
+ - lib/mutiny/tests/test_set/filterable.rb
251
254
  - lib/mutiny/version.rb
252
255
  - mutiny.gemspec
253
256
  - spec/integration/check_spec.rb
@@ -284,8 +287,11 @@ files:
284
287
  - spec/unit/pattern_spec.rb
285
288
  - spec/unit/subjects/environment/type_spec.rb
286
289
  - spec/unit/subjects/environment_spec.rb
290
+ - spec/unit/subjects/subject_set_spec.rb
287
291
  - spec/unit/subjects/subject_spec.rb
288
- - spec/unit/subjects/test_set_spec.rb
292
+ - spec/unit/tests/test_set/filter/default_spec.rb
293
+ - spec/unit/tests/test_set/filterable_spec.rb
294
+ - spec/unit/tests/test_set_spec.rb
289
295
  homepage: https://github.com/mutiny/mutiny
290
296
  licenses:
291
297
  - MIT
@@ -345,5 +351,8 @@ test_files:
345
351
  - spec/unit/pattern_spec.rb
346
352
  - spec/unit/subjects/environment/type_spec.rb
347
353
  - spec/unit/subjects/environment_spec.rb
354
+ - spec/unit/subjects/subject_set_spec.rb
348
355
  - spec/unit/subjects/subject_spec.rb
349
- - spec/unit/subjects/test_set_spec.rb
356
+ - spec/unit/tests/test_set/filter/default_spec.rb
357
+ - spec/unit/tests/test_set/filterable_spec.rb
358
+ - spec/unit/tests/test_set_spec.rb
@@ -1,75 +0,0 @@
1
- module Mutiny
2
- module Tests
3
- describe TestSet do
4
- let(:tests) { %w(test1 test2 test3) }
5
- subject { TestSet.new(tests) }
6
-
7
- context "take" do
8
- it "(n) should return TestSet containing first n tests" do
9
- expect(subject.take(2)).to eq(TestSet.new(tests.take(2)))
10
- end
11
-
12
- it "(0) should return an empty TestSet" do
13
- expect(subject.take(0)).to eq(TestSet.empty)
14
- end
15
-
16
- it "(n) of an empty TestSet should return an empty TestSet" do
17
- expect(TestSet.empty.take(2)).to eq(TestSet.empty)
18
- end
19
- end
20
-
21
- context "subset" do
22
- it "should return TestSet containing tests that match condition" do
23
- subset = subject.subset { |t| t.end_with? "2" }
24
- expected = TestSet.new(tests.select { |t| t.end_with? "2" })
25
-
26
- expect(subset).to eq(expected)
27
- end
28
-
29
- it "should return empty TestSet when no tests match condition" do
30
- subset = subject.subset { |_| false }
31
-
32
- expect(subset).to eq(TestSet.empty)
33
- end
34
- end
35
-
36
- context "for" do
37
- it "should return only those tests (whose expression) matches a subject" do
38
- subjects = subject_set_for("Max", "Min")
39
- test_set = test_set_for("Subtract", "Min", "Add")
40
-
41
- expect(test_set.for_all(subjects)).to eq(test_set.subset { |t| t.expression == "Min" })
42
- end
43
-
44
- it "should return multiple tests for a single subject" do
45
- subjects = subject_set_for("Min")
46
- test_set = test_set_for("Min", "Max", "Min", "Max", "Min")
47
-
48
- expect(test_set.for_all(subjects)).to eq(test_set.subset { |t| t.expression == "Min" })
49
- end
50
-
51
- it "should return no tests when there are no tests" do
52
- subjects = subject_set_for("Max", "Min")
53
- test_set = TestSet.empty
54
-
55
- expect(test_set.for_all(subjects)).to eq(TestSet.empty)
56
- end
57
-
58
- it "should return no tests when there are no relevant subjects" do
59
- subjects = subject_set_for("Max", "Min")
60
- test_set = test_set_for("Subtract", "Add")
61
-
62
- expect(test_set.for_all(subjects)).to eq(TestSet.empty)
63
- end
64
-
65
- def subject_set_for(*names)
66
- Subjects::SubjectSet.new(names.map { |n| Subjects::Subject.new(name: n) })
67
- end
68
-
69
- def test_set_for(*expressions)
70
- TestSet.new(expressions.map { |e| Test.new(expression: e) })
71
- end
72
- end
73
- end
74
- end
75
- end