shuber-interface 0.0.1 → 0.0.2
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/README.rdoc +40 -7
- data/lib/interface/test_helper.rb +42 -0
- data/lib/interface/version.rb +1 -1
- data/lib/interface.rb +22 -3
- data/test/interface_test.rb +31 -1
- data/test/test_helper.rb +9 -1
- metadata +5 -4
data/README.rdoc
CHANGED
@@ -18,7 +18,7 @@ Simply create a module with any methods that you'd like its implementing objects
|
|
18
18
|
# turns the device on
|
19
19
|
def on
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
# turns the device off
|
23
23
|
def off
|
24
24
|
end
|
@@ -29,24 +29,57 @@ Then use the <tt>implements</tt> method in your classes (also aliased as <tt>imp
|
|
29
29
|
class BrokenDevice
|
30
30
|
implements RemoteControl
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
BrokenDevice.new.on # NotImplementedError: BrokenDevice needs to implement 'on' for interface RemoteControl
|
34
|
-
|
34
|
+
|
35
35
|
class WorkingDevice < BrokenDevice
|
36
36
|
def on
|
37
37
|
@power = true
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
41
|
-
@power = false
|
40
|
+
def method_missing(method, *args)
|
41
|
+
method == :off ? @power = false : super
|
42
|
+
end
|
43
|
+
|
44
|
+
def respond_to_missing?(method, include_private)
|
45
|
+
method == :off
|
42
46
|
end
|
43
47
|
end
|
44
|
-
|
48
|
+
|
45
49
|
WorkingDevice.new.on # true
|
46
|
-
|
50
|
+
WorkingDevice.new.off # false
|
51
|
+
|
47
52
|
WorkingDevice.interfaces # [RemoteControl]
|
48
53
|
|
49
54
|
|
55
|
+
== Testing interface implementations
|
56
|
+
|
57
|
+
Include <tt>Interface::TestHelper</tt> in your test framework
|
58
|
+
|
59
|
+
Test::Unit::TestCase.send(:include, Interface::TestHelper)
|
60
|
+
|
61
|
+
Then you can use <tt>assert_implements_interfaces</tt> (aliased as <tt>assert_implements_interface</tt>) in your tests
|
62
|
+
|
63
|
+
class BrokenDeviceTest < Test::Unit::TestCase
|
64
|
+
def test_should_implement_interfaces
|
65
|
+
assert_implements_interfaces BrokenDevice.new # Failure: unimplemented interface methods for BrokenDevice: {Remote=>["off", "on"]}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
You can also explicitly list <tt>interfaces</tt> to test
|
70
|
+
|
71
|
+
module MockInterface end
|
72
|
+
|
73
|
+
class BrokenDevice
|
74
|
+
implements Remote, MockInterface
|
75
|
+
end
|
76
|
+
|
77
|
+
class BrokenDeviceTest < Test::Unit::TestCase
|
78
|
+
def test_should_implement_mock_interface
|
79
|
+
assert_implements_interface BrokenDevice.new, MockInterface # passes
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
50
83
|
== Note on Patches/Pull Requests
|
51
84
|
|
52
85
|
* Fork the project.
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Interface
|
2
|
+
# Contains interface testing methods to include in your test framework
|
3
|
+
module TestHelper
|
4
|
+
# Raises AssertionFailedError if <tt>object</tt> does not implement all methods from <tt>interfaces</tt>
|
5
|
+
#
|
6
|
+
# <tt>interfaces</tt> defaults to <tt>object.interfaces</tt> if none are specified
|
7
|
+
#
|
8
|
+
# Aliased as <tt>assert_implements_interface</tt>
|
9
|
+
#
|
10
|
+
# Example
|
11
|
+
#
|
12
|
+
# module Remote
|
13
|
+
# def on
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# def off
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# module MockInterface
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class BrokenDevice
|
24
|
+
# implements Remote, MockInterface
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# class BrokenDeviceTest < Test::Unit::TestCase
|
28
|
+
# def test_should_implement_interfaces
|
29
|
+
# assert_implements_interfaces BrokenDevice.new # Failure: unimplemented interface methods for BrokenDevice: {Remote=>["off", "on"]}
|
30
|
+
#
|
31
|
+
# # you can also explicitly pass interfaces to test
|
32
|
+
# assert_implements_interface BrokenDevice.new, MockInterface # passes
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
def assert_implements_interfaces(object, *interfaces)
|
36
|
+
interfaces = interfaces.empty? ? object.interfaces : interfaces.flatten
|
37
|
+
unimplemented_methods = object.unimplemented_methods.reject { |interface, methods| !interfaces.include?(interface) }
|
38
|
+
assert_block("unimplemented interface methods for #{object.class}: #{unimplemented_methods.inspect}") { unimplemented_methods.empty? }
|
39
|
+
end
|
40
|
+
alias_method :assert_implements_interface, :assert_implements_interfaces
|
41
|
+
end
|
42
|
+
end
|
data/lib/interface/version.rb
CHANGED
data/lib/interface.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# Implementable interfaces in ruby
|
2
2
|
module Interface
|
3
|
-
autoload :Abstract,
|
4
|
-
autoload :
|
3
|
+
autoload :Abstract, 'interface/abstract'
|
4
|
+
autoload :TestHelper, 'interface/test_helper'
|
5
|
+
autoload :Version, 'interface/version'
|
5
6
|
|
6
7
|
# Takes a module (or multiple in reverse order), extends it with <tt>Interface::Abstract</tt>, then includes it into the current object
|
7
8
|
#
|
@@ -29,7 +30,25 @@ module Interface
|
|
29
30
|
|
30
31
|
# Returns an array of interfaces implemented by the current object
|
31
32
|
def interfaces
|
32
|
-
|
33
|
+
klass = is_a?(Class) ? self : self.class
|
34
|
+
klass.included_modules.select { |mod| mod.is_a?(Abstract) }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a hash with each partially implemented <tt>interface</tt> as keys and an array of methods
|
38
|
+
# that the current object does not implement as values
|
39
|
+
def unimplemented_methods
|
40
|
+
self.class.interfaces.inject({}) do |hash, interface|
|
41
|
+
methods = unimplemented_methods_for(interface)
|
42
|
+
methods.empty? ? hash : hash.merge!(interface => methods)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns an array of methods from the specified <tt>interface</tt> that the current object does not implement
|
47
|
+
def unimplemented_methods_for(interface)
|
48
|
+
interface.instance_methods(false).reject do |method|
|
49
|
+
method = method.to_sym
|
50
|
+
(respond_to?(method, true) && self.method(method).owner != interface) || (respond_to?(:respond_to_missing?, true) && respond_to_missing?(method, true))
|
51
|
+
end.sort
|
33
52
|
end
|
34
53
|
end
|
35
54
|
|
data/test/interface_test.rb
CHANGED
@@ -25,6 +25,10 @@ class Device < BrokenDevice
|
|
25
25
|
def method_missing(method, *args)
|
26
26
|
method == :off ? @power = false : super
|
27
27
|
end
|
28
|
+
|
29
|
+
def respond_to_missing?(method, include_private)
|
30
|
+
method == :off
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
class InterfaceTest < Test::Unit::TestCase
|
@@ -50,7 +54,33 @@ class InterfaceTest < Test::Unit::TestCase
|
|
50
54
|
end
|
51
55
|
|
52
56
|
def test_should_return_interfaces
|
53
|
-
|
57
|
+
assert_all_equal [Remote, MockInterface], Device.interfaces, Device.new.interfaces
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_should_return_unimplemented_methods_for_interface
|
61
|
+
assert_equal ['off', 'on'], BrokenDevice.new.unimplemented_methods_for(Remote)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_should_return_empty_array_when_all_implemented
|
65
|
+
assert_equal [], Device.new.unimplemented_methods_for(Remote)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_return_hash_of_unimplemented_methods_with_interfaces
|
69
|
+
assert_equal({ Remote => ['off', 'on'] }, BrokenDevice.new.unimplemented_methods)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_should_return_empty_hash_when_all_interfaces_implemented
|
73
|
+
assert_equal Hash.new, Device.new.unimplemented_methods
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_should_pass_assertion
|
77
|
+
assert_implements_interfaces Device.new
|
78
|
+
assert_implements_interface BrokenDevice.new, MockInterface
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_should_fail_assertion
|
82
|
+
assert_raises(Test::Unit::AssertionFailedError) { assert_implements_interfaces BrokenDevice.new }
|
83
|
+
assert_raises(Test::Unit::AssertionFailedError) { assert_implements_interface BrokenDevice.new, Remote }
|
54
84
|
end
|
55
85
|
|
56
86
|
end
|
data/test/test_helper.rb
CHANGED
@@ -2,4 +2,12 @@ require 'test/unit'
|
|
2
2
|
|
3
3
|
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
4
|
$:.unshift(File.dirname(__FILE__))
|
5
|
-
require 'interface'
|
5
|
+
require 'interface'
|
6
|
+
|
7
|
+
Test::Unit::TestCase.class_eval do
|
8
|
+
include Interface::TestHelper
|
9
|
+
|
10
|
+
def assert_all_equal(expected, *results)
|
11
|
+
results.each { |result| assert_equal expected, result }
|
12
|
+
end
|
13
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shuber-interface
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Sean Huber
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-09 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -30,6 +30,7 @@ extra_rdoc_files: []
|
|
30
30
|
files:
|
31
31
|
- lib/interface.rb
|
32
32
|
- lib/interface/abstract.rb
|
33
|
+
- lib/interface/test_helper.rb
|
33
34
|
- lib/interface/version.rb
|
34
35
|
- lib/shuber-interface.rb
|
35
36
|
- MIT-LICENSE
|