schmetterling 0.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +48 -0
- data/Rakefile +11 -0
- data/lib/schmetterling.rb +7 -0
- data/lib/schmetterling/after.rb +19 -0
- data/lib/schmetterling/around.rb +19 -0
- data/lib/schmetterling/before.rb +19 -0
- data/lib/schmetterling/dsl.rb +21 -0
- data/lib/schmetterling/version.rb +3 -0
- data/schmetterling.gemspec +24 -0
- data/test/after_test.rb +53 -0
- data/test/around_test.rb +68 -0
- data/test/before_test.rb +63 -0
- data/test/dsl_test.rb +36 -0
- data/test/test_helper.rb +3 -0
- metadata +116 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Michał Łomnicki
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Schmetterling
|
2
|
+
|
3
|
+
Simple aspects implemented on Module#prepend.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
Ruby 2.0
|
8
|
+
|
9
|
+
## Highlights
|
10
|
+
|
11
|
+
* Support before/after/around advices
|
12
|
+
* Separate cross-cutting concerns
|
13
|
+
|
14
|
+
## How it works
|
15
|
+
|
16
|
+
Every time you add an advice a new anonymous module is created
|
17
|
+
and prepended to the adviced class.
|
18
|
+
|
19
|
+
The similar effect can be achived with alias method chaining.
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
class User
|
24
|
+
def recommend_book(book)
|
25
|
+
@recommended_books << book
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Twitter
|
30
|
+
def publish(token, message)
|
31
|
+
# ...
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'schmetterling/dsl'
|
36
|
+
class AppConfiguration
|
37
|
+
include Schmetterling::DSL
|
38
|
+
|
39
|
+
def initialize(twitter)
|
40
|
+
@twitter = twitter
|
41
|
+
end
|
42
|
+
|
43
|
+
def enable
|
44
|
+
After(User, :recommend_book) do |user, book|
|
45
|
+
twitter.publish(user.twitter_token, "Hey, I just read #{book.title}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Schmetterling
|
2
|
+
class After
|
3
|
+
|
4
|
+
def initialize(klass, method, advice=nil, &block)
|
5
|
+
advice ||= block
|
6
|
+
klass.send(:prepend, build_module_for_advice(method, advice))
|
7
|
+
end
|
8
|
+
|
9
|
+
def build_module_for_advice(adviced_method, advice)
|
10
|
+
Module.new do
|
11
|
+
define_method adviced_method do |*args|
|
12
|
+
super(*args)
|
13
|
+
advice.call(self, *args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Schmetterling
|
2
|
+
class Around
|
3
|
+
|
4
|
+
def initialize(klass, method, advice=nil, &block)
|
5
|
+
advice ||= block
|
6
|
+
klass.send(:prepend, build_module_for_advice(method, advice))
|
7
|
+
end
|
8
|
+
|
9
|
+
def build_module_for_advice(adviced_method, advice)
|
10
|
+
Module.new do
|
11
|
+
define_method adviced_method do |*args|
|
12
|
+
original_method = ->(args_for_original=args) { super(args_for_original) }
|
13
|
+
advice.call(original_method, self, *args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Schmetterling
|
2
|
+
class Before
|
3
|
+
|
4
|
+
def initialize(klass, method, advice=nil, &block)
|
5
|
+
advice ||= block
|
6
|
+
klass.send(:prepend, build_module_for_advice(method, advice))
|
7
|
+
end
|
8
|
+
|
9
|
+
def build_module_for_advice(adviced_method, advice)
|
10
|
+
Module.new do
|
11
|
+
define_method adviced_method do |*args|
|
12
|
+
advice.call(self, *args)
|
13
|
+
super(*args)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'schmetterling/before'
|
2
|
+
require 'schmetterling/after'
|
3
|
+
require 'schmetterling/around'
|
4
|
+
|
5
|
+
module Schmetterling
|
6
|
+
module DSL
|
7
|
+
|
8
|
+
def Before(*args, &block)
|
9
|
+
Schmetterling::Before.new(*args, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def After(*args, &block)
|
13
|
+
Schmetterling::After.new(*args, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def Around(*args, &block)
|
17
|
+
Schmetterling::Around.new(*args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'schmetterling/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "schmetterling"
|
8
|
+
spec.version = Schmetterling::VERSION
|
9
|
+
spec.authors = ["Michał Łomnicki"]
|
10
|
+
spec.email = ["michal.lomnicki@gmail.com"]
|
11
|
+
spec.description = %q{Aspects implemented on Ruby2.0 Class#prepend}
|
12
|
+
spec.summary = %q{Aspects implemented on Ruby2.0 Class#prepend}
|
13
|
+
spec.homepage = "https://github.com/x8/schmetterling"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "mocha", "~> 0.13"
|
24
|
+
end
|
data/test/after_test.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'schmetterling/after'
|
3
|
+
|
4
|
+
class AfterTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@adviced_class = Class.new do
|
8
|
+
def bar(calls)
|
9
|
+
calls << :method_called
|
10
|
+
end
|
11
|
+
end
|
12
|
+
@calls = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_executes_the_advice_after_adviced_method
|
16
|
+
Schmetterling::After.new(@adviced_class, :bar, -> (object, calls) { calls << :after_called })
|
17
|
+
@adviced_class.new.bar(@calls)
|
18
|
+
assert_equal [:method_called, :after_called], @calls
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_executes_multiple_advices
|
22
|
+
Schmetterling::After.new(@adviced_class, :bar, -> (object, calls) { calls << :after1_called })
|
23
|
+
Schmetterling::After.new(@adviced_class, :bar, -> (object, calls) { calls << :after2_called })
|
24
|
+
@adviced_class.new.bar(@calls)
|
25
|
+
assert_equal [:method_called, :after1_called, :after2_called], @calls
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_works_on_child_classes
|
29
|
+
@child_class = Class.new(@adviced_class)
|
30
|
+
Schmetterling::After.new(@adviced_class, :bar, -> (object, calls) { calls << :after_called })
|
31
|
+
@child_class.new.bar(@calls)
|
32
|
+
assert_equal [:method_called, :after_called], @calls
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_works_with_blocks
|
36
|
+
Schmetterling::After.new(@adviced_class, :bar) do |object, calls|
|
37
|
+
calls << :after_called
|
38
|
+
end
|
39
|
+
@adviced_class.new.bar(@calls)
|
40
|
+
assert_equal [:method_called, :after_called], @calls
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_advice_receives_the_adviced_object
|
44
|
+
@adviced_object = @adviced_class.new
|
45
|
+
Schmetterling::After.new(@adviced_class, :bar) do |object, calls|
|
46
|
+
assert_equal @adviced_object, object
|
47
|
+
calls << :after_called
|
48
|
+
end
|
49
|
+
@adviced_object.bar(@calls)
|
50
|
+
assert_equal [:method_called, :after_called], @calls
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/test/around_test.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'schmetterling/around'
|
3
|
+
|
4
|
+
class AroundTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@calls = []
|
8
|
+
@adviced_class = Class.new do
|
9
|
+
def bar(calls)
|
10
|
+
calls << :method_called
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_executes_the_advice_around_the_adviced_method
|
16
|
+
advice = -> (original_method, object, calls) {
|
17
|
+
calls << :before_called
|
18
|
+
original_method.call(calls)
|
19
|
+
calls << :after_called
|
20
|
+
}
|
21
|
+
Schmetterling::Around.new(@adviced_class, :bar, advice)
|
22
|
+
@adviced_class.new.bar(@calls)
|
23
|
+
assert_equal [:before_called, :method_called, :after_called], @calls
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_works_with_blocks
|
27
|
+
Schmetterling::Around.new(@adviced_class, :bar) do |original_method, object, calls|
|
28
|
+
calls << :before_called
|
29
|
+
original_method.call(calls)
|
30
|
+
calls << :after_called
|
31
|
+
end
|
32
|
+
@adviced_class.new.bar(@calls)
|
33
|
+
assert_equal [:before_called, :method_called, :after_called], @calls
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_changing_arguments
|
37
|
+
@changed_argument = []
|
38
|
+
advice = -> (original_method, object, calls) {
|
39
|
+
calls << :before_called
|
40
|
+
original_method.call(@changed_argument)
|
41
|
+
calls << :after_called
|
42
|
+
}
|
43
|
+
Schmetterling::Around.new(@adviced_class, :bar, advice)
|
44
|
+
@adviced_class.new.bar(@calls)
|
45
|
+
assert_equal [:before_called, :after_called], @calls
|
46
|
+
assert_equal [:method_called], @changed_argument
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_skipping_original_method
|
50
|
+
advice = -> (original_method, object, calls) {
|
51
|
+
calls << :evil_called
|
52
|
+
}
|
53
|
+
Schmetterling::Around.new(@adviced_class, :bar, advice)
|
54
|
+
@adviced_class.new.bar(@calls)
|
55
|
+
assert_equal [:evil_called], @calls
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_advice_receives_the_adviced_object
|
59
|
+
@adviced_object = @adviced_class.new
|
60
|
+
Schmetterling::Around.new(@adviced_class, :bar) do |original_method, object, calls|
|
61
|
+
assert_equal @adviced_object, object
|
62
|
+
original_method.call(calls)
|
63
|
+
end
|
64
|
+
@adviced_object.bar(@calls)
|
65
|
+
assert_equal [:method_called], @calls
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/test/before_test.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'schmetterling/before'
|
3
|
+
require 'schmetterling/after'
|
4
|
+
|
5
|
+
class BeforeTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@calls = []
|
9
|
+
@adviced_class = Class.new do
|
10
|
+
def bar(calls)
|
11
|
+
calls << :method_called
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_executes_the_advice_before_adviced_method
|
17
|
+
Schmetterling::Before.new(@adviced_class, :bar, -> (object, calls) { calls << :before_called })
|
18
|
+
@adviced_class.new.bar(@calls)
|
19
|
+
assert_equal [:before_called, :method_called], @calls
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_executes_multiple_advices
|
23
|
+
Schmetterling::Before.new(@adviced_class, :bar, -> (object, calls) { calls << :before1_called })
|
24
|
+
Schmetterling::Before.new(@adviced_class, :bar, -> (object, calls) { calls << :before2_called })
|
25
|
+
@adviced_class.new.bar(@calls)
|
26
|
+
assert_equal [:before2_called, :before1_called, :method_called], @calls
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_works_on_child_classes
|
30
|
+
@child_class = Class.new(@adviced_class)
|
31
|
+
Schmetterling::Before.new(@adviced_class, :bar, -> (object, calls) { calls << :before_called })
|
32
|
+
@child_class.new.bar(@calls)
|
33
|
+
assert_equal [:before_called, :method_called], @calls
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_works_with_blocks
|
37
|
+
Schmetterling::After.new(@adviced_class, :bar) do |object, calls|
|
38
|
+
calls << :after_called
|
39
|
+
end
|
40
|
+
@adviced_class.new.bar(@calls)
|
41
|
+
assert_equal [:method_called, :after_called], @calls
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_is_always_called_before_the_after_aspects
|
45
|
+
Schmetterling::After.new(@adviced_class, :bar, -> (_, calls) { calls << :after1_called })
|
46
|
+
Schmetterling::Before.new(@adviced_class, :bar, -> (_, calls) { calls << :before1_called })
|
47
|
+
Schmetterling::After.new(@adviced_class, :bar, -> (_, calls) { calls << :after2_called })
|
48
|
+
Schmetterling::Before.new(@adviced_class, :bar, -> (_, calls) { calls << :before2_called })
|
49
|
+
@adviced_class.new.bar(@calls)
|
50
|
+
assert_equal [:before2_called, :before1_called, :method_called, :after1_called, :after2_called], @calls
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_advice_receives_the_adviced_object
|
54
|
+
@adviced_object = @adviced_class.new
|
55
|
+
Schmetterling::Before.new(@adviced_class, :bar) do |object, calls|
|
56
|
+
assert_equal @adviced_object, object
|
57
|
+
calls << :before_called
|
58
|
+
end
|
59
|
+
@adviced_object.bar(@calls)
|
60
|
+
assert_equal [:before_called, :method_called], @calls
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
data/test/dsl_test.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'schmetterling/dsl'
|
3
|
+
|
4
|
+
class DslTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
class User
|
7
|
+
def foo; end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Configuration
|
11
|
+
include Schmetterling::DSL
|
12
|
+
|
13
|
+
def initialize(twitter)
|
14
|
+
@twitter = twitter
|
15
|
+
end
|
16
|
+
|
17
|
+
def enable
|
18
|
+
After(User, :foo) do |user|
|
19
|
+
@twitter.publish(user, "some text")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@twitter = mock("Twitter")
|
26
|
+
@configuration = Configuration.new(@twitter)
|
27
|
+
@configuration.enable
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_invokes_the_advice
|
31
|
+
@user = User.new
|
32
|
+
@twitter.expects(:publish).with(@user, "some text")
|
33
|
+
@user.foo
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: schmetterling
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michał Łomnicki
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
type: :development
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '1.3'
|
29
|
+
name: bundler
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
type: :development
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
name: rake
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
type: :development
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.13'
|
54
|
+
prerelease: false
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.13'
|
61
|
+
name: mocha
|
62
|
+
description: Aspects implemented on Ruby2.0 Class#prepend
|
63
|
+
email:
|
64
|
+
- michal.lomnicki@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/schmetterling.rb
|
75
|
+
- lib/schmetterling/after.rb
|
76
|
+
- lib/schmetterling/around.rb
|
77
|
+
- lib/schmetterling/before.rb
|
78
|
+
- lib/schmetterling/dsl.rb
|
79
|
+
- lib/schmetterling/version.rb
|
80
|
+
- schmetterling.gemspec
|
81
|
+
- test/after_test.rb
|
82
|
+
- test/around_test.rb
|
83
|
+
- test/before_test.rb
|
84
|
+
- test/dsl_test.rb
|
85
|
+
- test/test_helper.rb
|
86
|
+
homepage: https://github.com/x8/schmetterling
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options: []
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
requirements: []
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 1.8.24
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: Aspects implemented on Ruby2.0 Class#prepend
|
111
|
+
test_files:
|
112
|
+
- test/after_test.rb
|
113
|
+
- test/around_test.rb
|
114
|
+
- test/before_test.rb
|
115
|
+
- test/dsl_test.rb
|
116
|
+
- test/test_helper.rb
|