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 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 off
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
@@ -3,7 +3,7 @@ module Interface
3
3
  module Version
4
4
  MAJOR = 0
5
5
  MINOR = 0
6
- PATCH = 1
6
+ PATCH = 2
7
7
 
8
8
  # Returns a version string by joining <tt>MAJOR</tt>, <tt>MINOR</tt>, and <tt>PATCH</tt> with <tt>'.'</tt>
9
9
  #
data/lib/interface.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # Implementable interfaces in ruby
2
2
  module Interface
3
- autoload :Abstract, 'interface/abstract'
4
- autoload :Version, 'interface/version'
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
- included_modules.select { |mod| mod.is_a?(Abstract) }
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
 
@@ -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
- assert_equal [Remote, MockInterface], Device.interfaces
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: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
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-08 00:00:00 -08:00
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