shuber-interface 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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