thumblemonks-protest 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.markdown +42 -0
- data/Rakefile +11 -0
- data/lib/protest/assertion.rb +39 -0
- data/lib/protest/context.rb +47 -0
- data/lib/protest/macros.rb +9 -0
- data/lib/protest.rb +41 -0
- data/protest.gemspec +31 -0
- data/test/assertion_test.rb +41 -0
- data/test/context_test.rb +60 -0
- metadata +63 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Justin Knowlden, Gabriel Gironda, Dan Hodos, Thumble Monks
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Protest
|
2
|
+
|
3
|
+
An extremely fast-running, context-driven, unit testing framework.
|
4
|
+
|
5
|
+
context "Foo" do
|
6
|
+
setup do
|
7
|
+
# some setup stuff
|
8
|
+
@foo = Foo.new
|
9
|
+
end
|
10
|
+
|
11
|
+
asserts("this block returns true") do
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
asserts("this block returns a specific string").equals("my friend") do
|
16
|
+
@foo.your_mom
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
MORE TO COME
|
21
|
+
|
22
|
+
## You say, "OMG! Why did you write this?"
|
23
|
+
|
24
|
+
#### Some background, I guess
|
25
|
+
|
26
|
+
You start a new project. You get all excited. You're adding tests. You're adding factories. You're fixturating your setups. You're adding more tests. Your tests start slowing down, but you need to keep pushing because your backlog has a lot of new, nifty features in it. You've got 3000+ lines of test code, 2000+ assertions. Your tests are annoyingly slow. Your tests have become a burden.
|
27
|
+
|
28
|
+
I hate this and it happens a lot, even when I'm conscience that it's happening.
|
29
|
+
|
30
|
+
#### How Protest is different
|
31
|
+
|
32
|
+
Protest differs in that it does not rerun setup for each test in a context. Each assertion should not mangle the context and therefore not require setup to be run more than once. Contexts can be nested and setups inherited, but setup is called only once per context.
|
33
|
+
|
34
|
+
...
|
35
|
+
|
36
|
+
You say, "Ok, but Shoulda is like this!"
|
37
|
+
|
38
|
+
Well, it is ... sort of. I love Shoulda. It changed the way I coded. But, Shoulda is slow. Shoulda is based on Test::Unit. Shoulda reruns setups for every should. Shoulda could make my life even easier with some more expressiveness.
|
39
|
+
|
40
|
+
#### How Protest is the same
|
41
|
+
|
42
|
+
It defines a context. It prints .'s, F's, and E's when tests pass, fail, or error.
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Protest
|
2
|
+
class Failure < Exception; end
|
3
|
+
|
4
|
+
class Assertion
|
5
|
+
def initialize(description, &block)
|
6
|
+
@description = description
|
7
|
+
@boolean_expectation = true
|
8
|
+
expectation(true, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{@description}: expected [#{@expectation}]"
|
13
|
+
end
|
14
|
+
|
15
|
+
def not(&block)
|
16
|
+
@boolean_expectation = false
|
17
|
+
expectation(@expectation, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def equals(expectation, &block)
|
21
|
+
expectation(expectation, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(context)
|
25
|
+
actual = context.instance_eval(&@block)
|
26
|
+
assert(@expectation == actual, "#{context} asserted #{self}, but received [#{actual}] instead")
|
27
|
+
end
|
28
|
+
private
|
29
|
+
def expectation(expectation, &block)
|
30
|
+
@expectation = expectation
|
31
|
+
@block = block if block_given?
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def assert(expression, msg)
|
36
|
+
@boolean_expectation == expression || raise(Failure, msg)
|
37
|
+
end
|
38
|
+
end # Assertion
|
39
|
+
end # Protest
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Protest
|
2
|
+
class Context
|
3
|
+
attr_reader :assertions
|
4
|
+
def initialize(description, parent=nil)
|
5
|
+
@description = description
|
6
|
+
@assertions = []
|
7
|
+
@failures = []
|
8
|
+
@parent = parent
|
9
|
+
@setup = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
[@parent.to_s, @description].join(' ').strip
|
14
|
+
end
|
15
|
+
|
16
|
+
def context(description, &block)
|
17
|
+
Protest.context(description, self, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup(&block)
|
21
|
+
@setup = block
|
22
|
+
self.bootstrap(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def asserts(description, &block)
|
26
|
+
(assertions << Assertion.new(description, &block)).last
|
27
|
+
end
|
28
|
+
|
29
|
+
def run(writer)
|
30
|
+
assertions.each do |assertion|
|
31
|
+
begin
|
32
|
+
assertion.run(self)
|
33
|
+
writer.print '.'
|
34
|
+
rescue Protest::Failure => e
|
35
|
+
writer.print 'F'
|
36
|
+
@failures << e
|
37
|
+
end
|
38
|
+
end
|
39
|
+
@failures
|
40
|
+
end
|
41
|
+
|
42
|
+
def bootstrap(binder)
|
43
|
+
@parent.bootstrap(binder) if @parent
|
44
|
+
binder.instance_eval(&@setup) if @setup
|
45
|
+
end
|
46
|
+
end # Context
|
47
|
+
end # Protest
|
data/lib/protest.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'protest/context'
|
2
|
+
require 'protest/assertion'
|
3
|
+
require 'protest/macros'
|
4
|
+
|
5
|
+
module Protest
|
6
|
+
def self.contexts
|
7
|
+
@contexts ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.context(description, parent = nil, &block)
|
11
|
+
context = Context.new(description, parent)
|
12
|
+
context.instance_eval(&block)
|
13
|
+
(contexts << context).last
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.dequeue_context(context)
|
17
|
+
contexts.delete(context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.run(writer=nil)
|
21
|
+
writer ||= STDOUT
|
22
|
+
start = Time.now
|
23
|
+
failures = @contexts.map { |context| context.run(writer) }.flatten
|
24
|
+
running_time = Time.now - start
|
25
|
+
|
26
|
+
writer.puts "\n\n"
|
27
|
+
failures.each_with_index { |failure, idx|
|
28
|
+
message = ["##{idx + 1} - #{failure.to_s}"]
|
29
|
+
# message += failure.backtrace
|
30
|
+
writer.puts message.join("\n") + "\n\n"
|
31
|
+
} unless failures.empty?
|
32
|
+
assertions = @contexts.inject(0) { |acc, context| acc + context.assertions.length }
|
33
|
+
writer.puts "#{@contexts.length} contexts, #{assertions} assertions: #{"%0.6f" % running_time} seconds"
|
34
|
+
end
|
35
|
+
end # Protest
|
36
|
+
|
37
|
+
module Kernel
|
38
|
+
def context(*args, &block)
|
39
|
+
Protest.context(*args, &block)
|
40
|
+
end
|
41
|
+
end # Kernel
|
data/protest.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "protest"
|
3
|
+
s.version = "0.0.2"
|
4
|
+
s.date = "2009-06-28"
|
5
|
+
s.summary = "An extremely fast, expressive, and context-driven unit-testing framework"
|
6
|
+
s.email = %w[gus@gusg.us]
|
7
|
+
s.homepage = "http://github.com/thumblemonks/protest"
|
8
|
+
s.description = "An extremely fast, expressive, and context-driven unit-testing framework. A replacement for all other testing frameworks"
|
9
|
+
s.authors = %w[Justin\ Knowlden]
|
10
|
+
|
11
|
+
s.has_rdoc = false
|
12
|
+
s.rdoc_options = ["--main", "README.markdown"]
|
13
|
+
s.extra_rdoc_files = ["README.markdown"]
|
14
|
+
|
15
|
+
# run git ls-files to get an updated list
|
16
|
+
s.files = %w[
|
17
|
+
MIT-LICENSE
|
18
|
+
README.markdown
|
19
|
+
lib/protest.rb
|
20
|
+
lib/protest/assertion.rb
|
21
|
+
lib/protest/context.rb
|
22
|
+
lib/protest/macros.rb
|
23
|
+
protest.gemspec
|
24
|
+
]
|
25
|
+
|
26
|
+
s.test_files = %w[
|
27
|
+
Rakefile
|
28
|
+
test/assertion_test.rb
|
29
|
+
test/context_test.rb
|
30
|
+
]
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'protest'
|
2
|
+
|
3
|
+
context "any assertion" do
|
4
|
+
asserts("its description").equals("i will pass: expected [true]") do
|
5
|
+
Protest::Assertion.new("i will pass").to_s
|
6
|
+
end
|
7
|
+
end # any assertion
|
8
|
+
|
9
|
+
context "passing assertion" do
|
10
|
+
asserts("true is expected") do
|
11
|
+
Protest::Assertion.new("i will pass") { true }.run(Object.new)
|
12
|
+
end
|
13
|
+
|
14
|
+
asserts("provided block was executed and returned true") do
|
15
|
+
Protest::Assertion.new("i will pass").equals("foo bar") { "foo bar" }.run(Object.new)
|
16
|
+
end
|
17
|
+
|
18
|
+
asserts("false on denial") do
|
19
|
+
Protest::Assertion.new("i will fail").not { false }.run(Object.new)
|
20
|
+
end
|
21
|
+
|
22
|
+
asserts("expectation does not equal actual result") do
|
23
|
+
Protest::Assertion.new("i will fail").not.equals("foo") { "bar" }.run(Object.new)
|
24
|
+
end
|
25
|
+
end # passing assertion
|
26
|
+
|
27
|
+
context "failing assertion" do
|
28
|
+
setup do
|
29
|
+
@expected_message = "test context asserted failure: expected [true], but received [false] instead"
|
30
|
+
assertion = Protest::Assertion.new("failure") { false }
|
31
|
+
begin
|
32
|
+
assertion.run(Protest::Context.new("test context"))
|
33
|
+
rescue Protest::Failure => e
|
34
|
+
@result = e
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
asserts("failure message").equals(@expected_message) do
|
39
|
+
@result.to_s
|
40
|
+
end
|
41
|
+
end # failing assertion
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'protest'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
context "any context" do
|
5
|
+
denies("two contexts with same name are the same").equals(Protest::Context.new("a")) do
|
6
|
+
Protest::Context.new("a")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# Test Context
|
12
|
+
|
13
|
+
test_context = context "foo" do
|
14
|
+
setup { @test_counter = 0 }
|
15
|
+
asserts("a block returns true") { @test_counter += 1; true }
|
16
|
+
asserts("another block returns true") { @test_counter += 1; true }
|
17
|
+
end # A CONTEXT THAT IS DEQUEUED
|
18
|
+
|
19
|
+
context "test context" do
|
20
|
+
setup { Protest.dequeue_context(test_context) }
|
21
|
+
asserts("context description").equals("foo") { test_context.to_s }
|
22
|
+
asserts("assertion count").equals(2) { test_context.assertions.length }
|
23
|
+
|
24
|
+
asserts("setup runs only once").equals(2) do
|
25
|
+
test_context.run(StringIO.new)
|
26
|
+
test_context.instance_variable_get(:@test_counter)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Nested Context
|
32
|
+
|
33
|
+
inner_nested_context = nil
|
34
|
+
nested_context = context "foo" do
|
35
|
+
setup do
|
36
|
+
@test_counter = 0
|
37
|
+
end
|
38
|
+
asserts("a block returns true") { @test_counter += 1; true }
|
39
|
+
|
40
|
+
inner_nested_context = context("baz") do
|
41
|
+
setup { @test_counter += 10 }
|
42
|
+
end # A CONTEXT THAT IS DEQUEUED
|
43
|
+
end # A CONTEXT THAT IS DEQUEUED
|
44
|
+
|
45
|
+
context "nested context" do
|
46
|
+
setup do
|
47
|
+
Protest.dequeue_context(nested_context)
|
48
|
+
Protest.dequeue_context(inner_nested_context)
|
49
|
+
nested_context.run(StringIO.new)
|
50
|
+
inner_nested_context.run(StringIO.new)
|
51
|
+
end
|
52
|
+
|
53
|
+
asserts("inner context inherits parent context setup").equals(10) do
|
54
|
+
inner_nested_context.instance_variable_get(:@test_counter)
|
55
|
+
end
|
56
|
+
|
57
|
+
asserts("nested context name").equals("foo baz") do
|
58
|
+
inner_nested_context.to_s
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thumblemonks-protest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Knowlden
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-28 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: An extremely fast, expressive, and context-driven unit-testing framework. A replacement for all other testing frameworks
|
17
|
+
email:
|
18
|
+
- gus@gusg.us
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.markdown
|
25
|
+
files:
|
26
|
+
- MIT-LICENSE
|
27
|
+
- README.markdown
|
28
|
+
- lib/protest.rb
|
29
|
+
- lib/protest/assertion.rb
|
30
|
+
- lib/protest/context.rb
|
31
|
+
- lib/protest/macros.rb
|
32
|
+
- protest.gemspec
|
33
|
+
has_rdoc: false
|
34
|
+
homepage: http://github.com/thumblemonks/protest
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options:
|
37
|
+
- --main
|
38
|
+
- README.markdown
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
version:
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
requirements: []
|
54
|
+
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.2.0
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: An extremely fast, expressive, and context-driven unit-testing framework
|
60
|
+
test_files:
|
61
|
+
- Rakefile
|
62
|
+
- test/assertion_test.rb
|
63
|
+
- test/context_test.rb
|