bourne 1.0
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/LICENSE +19 -0
- data/README +59 -0
- data/Rakefile +61 -0
- data/lib/bourne.rb +2 -0
- data/lib/bourne/api.rb +82 -0
- data/lib/bourne/expectation.rb +23 -0
- data/lib/bourne/invocation.rb +14 -0
- data/lib/bourne/mock.rb +23 -0
- data/lib/bourne/mockery.rb +19 -0
- data/test/acceptance/acceptance_test_helper.rb +38 -0
- data/test/acceptance/mocha_example_test.rb +98 -0
- data/test/acceptance/spy_test.rb +134 -0
- data/test/acceptance/stubba_example_test.rb +102 -0
- data/test/execution_point.rb +36 -0
- data/test/matcher_helpers.rb +5 -0
- data/test/method_definer.rb +24 -0
- data/test/simple_counter.rb +13 -0
- data/test/test_helper.rb +19 -0
- data/test/test_runner.rb +47 -0
- data/test/unit/assert_received_test.rb +145 -0
- data/test/unit/expectation_test.rb +526 -0
- data/test/unit/have_received_test.rb +192 -0
- data/test/unit/invocation_test.rb +17 -0
- data/test/unit/mock_test.rb +329 -0
- data/test/unit/mockery_test.rb +163 -0
- metadata +128 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Joe Ferris and thoughtbot, inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
= Bourne
|
2
|
+
|
3
|
+
Bourne extends mocha to allow detailed tracking and querying of stub and mock
|
4
|
+
invocations. It allows test spies using the have_received rspec matcher and
|
5
|
+
assert_received for Test::Unit. Bourne was extracted from jferris-mocha, a fork
|
6
|
+
of mocha that adds test spies.
|
7
|
+
|
8
|
+
== Test Spies
|
9
|
+
|
10
|
+
Test spies are a form of test double that preserves the normal four-phase unit
|
11
|
+
test order and allows separation of stubbing and verification.
|
12
|
+
|
13
|
+
Using a test spy is like using a mocked expectation except that there are two steps:
|
14
|
+
|
15
|
+
1. Stub out a method for which you want to verify invocations
|
16
|
+
2. Use an assertion or matcher to verify that the stub was invoked correctly
|
17
|
+
|
18
|
+
== Examples
|
19
|
+
|
20
|
+
# rspec
|
21
|
+
|
22
|
+
mock.should have_received(:to_s)
|
23
|
+
Radio.should have_received(:new).with(1041)
|
24
|
+
radio.should have_received(:volume).with(11).twice
|
25
|
+
radio.should have_received(:off).never
|
26
|
+
|
27
|
+
# Test::Unit
|
28
|
+
|
29
|
+
assert_received(mock, :to_s)
|
30
|
+
assert_received(Radio, :new) {|expect| expect.with(1041) }
|
31
|
+
assert_received(radio, :volume) {|expect| expect.with(11).twice }
|
32
|
+
assert_received(radio, :off) {|expect| expect.never }
|
33
|
+
|
34
|
+
See Mocha::API for more information.
|
35
|
+
|
36
|
+
== Download
|
37
|
+
|
38
|
+
Rubygems:
|
39
|
+
gem install bourne
|
40
|
+
|
41
|
+
Github: http://github.com/thoughtbot/bourne/tree/master
|
42
|
+
|
43
|
+
== More Information
|
44
|
+
|
45
|
+
* RDoc[http://rdoc.info/projects/thoughtbot/bourne]
|
46
|
+
* Mocha mailing list[http://groups.google.com/group/mocha-developer?hl=en]
|
47
|
+
* Issues[http://github.com/thoughtbot/bourne/issues]
|
48
|
+
* GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS[http://giantrobots.thoughtbot.com]
|
49
|
+
|
50
|
+
== Author
|
51
|
+
|
52
|
+
Bourne was written by Joe Ferris. Mocha was written by James Mead. Several of
|
53
|
+
the test examples and helpers used in the Bourne test suite were copied
|
54
|
+
directly from Mocha.
|
55
|
+
|
56
|
+
Thanks to thoughtbot for inspiration, ideas, and funding. Thanks to James for
|
57
|
+
writing mocha.
|
58
|
+
|
59
|
+
Copyright 2010 Joe Ferris and thoughtbot[http://www.thoughtbot.com], inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
desc "Run all tests"
|
6
|
+
task 'default' => ['test:units', 'test:acceptance', 'test:performance']
|
7
|
+
|
8
|
+
namespace 'test' do
|
9
|
+
unit_tests = FileList['test/unit/**/*_test.rb']
|
10
|
+
acceptance_tests = FileList['test/acceptance/*_test.rb']
|
11
|
+
|
12
|
+
desc "Run unit tests"
|
13
|
+
Rake::TestTask.new('units') do |t|
|
14
|
+
t.libs << 'test'
|
15
|
+
t.test_files = unit_tests
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run acceptance tests"
|
19
|
+
Rake::TestTask.new('acceptance') do |t|
|
20
|
+
t.libs << 'test'
|
21
|
+
t.test_files = acceptance_tests
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Run performance tests"
|
25
|
+
task 'performance' do
|
26
|
+
require File.join(File.dirname(__FILE__), 'test', 'acceptance', 'stubba_example_test')
|
27
|
+
require File.join(File.dirname(__FILE__), 'test', 'acceptance', 'mocha_example_test')
|
28
|
+
iterations = 1000
|
29
|
+
puts "\nBenchmarking with #{iterations} iterations..."
|
30
|
+
[MochaExampleTest, StubbaExampleTest].each do |test_case|
|
31
|
+
puts "#{test_case}: #{benchmark_test_case(test_case, iterations)} seconds."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def benchmark_test_case(klass, iterations)
|
37
|
+
require 'benchmark'
|
38
|
+
require 'test/unit/ui/console/testrunner'
|
39
|
+
begin
|
40
|
+
require 'test/unit/ui/console/outputlevel'
|
41
|
+
silent_option = { :output_level => Test::Unit::UI::Console::OutputLevel::SILENT }
|
42
|
+
rescue LoadError
|
43
|
+
silent_option = Test::Unit::UI::SILENT
|
44
|
+
end
|
45
|
+
time = Benchmark.realtime { iterations.times { Test::Unit::UI::Console::TestRunner.run(klass, silent_option) } }
|
46
|
+
end
|
47
|
+
|
48
|
+
eval("$specification = #{IO.read('bourne.gemspec')}")
|
49
|
+
Rake::GemPackageTask.new($specification) do |package|
|
50
|
+
package.need_zip = true
|
51
|
+
package.need_tar = true
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Generate documentation.'
|
55
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
56
|
+
rdoc.rdoc_dir = 'rdoc'
|
57
|
+
rdoc.title = 'Bourne'
|
58
|
+
rdoc.options << '--line-numbers' << "--main" << "README"
|
59
|
+
rdoc.rdoc_files.include('README')
|
60
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
61
|
+
end
|
data/lib/bourne.rb
ADDED
data/lib/bourne/api.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'mocha/api'
|
2
|
+
require 'bourne/mockery'
|
3
|
+
|
4
|
+
module Mocha # :nodoc:
|
5
|
+
module API
|
6
|
+
# Asserts that the given mock received the given method.
|
7
|
+
#
|
8
|
+
# Examples:
|
9
|
+
#
|
10
|
+
# assert_received(mock, :to_s)
|
11
|
+
# assert_received(Radio, :new) {|expect| expect.with(1041) }
|
12
|
+
# assert_received(radio, :volume) {|expect| expect.with(11).twice }
|
13
|
+
def assert_received(mock, expected_method_name)
|
14
|
+
matcher = have_received(expected_method_name)
|
15
|
+
yield(matcher) if block_given?
|
16
|
+
assert matcher.matches?(mock), matcher.failure_message
|
17
|
+
end
|
18
|
+
|
19
|
+
class HaveReceived #:nodoc:
|
20
|
+
def initialize(expected_method_name)
|
21
|
+
@expected_method_name = expected_method_name
|
22
|
+
@expectations = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(method, *args, &block)
|
26
|
+
@expectations << [method, args, block]
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches?(mock)
|
31
|
+
if mock.respond_to?(:mocha)
|
32
|
+
@mock = mock.mocha
|
33
|
+
else
|
34
|
+
@mock = mock
|
35
|
+
end
|
36
|
+
|
37
|
+
@expectation = Expectation.new(@mock, @expected_method_name)
|
38
|
+
@expectations.each do |method, args, block|
|
39
|
+
@expectation.send(method, *args, &block)
|
40
|
+
end
|
41
|
+
@expectation.invocation_count = invocation_count
|
42
|
+
@expectation.verified?
|
43
|
+
end
|
44
|
+
|
45
|
+
def failure_message
|
46
|
+
@expectation.mocha_inspect
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def invocation_count
|
52
|
+
matching_invocations.size
|
53
|
+
end
|
54
|
+
|
55
|
+
def matching_invocations
|
56
|
+
invocations.select do |invocation|
|
57
|
+
@expectation.match?(invocation.method_name, *invocation.arguments)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def invocations
|
62
|
+
Mockery.instance.invocations.select do |invocation|
|
63
|
+
invocation.mock.equal?(@mock)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# :call-seq:
|
69
|
+
# should have_received(method).with(arguments).times(times)
|
70
|
+
#
|
71
|
+
# Ensures that the given mock received the given method.
|
72
|
+
#
|
73
|
+
# Examples:
|
74
|
+
#
|
75
|
+
# mock.should have_received(:to_s)
|
76
|
+
# Radio.should have_received(:new).with(1041)
|
77
|
+
# radio.should have_received(:volume).with(11).twice
|
78
|
+
def have_received(expected_method_name)
|
79
|
+
HaveReceived.new(expected_method_name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'mocha/expectation'
|
2
|
+
|
3
|
+
module Mocha # :nodoc:
|
4
|
+
# Extends Mocha::Expectation to record the full arguments and count whenver a
|
5
|
+
# stubbed or mocked method is invoked.
|
6
|
+
class Expectation # :nodoc:
|
7
|
+
attr_accessor :invocation_count
|
8
|
+
|
9
|
+
def invoke_with_args(args, &block)
|
10
|
+
Mockery.instance.invocation(@mock, method_name, args)
|
11
|
+
invoke_without_args(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :invoke_without_args, :invoke
|
15
|
+
alias_method :invoke, :invoke_with_args
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def method_name
|
20
|
+
@method_matcher.expected_method_name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Mocha # :nodoc:
|
2
|
+
# Used internally by Bourne extensions to Mocha. Represents a single
|
3
|
+
# invocation of a stubbed or mocked method. The mock, method name, and
|
4
|
+
# arguments are recorded and can be used to determine how a method was
|
5
|
+
# invoked.
|
6
|
+
class Invocation
|
7
|
+
attr_reader :mock, :method_name, :arguments
|
8
|
+
def initialize(mock, method_name, arguments)
|
9
|
+
@mock = mock
|
10
|
+
@method_name = method_name
|
11
|
+
@arguments = arguments
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/bourne/mock.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'mocha/mock'
|
2
|
+
require 'bourne/expectation'
|
3
|
+
|
4
|
+
module Mocha # :nodoc:
|
5
|
+
# Overwrites #method_missing on Mocha::Mock so pass arguments to
|
6
|
+
# Mocha::Expectation#invoke so that an Invocation can be created.
|
7
|
+
class Mock # :nodoc:
|
8
|
+
def method_missing(symbol, *arguments, &block)
|
9
|
+
if @responder and not @responder.respond_to?(symbol)
|
10
|
+
raise NoMethodError, "undefined method `#{symbol}' for #{self.mocha_inspect} which responds like #{@responder.mocha_inspect}"
|
11
|
+
end
|
12
|
+
if matching_expectation_allowing_invocation = @expectations.match_allowing_invocation(symbol, *arguments)
|
13
|
+
matching_expectation_allowing_invocation.invoke(arguments, &block)
|
14
|
+
else
|
15
|
+
if (matching_expectation = @expectations.match(symbol, *arguments)) || (!matching_expectation && !@everything_stubbed)
|
16
|
+
message = UnexpectedInvocation.new(self, symbol, *arguments).to_s
|
17
|
+
message << Mockery.instance.mocha_inspect
|
18
|
+
raise ExpectationError.new(message, caller)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mocha/mockery'
|
2
|
+
require 'bourne/mock'
|
3
|
+
require 'bourne/invocation'
|
4
|
+
|
5
|
+
module Mocha
|
6
|
+
# Used internally by Bourne extensions to Mocha when recording invocations of
|
7
|
+
# stubbed and mocked methods. You shouldn't need to interact with this module
|
8
|
+
# through normal testing, but you can access the full list of invocations
|
9
|
+
# directly if necessary.
|
10
|
+
class Mockery
|
11
|
+
def invocation(mock, method_name, args)
|
12
|
+
invocations << Invocation.new(mock, method_name, args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def invocations
|
16
|
+
@invocations ||= []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
require 'test_runner'
|
3
|
+
require 'mocha/configuration'
|
4
|
+
|
5
|
+
module AcceptanceTest
|
6
|
+
|
7
|
+
class FakeLogger
|
8
|
+
|
9
|
+
attr_reader :warnings
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@warnings = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def warn(message)
|
16
|
+
@warnings << message
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :logger
|
22
|
+
|
23
|
+
include TestRunner
|
24
|
+
|
25
|
+
def setup_acceptance_test
|
26
|
+
Mocha::Configuration.reset_configuration
|
27
|
+
@logger = FakeLogger.new
|
28
|
+
mockery = Mocha::Mockery.instance
|
29
|
+
@original_logger = mockery.logger
|
30
|
+
mockery.logger = @logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def teardown_acceptance_test
|
34
|
+
Mocha::Configuration.reset_configuration
|
35
|
+
Mocha::Mockery.instance.logger = @original_logger
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
require 'mocha'
|
3
|
+
|
4
|
+
class MochaExampleTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
class Rover
|
7
|
+
|
8
|
+
def initialize(left_track, right_track, steps_per_metre, steps_per_degree)
|
9
|
+
@left_track, @right_track, @steps_per_metre, @steps_per_degree = left_track, right_track, steps_per_metre, steps_per_degree
|
10
|
+
end
|
11
|
+
|
12
|
+
def forward(metres)
|
13
|
+
@left_track.step(metres * @steps_per_metre)
|
14
|
+
@right_track.step(metres * @steps_per_metre)
|
15
|
+
wait
|
16
|
+
end
|
17
|
+
|
18
|
+
def backward(metres)
|
19
|
+
forward(-metres)
|
20
|
+
end
|
21
|
+
|
22
|
+
def left(degrees)
|
23
|
+
@left_track.step(-degrees * @steps_per_degree)
|
24
|
+
@right_track.step(+degrees * @steps_per_degree)
|
25
|
+
wait
|
26
|
+
end
|
27
|
+
|
28
|
+
def right(degrees)
|
29
|
+
left(-degrees)
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait
|
33
|
+
while (@left_track.moving? or @right_track.moving?); end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_should_step_both_tracks_forward_ten_steps
|
39
|
+
left_track = mock('left_track')
|
40
|
+
right_track = mock('right_track')
|
41
|
+
steps_per_metre = 5
|
42
|
+
rover = Rover.new(left_track, right_track, steps_per_metre, nil)
|
43
|
+
|
44
|
+
left_track.expects(:step).with(10)
|
45
|
+
right_track.expects(:step).with(10)
|
46
|
+
|
47
|
+
left_track.stubs(:moving?).returns(false)
|
48
|
+
right_track.stubs(:moving?).returns(false)
|
49
|
+
|
50
|
+
rover.forward(2)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_should_step_both_tracks_backward_ten_steps
|
54
|
+
left_track = mock('left_track')
|
55
|
+
right_track = mock('right_track')
|
56
|
+
steps_per_metre = 5
|
57
|
+
rover = Rover.new(left_track, right_track, steps_per_metre, nil)
|
58
|
+
|
59
|
+
left_track.expects(:step).with(-10)
|
60
|
+
right_track.expects(:step).with(-10)
|
61
|
+
|
62
|
+
left_track.stubs(:moving?).returns(false)
|
63
|
+
right_track.stubs(:moving?).returns(false)
|
64
|
+
|
65
|
+
rover.backward(2)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_step_left_track_forwards_five_steps_and_right_track_backwards_five_steps
|
69
|
+
left_track = mock('left_track')
|
70
|
+
right_track = mock('right_track')
|
71
|
+
steps_per_degree = 5.0 / 90.0
|
72
|
+
rover = Rover.new(left_track, right_track, nil, steps_per_degree)
|
73
|
+
|
74
|
+
left_track.expects(:step).with(+5)
|
75
|
+
right_track.expects(:step).with(-5)
|
76
|
+
|
77
|
+
left_track.stubs(:moving?).returns(false)
|
78
|
+
right_track.stubs(:moving?).returns(false)
|
79
|
+
|
80
|
+
rover.right(90)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_should_step_left_track_backwards_five_steps_and_right_track_forwards_five_steps
|
84
|
+
left_track = mock('left_track')
|
85
|
+
right_track = mock('right_track')
|
86
|
+
steps_per_degree = 5.0 / 90.0
|
87
|
+
rover = Rover.new(left_track, right_track, nil, steps_per_degree)
|
88
|
+
|
89
|
+
left_track.expects(:step).with(-5)
|
90
|
+
right_track.expects(:step).with(+5)
|
91
|
+
|
92
|
+
left_track.stubs(:moving?).returns(false)
|
93
|
+
right_track.stubs(:moving?).returns(false)
|
94
|
+
|
95
|
+
rover.left(90)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|