speck 1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/LICENSE.text +16 -0
- data/README.markdown +82 -0
- data/lib/speck.rb +143 -0
- data/lib/speck/battery.rb +28 -0
- data/lib/speck/check.rb +43 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTcxNDI0MDBkZDlkYWQ4OGJjOGI2N2RhYzQwM2Y5NDEyNmQzN2QyNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OTM5MTY0MDJjZDZiNDhlNmI3OWZlMmQ0M2FkNTBmMTgwODFkYThjYw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzVhMWIzMjk3NDU4ZTg5MGRlOWRkMzI1ODhiZDVlMzI1YzA0Y2ZmZmU1MTkz
|
10
|
+
MjVmMjBmMWRjNjBlOTkxZDMwNzI0MWFmOGQ4MDIwNzVhYTlhYTMyOTI4OTQ0
|
11
|
+
OTNiNjhlZDI0ZGYzOTU2NGE1YmUwYzRjMGJmMGMxNjI3NjU3Y2E=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MzIyODQ4MGZlYzRlNTNkMWU4MmRiYWJhZDkzYmE5NGZmN2U3ODVjNjI0Y2U0
|
14
|
+
N2Q1ZjJhNmZhMDU5OWYxMzZkMmVkYmYzZGUzMThjMDFkYzc4NmY5YTIxZGMw
|
15
|
+
ZTdhMjc5YmQ4YTU4NGJlZWQ2NjBmM2IzZjNlNWE2OWY2MjA0NGE=
|
data/LICENSE.text
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Copyright (C) 2013 elliottcable
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
4
|
+
associated documentation files (the "Software"), to deal in the Software without restriction,
|
5
|
+
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
6
|
+
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
7
|
+
furnished to do so, subject to the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
10
|
+
portions of the Software.
|
11
|
+
|
12
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
13
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
14
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
15
|
+
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
16
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
Speck
|
2
|
+
=====
|
3
|
+
This is a super-light library that attempts to enable a “literate” form of code-testing. This
|
4
|
+
library is a part of a suite of tools intended to be used together; alongside its
|
5
|
+
[brethren](#suite), it allows for “literate” specifications that look something like this:
|
6
|
+
|
7
|
+
Speck.new Dog do
|
8
|
+
some_breed = Dog::Shetland
|
9
|
+
|
10
|
+
Dog.new.check { it.breed == Dog::Labrador }
|
11
|
+
Dog.new(a_breed).check { it.breed == a_breed }
|
12
|
+
|
13
|
+
Speck.new Dog.class_method :bark do
|
14
|
+
a_dog = Dog.new
|
15
|
+
a_dog.bark.check { |rv| rv == 'arf' }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Ideas, feature requests, and bugs can be reported on the [GitHub][] issue tracker:
|
20
|
+
|
21
|
+
<http://github.com/elliottcable/Speck/issues>
|
22
|
+
|
23
|
+
[GitHub]: http://github.com/
|
24
|
+
|
25
|
+
Philosophy
|
26
|
+
----------
|
27
|
+
If you'll pay attention to the above example, you'll notice we do things a little differently around
|
28
|
+
here. The conventions that Speck is designed to enable include:
|
29
|
+
|
30
|
+
- first and foremost, we want the specification to be *source-code*, that is, we want it to read
|
31
|
+
like any Ruby code anywhere else. Our ‘checks’ (called assertions in other environments) simply
|
32
|
+
boil down to a `target` and a block to execute *against* that target. This is intended to be very
|
33
|
+
idiomatic Ruby-style code.
|
34
|
+
- secondary to the former, we also like it when the checks themselves read a bit like an English
|
35
|
+
sentence. For example, Slack ([see below](#suite)) uses the [it][] library to make the blocks look
|
36
|
+
a little more readable in the usual case.
|
37
|
+
- this is especially powerful, because Spark extracts the source-code responsible for each check,
|
38
|
+
and hilights it based on the execution of that check. Thus, specification code itself is self-
|
39
|
+
documenting output for your test suite.
|
40
|
+
- related to that, we tend to abstract the specifics of each check into variables, named in such a
|
41
|
+
way as to express the generic form of that specific element. If we're testing that a dog takes a
|
42
|
+
breed, but not *which* breed it takes, we place a relevant breed into `some_breed`. Similarly, we
|
43
|
+
might put an arbitrary numeric argument into a variable named `some_number` instead of using the
|
44
|
+
developer standards of `42` or `1337`. If, however, we were testing specific functionality related
|
45
|
+
to handling of the magic number `2`, as opposed to `1` or `3`, then we'd write the `2` directly
|
46
|
+
into the check-line.
|
47
|
+
- our specification sections directly include *what* they're testing, instead of a description
|
48
|
+
thereof in a string. Whether this is an class, an instance method, or something else entirely.
|
49
|
+
|
50
|
+
Generally speaking, we try to write *code* that self-describes what it's testing, instead of writing
|
51
|
+
lots of english sentences in a DSL to redundantly describe the code being tested. (Lookin' at you,
|
52
|
+
RSpec 'n friends.)
|
53
|
+
|
54
|
+
[it]: <http://github.com/elliottcable/it> "A sneakly library to expose iterated elements beautifully"
|
55
|
+
|
56
|
+
Suite
|
57
|
+
-----
|
58
|
+
I've intentionally designed Speck to be tiny and modular. Speck itself, here, is three extremely
|
59
|
+
brief files. To do more interesting things, and make specks easier to write, I've provided several
|
60
|
+
“sister libraries” that expand upon what Speck itself does:
|
61
|
+
|
62
|
+
- **[Slack][]**: Provides convenience methods for creating specks directly from various objects and
|
63
|
+
values, and various other comparators and tools to make *writing* specks easier.
|
64
|
+
- **[Spark][]**: Provides a [Rake][] task and other tools that beautifully *runs* your specks.
|
65
|
+
- **[Smock][]**: Provides a “mocking and spying” toolkit, for testing intricate code that you can't
|
66
|
+
easily decouple enough to test in minutae.
|
67
|
+
|
68
|
+
In no way are any of these *required* to use Speck, nor do any of them inter-depend upon eachother.
|
69
|
+
Mix-and-match as you desire. Or, y'know, write your own.
|
70
|
+
|
71
|
+
[Slack]: <http://github.com/elliottcable/slack>
|
72
|
+
[Spark]: <http://github.com/elliottcable/spark>
|
73
|
+
[Smock]: <http://github.com/elliottcable/smock>
|
74
|
+
|
75
|
+
[Rake]: <http://rake.rubyforge.org> "A Ruby DSL for `make`-like project tasks"
|
76
|
+
|
77
|
+
License
|
78
|
+
-------
|
79
|
+
This project is licensed very openly for your use, under a variation of the ‘MIT license.’
|
80
|
+
Details are available in [LICENSE][].
|
81
|
+
|
82
|
+
[LICENSE]: <./blob/master/LICENSE.text>
|
data/lib/speck.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# All library files are required at the bottom, because in this unique case we
|
2
|
+
# need `Speck` defined before we can use it to `Speck` anything.
|
3
|
+
|
4
|
+
class Speck
|
5
|
+
VERSION = 1
|
6
|
+
|
7
|
+
class <<self
|
8
|
+
|
9
|
+
##
|
10
|
+
# All specks not bound to an environment
|
11
|
+
attr_accessor :unbound
|
12
|
+
def unbound; @unbound ||= Array.new; end
|
13
|
+
|
14
|
+
# The current `Speck` execution stack
|
15
|
+
#
|
16
|
+
# @see #current
|
17
|
+
attr_accessor :stack
|
18
|
+
def stack; @stack ||= Array.new; end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Returns the top `Speck` on the execution stack (the one currently in the
|
22
|
+
# process of executing)
|
23
|
+
#
|
24
|
+
# When your `Speck`s are being run, there is a `stack` of `Speck` objects,
|
25
|
+
# consisting of the current nesting list of `Speck`s being run.
|
26
|
+
def current
|
27
|
+
stack.last
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Retreives the `Speck`s defiend for a given object, or, if none are
|
32
|
+
# defined, creates a new (empty) `Speck` for it.
|
33
|
+
#
|
34
|
+
# It’s worth noting that `Module#instance_method` returns a new
|
35
|
+
# `UnboundMethod` object every time you call it, even for the same method…
|
36
|
+
# so you can’t retreive Specks assigned to `UnboundMethods` via
|
37
|
+
# `Module#instance_method` with this method.
|
38
|
+
def for object
|
39
|
+
specks = Speck::on object
|
40
|
+
specks << Speck.new(object) if specks.empty?
|
41
|
+
return specks
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Functions like `Speck::for`, without creating a new `Speck` if none are
|
46
|
+
# defined.
|
47
|
+
#
|
48
|
+
# @see `Speck::for`
|
49
|
+
def on object
|
50
|
+
object.instance_variable_get(NinjaVar) ||
|
51
|
+
object.instance_variable_set(NinjaVar, Array.new)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# This instance variable will be set on target objects to point to the
|
58
|
+
# specks for that object
|
59
|
+
NinjaVar = :@_specks_
|
60
|
+
|
61
|
+
##
|
62
|
+
# The block to be executed
|
63
|
+
attr_accessor :block
|
64
|
+
|
65
|
+
##
|
66
|
+
# `Speck`s which consider this `Speck` to be their environment
|
67
|
+
attr_accessor :children
|
68
|
+
def children; @children ||= Array.new; end
|
69
|
+
|
70
|
+
##
|
71
|
+
# The `environment` of a `Speck` is another `Speck`, describing some sort of
|
72
|
+
# parent. The environment of a `Speck` describing an `UnboundMethod`, for
|
73
|
+
# instance, would most likely be a `Speck` describing a `Module` or `Class`
|
74
|
+
# on which that method is defined
|
75
|
+
attr_accessor :environment
|
76
|
+
def environment= object
|
77
|
+
(@environment ? @environment.children : Speck.unbound).delete self
|
78
|
+
|
79
|
+
speck = object.is_a?(Speck) || object.nil? ?
|
80
|
+
object : Speck::for(object).first
|
81
|
+
@environment = speck
|
82
|
+
|
83
|
+
(@environment ? @environment.children : Speck.unbound) << self
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# The checks involved in the current `Speck`
|
88
|
+
attr_accessor :checks
|
89
|
+
def checks; @checks ||= Array.new; end
|
90
|
+
|
91
|
+
##
|
92
|
+
# The `target` of a speck is usually the object which it is intended to
|
93
|
+
# describe (and test) the functionality of (Usually, this will be an
|
94
|
+
# instance of `Class`, `Module`, `Method` for “class” methods, or
|
95
|
+
# `UnboundMethod` for instance methods)
|
96
|
+
attr_accessor :target
|
97
|
+
def target= object
|
98
|
+
Speck::on(@target).delete self if @target and Speck::on(@target).include? self
|
99
|
+
|
100
|
+
@target = object
|
101
|
+
|
102
|
+
Speck::on(@target) << self if @target
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Creates a new `Speck`.
|
107
|
+
def initialize *environment, &block
|
108
|
+
self.target = environment.pop
|
109
|
+
|
110
|
+
environment = environment.inject do |prev, curr|
|
111
|
+
raise Exception::EnvironmentConflict if Speck::for(curr).first.environment and Speck::for(curr).first.environment != Speck::for(prev).first
|
112
|
+
Speck::for(curr).first.environment = Speck::for(prev).first
|
113
|
+
curr
|
114
|
+
end
|
115
|
+
|
116
|
+
self.environment = environment ? Speck::for(environment).first : Speck.current
|
117
|
+
@block = block || lambda {}
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Executes the `Speck`.
|
122
|
+
def execute
|
123
|
+
Speck.stack << self
|
124
|
+
@block.call
|
125
|
+
Speck.stack.pop
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
##
|
130
|
+
# A root class, and container class, for `Speck` exceptions.
|
131
|
+
class Exception < StandardError
|
132
|
+
# Raised when a `Check` fails
|
133
|
+
CheckFailed = Class.new self
|
134
|
+
|
135
|
+
# Raised when you attempt to stack an environment contrary to the existing
|
136
|
+
# environment
|
137
|
+
EnvironmentConflict = Class.new self
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
require 'speck/battery'
|
143
|
+
require 'speck/check'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Speck
|
2
|
+
##
|
3
|
+
# A `Battery` of `Speck`s is a set of specks which should be executed
|
4
|
+
# together, and which interdepend upon other `Speck`s in the battery.
|
5
|
+
# `Batteries` are recursive structures; that is, a `Battery` may have sub–
|
6
|
+
# `Batteries` for relevant objects.
|
7
|
+
class Battery
|
8
|
+
|
9
|
+
attr_reader :specks
|
10
|
+
attr_reader :targets
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@specks = Array.new
|
14
|
+
@targets = Hash.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def << speck
|
18
|
+
@specks << speck
|
19
|
+
@targets[speck.target] ||= Battery.new
|
20
|
+
return self
|
21
|
+
end
|
22
|
+
|
23
|
+
def [] object
|
24
|
+
return @targets[object]
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/speck/check.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
class Speck
|
2
|
+
##
|
3
|
+
# Represents a queued thing to be checked of some sort, within a `Speck`.
|
4
|
+
class Check
|
5
|
+
##
|
6
|
+
# The block to be executed, determining the success or failure of this
|
7
|
+
# particular `Check`. If it accepts an argument, the result of the target
|
8
|
+
# block will be passed as that argument.
|
9
|
+
attr_accessor :expectation
|
10
|
+
|
11
|
+
##
|
12
|
+
# The `target` of a `Check` is a block that returns an object to be passed
|
13
|
+
# to the `expectation`. It represents the object that the `Check` is
|
14
|
+
# intended to, well, check.
|
15
|
+
attr_accessor :target
|
16
|
+
|
17
|
+
##
|
18
|
+
# The status of the `Check`. `nil` indicates the `Check` hasn’t been
|
19
|
+
# executed, and `true` or `false` indicate the success of the latest
|
20
|
+
# execution
|
21
|
+
attr_accessor :status
|
22
|
+
def passed?
|
23
|
+
status == :passed
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(target = nil, &expectation)
|
27
|
+
@target = target.respond_to?(:call) ? target : ->{target}
|
28
|
+
@expectation = expectation
|
29
|
+
Speck.current.checks << self if Speck.current
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Executes this `Check`, raising an error if the expectation returns nil
|
34
|
+
# or false.
|
35
|
+
def execute
|
36
|
+
call = @expectation.arity == 0 ? ->{@target.call; @expectation.call} : ->{@expectation[@target.call]}
|
37
|
+
@status = call.call ? :passed : :failed
|
38
|
+
raise Exception::CheckFailed unless passed?
|
39
|
+
return self
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: speck
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- elliottcable [http://ell.io/tt]
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-02-27 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/speck/battery.rb
|
20
|
+
- lib/speck/check.rb
|
21
|
+
- lib/speck.rb
|
22
|
+
- README.markdown
|
23
|
+
- LICENSE.text
|
24
|
+
homepage: http://github.com/elliottcable/speck
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ! '>='
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.0.0
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: ! 'An (extremely light) source-code literate code-specification and -testing
|
48
|
+
system. (see: spark, slack)'
|
49
|
+
test_files: []
|
50
|
+
has_rdoc:
|