Class | Contract |
In: |
lib/contract/exception.rb
lib/contract/overrides.rb lib/contract/assertions.rb lib/contract/integration.rb lib/contract.rb |
Parent: | Test::Unit::TestCase |
Represents a contract between Objects as a collection of test cases. Objects are said to fulfill a contract if all test cases suceed. This is useful for ensuring that Objects your code is getting behave in a way that you expect them to behave so you can fail early or execute different logic for Objects with different interfaces.
The tests of the test suite will be run on a copy of the tested Object so you can safely test its behavior without having to fear data loss. By default Contracts obtain deep copies of Objects by serializing and unserializing them with Ruby’s Marshal functionality. This will work in most cases but can fail for Objects containing unserializable parts like Procs, Files or Sockets. In those cases it is currently of your responsibility to provide a fitting implementation by overwriting the Contract.deep_copy method. In the future the contract library might provide different implementations of it via Ruby’s mixin mechanism.
Version | = | current_version | The Version of the contract library you are using as String of the 1.2.3 form where the digits stand for release, major and minor version respectively. |
check_signatures | -> | check_signatures? |
check_fulfills | -> | check_fulfills? |
fulfilled_by? | -> | === |
You can use contracts in case … when statements or
in Module#signature checks. For example:
case obj when Numeric then obj + 1 when ListContract then obj + [1] end |
check_fulfills | [RW] |
Whether fulfills should be checked. This is enabled by default.
Note: If you want to change this you need to do so before doing any Module#fulfills calls or it will not be applied. It’s probably best set right after requiring the contract library. |
check_signatures | [RW] |
Whether signatures should be checked. By default signatures are checked
only when the application is run in $DEBUG mode. (By specifying the -d
switch on the invocation of Ruby.)
Note: If you want to change this you need to do so before doing any Module#signature calls or it will not be applied. It’s probably best set right after requiring the contract library. |
implications | [R] | Returns all implications of a given contract that were stated via calling Contract.implies. |
Tries to adapt the specified object to the specified type. Returns the old object if no suitable adaption route was found or if it already is of the specified type.
This will only use adaptions where the :to part is equal to the specified type. No multi-step conversion will be performed.
# File lib/contract/integration.rb, line 124 124: def self.adapt(object, type) 125: return object if type === object 126: 127: @adaptions[type].each do |adaption| 128: if adaption[:from] === object and 129: (adaption[:if].nil? or adaption[:if] === object) 130: then 131: result = adaption[:via].call(object) 132: return result if type === result 133: end 134: end 135: 136: return object 137: end
This method is used internally for getting a copy of Objects that the contract is checked against. By default it uses Ruby’s Marshal functionality for obtaining a copy, but this can fail if the Object contains unserializable parts like Procs, Files or Sockets. It is currently your responsibility to provide a fitting implementation of this by overwriting the method in case the default implementation does not work for you. In the future the contract library might offer different implementations for this via Ruby’s mixin mechanism.
# File lib/contract.rb, line 104 104: def self.deep_copy(object) 105: Marshal.load(Marshal.dump(object)) 106: end
Enforces that object implements this contract. If it does not an Exception will be raised. This is useful for example useful when you need to ensure that the arguments given to a method fulfill a given contract.
Note that using Module#enforce is a higher-level way of checking arguments and return values for the conformance of a given type. You might however still want to use Contract.enforce directly when you need more flexibility.
# File lib/contract.rb, line 65 65: def self.enforce(object) 66: reason = self.test(object) 67: raise reason if reason 68: end
Returns true if the given object fulfills this contract. This is useful for implementing dispatching mechanisms where you want to hit different code branches based on whether an Object has one or another interface.
# File lib/contract.rb, line 42 42: def self.fulfilled_by?(object) 43: self.test(object).nil? 44: end
Fulfilling this Contract (via Module#fulfills) implies that the Object is automatically compatible with the specified mixins which will then be included automatically. For example the Enumerable relationship could be expressed like this:
class EnumerableContract < Contract provides :each implies Enumerable end
# File lib/contract.rb, line 117 117: def self.implies(*mixins) 118: mixins.each do |mixin| 119: if not mixin.is_a?(Module) then 120: raise(TypeError, "wrong argument type #{mixin.class} for " + 121: "#{mixin.inspect} (expected Module)") 122: end 123: end 124: 125: @implications ||= Array.new 126: @implications += mixins 127: end
Tests that the tested Object provides the specified methods with the specified behavior.
If a block is supplied it will be evaluated in the context of the contract so @object will refer to the object being tested.
This can be used like this:
class ListContract < Contract provides :size do assert(@object.size >= 0, "#size should never be negative.") end provides :include? provides :each do count = 0 @object.each do |item| assert(@object.include?(item), "#each should only yield items that the list includes.") count += 1 end assert_equal(@object.size, count, "#each should yield #size items.") end end
# File lib/contract/assertions.rb, line 34 34: def self.provides(*symbols, &block) # :yields: 35: symbols.each do |symbol| 36: define_method("test_provides_#{symbol}".intern) do 37: assert_respond_to(@object, symbol) 38: instance_eval(&block) if block 39: end 40: end 41: end
Tests whether the given Object fulfils this contract.
Note: This will return the first reason for the Object not fulfilling the contract or nil in case it fulfills it.
# File lib/contract.rb, line 74 74: def self.test(object, return_all = false) 75: reasons = [] 76: 77: result = Test::Unit::TestResult.new 78: result.add_listener(Test::Unit::TestResult::FAULT) do |fault| 79: reason = Contract.fault_to_exception(fault, object, self) 80: return reason unless return_all 81: reasons << reason 82: end 83: 84: self.suite.run(result, deep_copy(object)) 85: 86: return reasons unless result.passed? 87: end
Same as Contract.test, but will return all reasons for the Object not fulfilling the contract in an Array or nil in case of fulfillment. (as an Array of Exceptions) or nil in the case it does fulfill it.
# File lib/contract.rb, line 92 92: def self.test_all(object) 93: test(object, true) 94: end