whitestone 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/LICENSE +16 -0
- data/README.txt +78 -0
- data/Rakefile +1 -0
- data/bin/whitestone +263 -0
- data/doc/README-snippets.rdoc +58 -0
- data/doc/whitestone.markdown +806 -0
- data/etc/aliases +5 -0
- data/etc/examples/example_1.rb +51 -0
- data/etc/examples/example_2.rb +51 -0
- data/etc/extra_tests/basic.rb +56 -0
- data/etc/extra_tests/error_should_not_also_fail.rb +17 -0
- data/etc/extra_tests/output_examples.rb +108 -0
- data/etc/extra_tests/output_examples_code.rb +38 -0
- data/etc/extra_tests/raise.rb +4 -0
- data/etc/extra_tests/realistic_example.rb +94 -0
- data/etc/extra_tests/specification_error.rb +8 -0
- data/etc/extra_tests/stop.rb +16 -0
- data/etc/extra_tests/terminate_suite.rb +56 -0
- data/etc/run-output-examples +1 -0
- data/etc/run-unit-tests +1 -0
- data/etc/ws +1 -0
- data/lib/whitestone.rb +710 -0
- data/lib/whitestone/assertion_classes.rb +418 -0
- data/lib/whitestone/auto.rb +20 -0
- data/lib/whitestone/custom_assertions.rb +252 -0
- data/lib/whitestone/include.rb +14 -0
- data/lib/whitestone/output.rb +335 -0
- data/lib/whitestone/support.rb +29 -0
- data/lib/whitestone/version.rb +3 -0
- data/test/_setup.rb +5 -0
- data/test/custom_assertions.rb +120 -0
- data/test/insulation.rb +202 -0
- data/test/whitestone_test.rb +616 -0
- data/whitestone.gemspec +31 -0
- metadata +125 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
(the ISC license)
|
2
|
+
|
3
|
+
'attest': Copyright 2010 Gavin Sinclair <gsinclair@gmail.com>
|
4
|
+
'dfect': Copyright 2009 Suraj N. Kurapati <sunaku@gmail.com>
|
5
|
+
|
6
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
7
|
+
purpose with or without fee is hereby granted, provided that the above
|
8
|
+
copyright notice and this permission notice appear in all copies.
|
9
|
+
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
11
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
12
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
13
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
14
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
15
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
16
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
Whitestone: simple and succint unit testing
|
2
|
+
-------------------------------------------
|
3
|
+
|
4
|
+
Whitestone is a unit-testing library, a derivative work of Suraj N. Karaputi's
|
5
|
+
"dfect" (http://snk.tuxfamily.org/lib/dfect), v2.1 (called "detest" as of v3.0).
|
6
|
+
|
7
|
+
Key features:
|
8
|
+
* terse assertion methods
|
9
|
+
* arbitrarily nested tests
|
10
|
+
* colorful and informative console output
|
11
|
+
* custom assertions
|
12
|
+
* an excellent test runner
|
13
|
+
|
14
|
+
Example of test code, demonstrating many assertions:
|
15
|
+
|
16
|
+
D "Person" do
|
17
|
+
# setup code run just once
|
18
|
+
D.<< { Person.initialize_tfn_lookup }
|
19
|
+
|
20
|
+
# setup code run for each test at this level
|
21
|
+
D.< { @p = Person.new("John", "Smith", 49) }
|
22
|
+
|
23
|
+
D "basic methods" do
|
24
|
+
Eq @p.first_name, "John"
|
25
|
+
Eq @p.last_name, "Smith"
|
26
|
+
Eq @p.age, 49
|
27
|
+
end
|
28
|
+
|
29
|
+
D "graceful error message if badly initialized" do
|
30
|
+
E(Person::Error) { Person.new(1, 2, 3) }
|
31
|
+
Mt Attest.exception.message, /invalid first name: 1/
|
32
|
+
end
|
33
|
+
|
34
|
+
D "equality" do
|
35
|
+
copy = @p.dup
|
36
|
+
T { copy == @p }
|
37
|
+
Eq copy, @p # equivalent to above line
|
38
|
+
end
|
39
|
+
|
40
|
+
D "interactions with system" do
|
41
|
+
D "tax file number is nil until set" do
|
42
|
+
N @p.tfn
|
43
|
+
F { @p.instance_variable_get :resolved_tfn }
|
44
|
+
@p.resolve_tfn
|
45
|
+
T { @p.instance_variable_get :resolved_tfn }
|
46
|
+
Mt @p.tfn, /\d\d\d-\d\d\d-\d\d\d/
|
47
|
+
end
|
48
|
+
|
49
|
+
D "address lookup is cached" do
|
50
|
+
a1 = @p.address
|
51
|
+
a2 = @p.address
|
52
|
+
Id a1, a2 # identical
|
53
|
+
Ko a1, Address
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end # "Person"
|
58
|
+
|
59
|
+
The assertion methods demonstrated were:
|
60
|
+
|
61
|
+
T -- assert true
|
62
|
+
F -- assert false
|
63
|
+
N -- assert object is nil
|
64
|
+
Eq -- assert two objects equal
|
65
|
+
Mt -- assert string matches regular expression
|
66
|
+
Id -- assert two objects are identical (same object)
|
67
|
+
E -- assert error is raised
|
68
|
+
Ko -- assert an object is kind_of a class/module
|
69
|
+
|
70
|
+
Other assertion methods:
|
71
|
+
|
72
|
+
Ft -- assert two floats are essentially equal
|
73
|
+
C -- assert object is thrown
|
74
|
+
|
75
|
+
All assertions can be negated by appending an exclamation mark.
|
76
|
+
|
77
|
+
See http://gsinclair.github.com/whitestone.html for full details (and screenshots).
|
78
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/whitestone
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'col'
|
4
|
+
|
5
|
+
class WhitestoneRunner
|
6
|
+
def banner()
|
7
|
+
@banner ||= %{
|
8
|
+
# bin/whitestone: A test runner worth using
|
9
|
+
#
|
10
|
+
# Key features:
|
11
|
+
#
|
12
|
+
# * Run it from the base directory of your project and it will include the
|
13
|
+
# 'lib' directory and run all tests in the 'test' directory
|
14
|
+
#
|
15
|
+
# * Easily specify a glob to restrict the test files that are run
|
16
|
+
#
|
17
|
+
# * Select which top-level test(s) to run using a regular expression
|
18
|
+
#
|
19
|
+
# * Keep your test code free of require statements: put common ones
|
20
|
+
# in test/_setup.rb; the runner loads 'whitestone' for you
|
21
|
+
#
|
22
|
+
# * Run each test file separately if you wish
|
23
|
+
#
|
24
|
+
# Usage examples:
|
25
|
+
#
|
26
|
+
# whitestone (run all test files under 'test' dir)
|
27
|
+
# whitestone circle arc (only run files whose path contains 'circle' or 'arc')
|
28
|
+
#
|
29
|
+
# whitestone --list (list the test files and exit)
|
30
|
+
# whitestone -t spec (run tests from the 'spec' directory, not 'test')
|
31
|
+
# whitestone -t spec widget (as above, but only files whose path contains 'widget')
|
32
|
+
# whitestone -f etc/a.rb (just run the one file; full path required)
|
33
|
+
# whitestone -e simple (only run top-level tests matching /simple/i)
|
34
|
+
#
|
35
|
+
# Formal options:
|
36
|
+
}.strip.gsub(/^#/, ' ')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
require 'optparse'
|
42
|
+
require 'ostruct'
|
43
|
+
require 'whitestone'
|
44
|
+
class ::Object; include Whitestone; end
|
45
|
+
|
46
|
+
END { WhitestoneRunner.new.run }
|
47
|
+
|
48
|
+
class WhitestoneRunner
|
49
|
+
def initialize
|
50
|
+
@options = OpenStruct.new(defaults())
|
51
|
+
@files = nil # will be set by generate_file_list
|
52
|
+
end
|
53
|
+
|
54
|
+
def run
|
55
|
+
parse_options(ARGV) # side-effect: @options
|
56
|
+
case @options.command
|
57
|
+
when :run then do_run
|
58
|
+
when :list then do_list
|
59
|
+
when :file then do_file
|
60
|
+
when :help then do_help
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# *--------------------------------------------------------------------*
|
65
|
+
# parse_options, defaults, parser
|
66
|
+
|
67
|
+
def parse_options(args)
|
68
|
+
parser().parse!(args)
|
69
|
+
# Having removed all options from args, whatever is left is our glob(s)
|
70
|
+
@options.globs = args.slice!(0..-1)
|
71
|
+
vmsg "Specified glob(s): #{@options.globs.join(' ')}"
|
72
|
+
rescue OptionParser::ParseError => e
|
73
|
+
$stderr.puts
|
74
|
+
$stderr.puts e
|
75
|
+
$stderr.puts
|
76
|
+
$stderr.puts parser()
|
77
|
+
exit 1
|
78
|
+
end
|
79
|
+
|
80
|
+
def defaults
|
81
|
+
@default ||= {
|
82
|
+
:command => :run, # :run, :example, :list, :file, :help
|
83
|
+
:globs => [],
|
84
|
+
:includes => ['lib'],
|
85
|
+
:testdir => 'test',
|
86
|
+
:pattern => nil,
|
87
|
+
:file => nil,
|
88
|
+
:run_separately => false,
|
89
|
+
:full_backtrace => false,
|
90
|
+
:verbose => false,
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def parser
|
95
|
+
@parser ||= OptionParser.new do |p|
|
96
|
+
p.banner = banner()
|
97
|
+
p.separator " "
|
98
|
+
p.separator " Commands"
|
99
|
+
p.on('--file', '-f FILE', "Run the specified file only",
|
100
|
+
" (_setup.rb won't be run)") \
|
101
|
+
{ |file| @options.command = :file; @options.file = file }
|
102
|
+
p.on('--list', '-l', "List the available test files and exit") \
|
103
|
+
{ @options.command = :list }
|
104
|
+
p.separator " "
|
105
|
+
p.separator " Modifiers"
|
106
|
+
p.on('-e', '--filter REGEX', String, "Select top-level test(s) to run") \
|
107
|
+
{ |pattern| @options.filter = Regexp.compile(pattern, Regexp::IGNORECASE) }
|
108
|
+
p.on('-I', '--include DIR,...', Array, "Add directories to library path",
|
109
|
+
" instead of 'lib'") \
|
110
|
+
{ |dirs| @options.includes = dirs }
|
111
|
+
p.on('--testdir', '-t DIR', "Specify the test directory (default 'test')") \
|
112
|
+
{ |dir| @options.testdir = dir }
|
113
|
+
p.on('--no-include', "Don't add any directory to library path") \
|
114
|
+
{ @options.includes = [] }
|
115
|
+
p.separator " "
|
116
|
+
p.separator " Running options"
|
117
|
+
p.on('--separate', '-s', "Run each test file separately") \
|
118
|
+
{ @options.run_separately = true }
|
119
|
+
p.on('--full-backtrace', "Suppress filtering of backtraces") \
|
120
|
+
{ @options.full_backtrace = true }
|
121
|
+
p.separator " "
|
122
|
+
p.separator " Miscellaneous"
|
123
|
+
p.on('-v', '--verbose') \
|
124
|
+
{ @options.verbose = true }
|
125
|
+
p.on('-h', '--help') \
|
126
|
+
{ @options.command = :help }
|
127
|
+
p.separator " "
|
128
|
+
p.separator " Manual: http://gsinclair.github.com/whitestone.html"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# *--------------------------------------------------------------------*
|
133
|
+
# do_run, do_example, do_list, do_file, do_help
|
134
|
+
|
135
|
+
def do_run
|
136
|
+
ready
|
137
|
+
aim
|
138
|
+
fire!
|
139
|
+
end
|
140
|
+
|
141
|
+
def do_list
|
142
|
+
testdir = @options.testdir
|
143
|
+
globs = @options.globs
|
144
|
+
suffix = globs.empty? ? "" : "matching #{globs.join(',')}"
|
145
|
+
puts "Ruby files under '#{testdir}' #{suffix}"
|
146
|
+
generate_file_list
|
147
|
+
puts @files.map { |f| " #{f}" }
|
148
|
+
end
|
149
|
+
|
150
|
+
def do_file
|
151
|
+
adjust_library_path
|
152
|
+
file = @options.file
|
153
|
+
vmsg "Running single file: #{file}"
|
154
|
+
unless File.file?(file)
|
155
|
+
error "File '#{file}' doesn't exist!"
|
156
|
+
end
|
157
|
+
load file
|
158
|
+
Whitestone.run(_whitestone_options)
|
159
|
+
end
|
160
|
+
|
161
|
+
def do_help
|
162
|
+
puts
|
163
|
+
puts parser()
|
164
|
+
puts
|
165
|
+
exit
|
166
|
+
end
|
167
|
+
|
168
|
+
# *--------------------------------------------------------------------*
|
169
|
+
# ready, aim, fire! (implementation of #run)
|
170
|
+
|
171
|
+
# Set up the library path.
|
172
|
+
def ready
|
173
|
+
includes = @options.includes
|
174
|
+
unless Array === includes
|
175
|
+
error "Invalid value for @options.includes: #{includes.inspect}"
|
176
|
+
end
|
177
|
+
includes.each do |dir|
|
178
|
+
$:.unshift dir
|
179
|
+
end
|
180
|
+
end
|
181
|
+
alias adjust_library_path ready
|
182
|
+
|
183
|
+
# Set @files to be the files we're running.
|
184
|
+
# Set @setup_file to be the setup file _setup.rb, if any.
|
185
|
+
def aim
|
186
|
+
testdir = @options.testdir
|
187
|
+
vmsg "Current directory: '#{Dir.pwd}'; Test: '#{testdir}'"
|
188
|
+
files = Dir["#{testdir}" + "/**/*.rb"]
|
189
|
+
setup = File.join(@options.testdir, "_setup.rb")
|
190
|
+
@setup_file = files.delete(setup)
|
191
|
+
globs = @options.globs
|
192
|
+
@files = _find_matching_files(files)
|
193
|
+
end
|
194
|
+
alias generate_file_list aim
|
195
|
+
|
196
|
+
# Load and run the tests. Run the setup file first if it exists.
|
197
|
+
def fire!
|
198
|
+
if @setup_file
|
199
|
+
vmsg "Running #{@setup_file} first"
|
200
|
+
load @setup_file
|
201
|
+
else
|
202
|
+
vmsg "No setup file #{@setup_file} to run"
|
203
|
+
end
|
204
|
+
if @options.run_separately
|
205
|
+
@files.each { |file|
|
206
|
+
_print_banner(file)
|
207
|
+
load file
|
208
|
+
Whitestone.run(_whitestone_options)
|
209
|
+
}
|
210
|
+
else
|
211
|
+
@files.each { |file|
|
212
|
+
vmsg "Loading file: #{file}"
|
213
|
+
load file
|
214
|
+
}
|
215
|
+
Whitestone.run(_whitestone_options)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def _find_matching_files(files)
|
220
|
+
globs = @options.globs
|
221
|
+
if globs.empty?
|
222
|
+
files
|
223
|
+
else
|
224
|
+
partial_re = Regexp.union(globs)
|
225
|
+
partial = files.grep(partial_re)
|
226
|
+
exact_re = %r[/#{partial_re}\.rb]
|
227
|
+
exact = files.grep(exact_re)
|
228
|
+
if exact.empty? then return partial else return exact end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def _print_banner(str)
|
233
|
+
ndashes = 76 - str.length
|
234
|
+
n1 = ndashes / 2
|
235
|
+
n2 = ndashes - n1
|
236
|
+
puts
|
237
|
+
puts
|
238
|
+
puts Col[" #{'=' * n1} #{str} #{'=' * n2}"].yb
|
239
|
+
end
|
240
|
+
|
241
|
+
# Return a hash suitable for passing to Whitestone.run
|
242
|
+
def _whitestone_options()
|
243
|
+
{ :filter => @options.filter, :full_backtrace => @options.full_backtrace }
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
# *--------------------------------------------------------------------*
|
248
|
+
# vmsg, error
|
249
|
+
|
250
|
+
def vmsg(str)
|
251
|
+
if @options.verbose
|
252
|
+
puts "[whitestone] #{str}"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def error(str)
|
257
|
+
$stderr.puts "[whitestone] Error!"
|
258
|
+
$stderr.puts "[whitestone] #{str}"
|
259
|
+
exit 1
|
260
|
+
end
|
261
|
+
|
262
|
+
end # class WhitestoneRunner
|
263
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
|
2
|
+
[[[ Text from README that I've deleted and may put back in textile format ]]]
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
* Assertions Eq, N, Mt; demonstrated thus:
|
7
|
+
|
8
|
+
D "Demonstrate new assertions" do
|
9
|
+
string = "example"
|
10
|
+
Eq string.upcase, "EXAMPLE"
|
11
|
+
Mt string, /a/
|
12
|
+
Mt! string, /aa/
|
13
|
+
Eq! string.size, 10
|
14
|
+
N string.match(/foo/)
|
15
|
+
end
|
16
|
+
|
17
|
+
Note the order: actual then expected (the opposite of test/unit).
|
18
|
+
|
19
|
+
* The global variable <tt>$dfect_test</tt> is set to the description of the
|
20
|
+
currently-running test block (e.g. "Demonstrate new assertions" above). The
|
21
|
+
aim is to allow you to set off the debugger when you want to by inserting a
|
22
|
+
line like this somewhere in your code:
|
23
|
+
|
24
|
+
debugger if $dfect_test =~ /assertions/
|
25
|
+
|
26
|
+
* Some aspects of the original are removed:
|
27
|
+
- Inochi project management
|
28
|
+
- bin/dfect test runner
|
29
|
+
- Emulation layers for minitest, rspec, test/unit
|
30
|
+
|
31
|
+
|
32
|
+
== How do I run my tests?
|
33
|
+
|
34
|
+
Include a test-runner in your project. Here's mine (<tt>test/_all.rb</tt>),
|
35
|
+
squashed down:
|
36
|
+
|
37
|
+
require 'rubygems'
|
38
|
+
require 'dfect'
|
39
|
+
require 'ruby-debug' # and 'ap' and any other things you need while debugging
|
40
|
+
include Dfect
|
41
|
+
require 'the_project_being_tested'
|
42
|
+
# The first argument allows us to decide which file(s) get loaded.
|
43
|
+
filter = Regexp.compile(ARGV.first || '.')
|
44
|
+
Dir['test/**/*.rb'].grep(filter).each do |file|
|
45
|
+
next if file == "test/_all.rb"
|
46
|
+
load file
|
47
|
+
end
|
48
|
+
Dfect.run
|
49
|
+
|
50
|
+
Combined with <tt>alias test='ruby -Ilib test/_all.rb'</tt> I can then do
|
51
|
+
|
52
|
+
$ test # Run all tests
|
53
|
+
$ test resource # Run the tests in file(s) matching /resource/
|
54
|
+
|
55
|
+
Perhaps greater support for such a runner will be included in a future version.
|
56
|
+
I've never used the runners provided by test frameworks so am not including one
|
57
|
+
at this stage.
|
58
|
+
|
@@ -0,0 +1,806 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Whitestone
|
4
|
+
---
|
5
|
+
|
6
|
+
# Whitestone: succinct and simple unit testing
|
7
|
+
|
8
|
+
> **attest** (v.) to bear witness to; certify; declare to be correct, true, or
|
9
|
+
> genuine; declare the truth of, in words or writing, esp. affirm in an official
|
10
|
+
> capacity: _to whitestone the truth of a statement_.
|
11
|
+
|
12
|
+
That's what I _wanted_ to call it, but the name 'attest' was taken. So here's
|
13
|
+
another definition:
|
14
|
+
|
15
|
+
> **whitestone** (n.) a nice word that happens to contain the substring 'test'.
|
16
|
+
|
17
|
+
**Contents**
|
18
|
+
|
19
|
+
* This will be replaced by a table of contents
|
20
|
+
{:toc}
|
21
|
+
|
22
|
+
## Overview
|
23
|
+
|
24
|
+
Whitestone saw its public release in January 2012 as an already-mature unit
|
25
|
+
testing library, being a derivative work of [Dfect][] v2.1.0 (renamed "Detest"
|
26
|
+
as of v3.0.0). Whitestone inherits Dfect's terse methods (D, F, E, C, T) and
|
27
|
+
adds extra assertions (Eq, N, Ko, Mt, Id, Ft), custom assertions, colourful
|
28
|
+
output on the terminal, and more.
|
29
|
+
|
30
|
+
[Dfect]: http://snk.tuxfamily.org/lib/dfect/
|
31
|
+
|
32
|
+
### Installation
|
33
|
+
|
34
|
+
$ [sudo] gem install whitestone
|
35
|
+
|
36
|
+
Source code is hosted on Github. See [Project details](#project_details).
|
37
|
+
|
38
|
+
### Methods
|
39
|
+
|
40
|
+
* Assertion methods: `T`, `F`, `N`, `Eq`, `Mt`, `Ko`, `Ft`, `Id`, `E`, `C`
|
41
|
+
* Other methods: `D`, `S`, `<`, `<<`, `>>`, `>`, `run`, `stop`, `current_test`,
|
42
|
+
`caught_value`, `exception`, `xT`, `xF`, etc.
|
43
|
+
|
44
|
+
### Benefits of Whitestone
|
45
|
+
|
46
|
+
* Terse testing methods that keeps the visual emphasis on your code.
|
47
|
+
* Nested tests with individual or shared setup and teardown code.
|
48
|
+
* Colourful and informative terminal output that lubricates the code, test, fix cycle.
|
49
|
+
* Clear report of which tests have passed and failed.
|
50
|
+
* An emphasis on informative failure and error messages. For instance, when two
|
51
|
+
long strings are expected to be equal but are not, the differences between them
|
52
|
+
are colour-coded.
|
53
|
+
* The name of the current test is available to you for setting conditional
|
54
|
+
breakpoints in the code you're testing.
|
55
|
+
* Very useful and configurable test runner (`whitestone`).
|
56
|
+
* Custom assertions to test complex objects and still get helpful failure
|
57
|
+
messages.
|
58
|
+
|
59
|
+
### Example of usage
|
60
|
+
|
61
|
+
Imagine you wrote the `Date` class in the Ruby standard library. The following
|
62
|
+
Whitestone code could be used to test some of it. All of these tests pass.
|
63
|
+
|
64
|
+
{% highlight ruby %}
|
65
|
+
|
66
|
+
require 'date'
|
67
|
+
|
68
|
+
D "Date" do
|
69
|
+
|
70
|
+
D.< { # setup for each block
|
71
|
+
@d = Date.new(1972, 5, 13)
|
72
|
+
}
|
73
|
+
|
74
|
+
D "#to_s" do
|
75
|
+
Eq @d.to_s, "1972-05-13"
|
76
|
+
end
|
77
|
+
|
78
|
+
D "#next" do
|
79
|
+
end_of_april = Date.new(2010, 4, 30)
|
80
|
+
start_of_may = Date.new(2010, 5, 1)
|
81
|
+
T { end_of_april.next == start_of_may }
|
82
|
+
end
|
83
|
+
|
84
|
+
D "day, month, year, week, day-of-year, etc." do
|
85
|
+
|
86
|
+
D.< { :extra_setup_for_these_three_blocks_if_required }
|
87
|
+
|
88
|
+
D "civil" do
|
89
|
+
Eq @d.year, 1972
|
90
|
+
Eq @d.month, 5
|
91
|
+
Eq @d.day, 13
|
92
|
+
end
|
93
|
+
D "commercial" do
|
94
|
+
Eq @d.cwyear, 1972
|
95
|
+
Eq @d.cweek, 19 # Commercial week-of-year
|
96
|
+
Eq @d.cwday, 6 # Commercial day-of-week (6 = Sat)
|
97
|
+
end
|
98
|
+
D "ordinal" do
|
99
|
+
Eq @d.yday, 134 # 134th day of the year
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
D "#leap?" do
|
104
|
+
[1984, 2000, 2400].each do |year|
|
105
|
+
T { Date.new(year, 6, 27).leap? }
|
106
|
+
end
|
107
|
+
[1900, 2007, 2100, 2401].each do |year|
|
108
|
+
F { Date.new(year, 12, 3).leap? }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
D "#succ creates new Date object" do
|
113
|
+
Ko @d.succ, Date
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
{% endhighlight %}
|
119
|
+
|
120
|
+
If you run `whitestone` on this code (e.g. `whitestone -f date_test.rb`) you get the
|
121
|
+
following output:
|
122
|
+
|
123
|
+
![Successful test run](img/whitestone1.png "Successful test run")
|
124
|
+
|
125
|
+
A dash (`-`) instead of `PASS` means no assertions were run in that scope. That
|
126
|
+
is, two of the "tests" are just containers for grouping related tests.
|
127
|
+
|
128
|
+
Changing two lines of the test code in order to force test failures, we get:
|
129
|
+
|
130
|
+
![Unsuccessful test run](img/whitestone2.png "Unsuccessful test run")
|
131
|
+
|
132
|
+
In both these cases, the error is in the testing code, not the tested code.
|
133
|
+
Nonetheless, it serves to demonstrate the kind of output Whitestone produces.
|
134
|
+
|
135
|
+
|
136
|
+
## Assertion methods
|
137
|
+
|
138
|
+
Method Nmenomic Definition, syopsis, examples
|
139
|
+
|
140
|
+
T True Asserts condition or block has a "true" value
|
141
|
+
T { code... }
|
142
|
+
T value
|
143
|
+
T { person.age > 18 }
|
144
|
+
T connection.closed?
|
145
|
+
|
146
|
+
F False Asserts condition or block has a "false" value
|
147
|
+
F { code... }
|
148
|
+
F value
|
149
|
+
F { date.leap }
|
150
|
+
F connection.open?
|
151
|
+
|
152
|
+
N Nil Asserts condition or block is specifically nil
|
153
|
+
N { code... }
|
154
|
+
N value
|
155
|
+
N { string.index('a') }
|
156
|
+
N person.title
|
157
|
+
|
158
|
+
Eq Equals Asserts an object is equal to its expected value
|
159
|
+
Eq OBJECT, VALUE
|
160
|
+
Eq person.name, "Theresa"
|
161
|
+
[see Note 1]
|
162
|
+
|
163
|
+
Mt Matches Asserts a string matches a regular expression
|
164
|
+
Mt STRING, REGEX
|
165
|
+
Mt REGEX, STRING
|
166
|
+
Mt "banana", /(an)+/
|
167
|
+
Mt /(an)+/, "banana"
|
168
|
+
[see Note 2]
|
169
|
+
|
170
|
+
Ko KindOf Asserts an object is kind_of? a certain class/module
|
171
|
+
Ko OBJECT, CLASS
|
172
|
+
Ko "foo", String
|
173
|
+
Ko (1..10), Enumerable
|
174
|
+
|
175
|
+
Ft Float Asserts a float is "essentially" equal to its expected value
|
176
|
+
Ft FLOAT, FLOAT [, EPSILON]
|
177
|
+
Ft Math::PI, 3.14159265
|
178
|
+
Ft Math::PI, 3.14 # will fail
|
179
|
+
Ft Math::PI, 3.14, 0.1 # will pass
|
180
|
+
The comparison used is relative, not absolute. The
|
181
|
+
difference divided by the expected value must be less
|
182
|
+
than 'epsilon' (default 0.000001).
|
183
|
+
|
184
|
+
Id Identity Asserts two objects have the same object_id
|
185
|
+
Id OBJECT, OBJECT
|
186
|
+
Id (x = "foo"), x
|
187
|
+
Id! "bar", "bar"
|
188
|
+
|
189
|
+
E Exception Asserts an exception is raised
|
190
|
+
E { code... }
|
191
|
+
E(Class1, Class2, ...) { code...}
|
192
|
+
E { "hello".frobnosticate }
|
193
|
+
E(NameError) { "hello".frobnosticate }
|
194
|
+
|
195
|
+
C Catches Asserts the given symbol is thrown
|
196
|
+
C(symbol) { code... }
|
197
|
+
C(:done) { some_method(5, :deep) }
|
198
|
+
|
199
|
+
**Note 1**: The order of arguments in `Eq OBJ, VALUE` is different from test/unit,
|
200
|
+
where the expected value comes first. To remember it, compare the following two
|
201
|
+
lines.
|
202
|
+
|
203
|
+
{% highlight ruby %}
|
204
|
+
|
205
|
+
T { person.name == "Theresa" }
|
206
|
+
Eq person.name, "Theresa"
|
207
|
+
|
208
|
+
{% endhighlight %}
|
209
|
+
|
210
|
+
The same is true for `Ko OBJ, CLASS`:
|
211
|
+
|
212
|
+
{% highlight ruby %}
|
213
|
+
|
214
|
+
T { object.kind_of? String }
|
215
|
+
Ko object, String
|
216
|
+
|
217
|
+
{% endhighlight %}
|
218
|
+
|
219
|
+
**Note 2**: Before the string is compared with the regular expression, it is
|
220
|
+
stripped of any color codes. This is an esoteric but convenient feature,
|
221
|
+
unlikely to cause any harm. If you specifically need to test for color codes,
|
222
|
+
there's always:
|
223
|
+
|
224
|
+
{% highlight ruby %}
|
225
|
+
|
226
|
+
T { str =~ /.../ }
|
227
|
+
|
228
|
+
{% endhighlight %}
|
229
|
+
|
230
|
+
|
231
|
+
### Negative assertions, queries and no-op methods
|
232
|
+
|
233
|
+
Each assertion method has three _modes_: assert, negate and query. Best
|
234
|
+
demonstrated by example:
|
235
|
+
|
236
|
+
{% highlight ruby %}
|
237
|
+
|
238
|
+
string = "foobar"
|
239
|
+
Eq string.upcase, "FOOBAR" # assert
|
240
|
+
Eq! string.length, 10 # negate
|
241
|
+
Eq? string.length, 10 # query -- returns true or false
|
242
|
+
# (doesn't assert anything)
|
243
|
+
|
244
|
+
{% endhighlight %}
|
245
|
+
|
246
|
+
For completeness, all of the **negative assertion methods** are briefly described
|
247
|
+
below.
|
248
|
+
|
249
|
+
Method Asserts that...
|
250
|
+
T! ...the condition/block does NOT have a true value
|
251
|
+
F! ...the condition/block does NOT have a false value
|
252
|
+
N! ...the condition/block is NOT nil
|
253
|
+
Eq! ...the object is NOT equal to the given value
|
254
|
+
Mt! ...the string does NOT match the regular expression
|
255
|
+
Ko! ...the object is NOT an instance of the given class/module
|
256
|
+
Ft! ...the float value is NOT "essentially" equal to the expected value
|
257
|
+
Id! ...the two objects are NOT identical
|
258
|
+
E! ...the code in the block does NOT raise an exception
|
259
|
+
(specific exceptions may be specified)
|
260
|
+
C! ...the code in the block does NOT throw the given symbol
|
261
|
+
|
262
|
+
Obviously there is not much use to `T!` and `F!`, but the rest are very
|
263
|
+
important.
|
264
|
+
|
265
|
+
Again for completeness, here is the list of **query methods**:
|
266
|
+
|
267
|
+
T? F? N? Eq? Mt? Ko? Ft? Id? E? C?
|
268
|
+
|
269
|
+
`E?` takes optional arguments: the Exception classes to query. `C?`, like `C`
|
270
|
+
and `C!`, takes a mandatory argument: the symbol that is expected to be thrown.
|
271
|
+
|
272
|
+
Finally, there are the **no-op methods**. These allow you to prevent an
|
273
|
+
assertion from running.
|
274
|
+
|
275
|
+
xT xF xN xEq # etc.
|
276
|
+
xT! xF! xN! xEq! # etc.
|
277
|
+
xT? xF? xN? xEq? # etc.
|
278
|
+
|
279
|
+
`xD` prevents an entire test from running.
|
280
|
+
|
281
|
+
|
282
|
+
## Other methods
|
283
|
+
|
284
|
+
Briefly:
|
285
|
+
* **D** introduces a test, _describing_ it.
|
286
|
+
* **S** shares data between test blocks.
|
287
|
+
* `<` and `>` do setup and teardown for each test block in the current scope.
|
288
|
+
* `<<` and `>>` do global setup and teardown for the current scope.
|
289
|
+
* `Whitestone.run` runs the currently-loaded test suite; `Whitestone.stop` aborts it.
|
290
|
+
If you use `require "whitestone/auto"` or the `whitestone` test runner, you
|
291
|
+
don't need to start the tests yourself.
|
292
|
+
* `Whitestone.current_test` is the name of the currently-running test.
|
293
|
+
* `Whitestone.caught_value` is the most recent value caught in a `C` assertion (see
|
294
|
+
above).
|
295
|
+
* `Whitestone.exception` is the most recently caught exception in an `E` assertion.
|
296
|
+
* `Whitestone.stats` is a hash containing the number of passes, failures, and
|
297
|
+
errors, and the total time taken to run the tests.
|
298
|
+
|
299
|
+
### Describing tests: D and D!
|
300
|
+
|
301
|
+
**D** is used to introduce a test. Tests can be nested. If you use **D!**
|
302
|
+
instead, the test will run in an _insulated_ environment: methods and instance
|
303
|
+
variables from the outside will not be seen within, and those defined inside
|
304
|
+
will not be seen without.
|
305
|
+
|
306
|
+
A note on classes, modules, methods, constants and instance variables:
|
307
|
+
* No matter where you define a class or constant, it is visible everywhere.
|
308
|
+
* Instance variables and methods defined in a test will be available to sibling
|
309
|
+
tests and nested tests, unless they are insulated.
|
310
|
+
* You can mix in a module using `extend Foo` (not `include Foo` as you are not
|
311
|
+
in a Class environment). This is the same as defining methods, so the normal
|
312
|
+
insulation applies.
|
313
|
+
|
314
|
+
Top-level tests are always insulated, so methods and instance variables defined
|
315
|
+
inside them will not be seen in other top-level tests.
|
316
|
+
|
317
|
+
### Sharing code: S, S! and S?
|
318
|
+
|
319
|
+
**S** is used to share code between tests. When called with a block, it stores
|
320
|
+
the code with the given identifier. When called without the block, it injects
|
321
|
+
the appropriate block into the current environment.
|
322
|
+
|
323
|
+
{% highlight ruby %}
|
324
|
+
|
325
|
+
S :data1 do
|
326
|
+
@text = "I must go down to the seas again..." }
|
327
|
+
end
|
328
|
+
|
329
|
+
D "Length" do
|
330
|
+
S :data1
|
331
|
+
T { @text.length > 10 }
|
332
|
+
end
|
333
|
+
|
334
|
+
D "Regex" do
|
335
|
+
S :data1
|
336
|
+
Mt /again/, @text
|
337
|
+
end
|
338
|
+
|
339
|
+
{% endhighlight %}
|
340
|
+
|
341
|
+
**S!** combines the two uses of **S**: it simultaneously shares the block while
|
342
|
+
injecting it into the current environment.
|
343
|
+
|
344
|
+
Finally, **S?** is simply a query to ascertain whether a certain block is shared
|
345
|
+
in the current scope.
|
346
|
+
|
347
|
+
{% highlight ruby %}
|
348
|
+
|
349
|
+
S :data2 do
|
350
|
+
@text = "Once upon a midnight dreary, while I pondered weak and weary..."
|
351
|
+
end
|
352
|
+
|
353
|
+
D! "Insulated test" do
|
354
|
+
S :data2
|
355
|
+
S? :data2 # -> true
|
356
|
+
S? :data1 # -> false
|
357
|
+
end
|
358
|
+
|
359
|
+
{% endhighlight %}
|
360
|
+
|
361
|
+
### Setup and teardown hooks
|
362
|
+
|
363
|
+
{% highlight ruby %}
|
364
|
+
|
365
|
+
D "outer test" do
|
366
|
+
D.< { puts "before each nested test -- e.g. prepare some data" }
|
367
|
+
D.> { puts "after each nested test -- e.g. close a file" }
|
368
|
+
|
369
|
+
D.<< { puts "before all nested tests -- e.g. create a database connection" }
|
370
|
+
D.>> { puts "after all nested tests -- e.g. close a database connection" }
|
371
|
+
|
372
|
+
D "inner test 1" do
|
373
|
+
# assertions and logic here
|
374
|
+
end
|
375
|
+
|
376
|
+
D "inner test 2" do
|
377
|
+
D.< { :setup_relevant_to_inner_test_2 }
|
378
|
+
# ...
|
379
|
+
end
|
380
|
+
|
381
|
+
# and so on
|
382
|
+
end
|
383
|
+
|
384
|
+
{% endhighlight %}
|
385
|
+
|
386
|
+
The hooks are easy to use and remember. However, note that they are not
|
387
|
+
top-level methods like `D()`, `T()`, `Eq()` etc. They are module methods in the
|
388
|
+
`Whitestone` module, which is aliases to `D` via the code `D = Whitestone` to
|
389
|
+
enable the convenient usage above.
|
390
|
+
|
391
|
+
### The name of the currently-running test
|
392
|
+
|
393
|
+
`Whitestone.current_test` is the name of the currently-running test. This allows
|
394
|
+
you to set useful conditional breakpoints deep within the library code that you
|
395
|
+
are testing. Here's an example scenario:
|
396
|
+
|
397
|
+
{% highlight ruby %}
|
398
|
+
|
399
|
+
def paragraphs
|
400
|
+
result = []
|
401
|
+
paragraph = []
|
402
|
+
loop do
|
403
|
+
if eof?
|
404
|
+
# ...
|
405
|
+
elsif current_line.empty?
|
406
|
+
if paragraph.empty?
|
407
|
+
debugger if Whitestone.current_test =~ /test1/
|
408
|
+
|
409
|
+
{% endhighlight %}
|
410
|
+
|
411
|
+
This method is called often during the course of tests, but something is failing
|
412
|
+
during a particular test and I want to debug it. If I start the debugger in the
|
413
|
+
_test_ code, then I need to step through a lot of code to reach the problem
|
414
|
+
area. Using `Whitestone.current_test`, I can start the debugger close to where the
|
415
|
+
problem actually is.
|
416
|
+
|
417
|
+
### The most recent exception and caught value
|
418
|
+
|
419
|
+
If the method you're testing throws a value and you want to test what that value
|
420
|
+
is, use `Whitestone.caught_value`:
|
421
|
+
|
422
|
+
{% highlight ruby %}
|
423
|
+
|
424
|
+
D "Testing the object that is thrown" do
|
425
|
+
array = [37, 42, 9, 105, 99, -1]
|
426
|
+
C(:found) { search array, :greater_than => 100 }
|
427
|
+
Eq Whitestone.caught_value, 105
|
428
|
+
end
|
429
|
+
|
430
|
+
{% endhighlight %}
|
431
|
+
|
432
|
+
`Whitestone.caught_value` will return the most recent caught value, but only
|
433
|
+
those values caught in the process of running a `C` assertion. If no value was
|
434
|
+
thrown with the symbol, `Whitestone.caught_value` will be `nil`.
|
435
|
+
|
436
|
+
If the method you're testing raises an error and you want to test the error
|
437
|
+
message, use `Whitestone.exception`:
|
438
|
+
|
439
|
+
{% highlight ruby %}
|
440
|
+
|
441
|
+
D "..." do
|
442
|
+
E(DomainSpecificError) { ...code... }
|
443
|
+
Mt Whitestone.exception.message, / ...pattern... /
|
444
|
+
end
|
445
|
+
|
446
|
+
{% endhighlight %}
|
447
|
+
|
448
|
+
|
449
|
+
## `whitestone`, the test runner
|
450
|
+
|
451
|
+
`whitestone` is a test runner worth using for many reasons:
|
452
|
+
|
453
|
+
* It knows that the code you're testing lives in `lib` and your test code lives
|
454
|
+
in `test` (but both of these are configurable).
|
455
|
+
* You can easily restrict the test files that are loaded.
|
456
|
+
* You can easily restrict the tests that are run.
|
457
|
+
* It loads common test code in `test/_setup.rb` before loading any test files.
|
458
|
+
* It will produce a separate report on each test file if you wish.
|
459
|
+
* You can run a specific test file that's not part of the test suite if you need
|
460
|
+
to. In this case `test/_setup.rb` won't be loaded.
|
461
|
+
|
462
|
+
Here is the information from `whitestone -h`:
|
463
|
+
|
464
|
+
Usage examples:
|
465
|
+
|
466
|
+
whitestone (run all test files under 'test' dir)
|
467
|
+
whitestone topic (run only files whose path contains 'topic')
|
468
|
+
|
469
|
+
whitestone --list (list the test files and exit)
|
470
|
+
whitestone -t spec (run tests from the 'spec' directory, not 'test')
|
471
|
+
whitestone -t spec widget (as above, but only files whose path contains 'widget')
|
472
|
+
whitestone -f etc/a.rb (just run the one file; full path required)
|
473
|
+
whitestone -e simple (only run top-level tests matching /simple/i)
|
474
|
+
|
475
|
+
Formal options:
|
476
|
+
|
477
|
+
Commands
|
478
|
+
-f, --file FILE Run the specified file only (_setup.rb won't be run)
|
479
|
+
-l, --list List the available test files and exit
|
480
|
+
|
481
|
+
Modifiers
|
482
|
+
-e, --filter REGEX Select top-level test(s) to run
|
483
|
+
-I, --include DIR,... Add directories to library path instead of 'lib'
|
484
|
+
-t, --testdir DIR Specify the test directory (default 'test')
|
485
|
+
--no-include Don't add any directory to library path
|
486
|
+
|
487
|
+
Running options
|
488
|
+
-s, --separate Run each test file separately
|
489
|
+
--full-backtrace Suppress filtering of backtraces
|
490
|
+
|
491
|
+
Miscellaneous
|
492
|
+
-v, --verbose
|
493
|
+
-h, --help
|
494
|
+
|
495
|
+
In most cases, you'd just run `whitestone`. If your tests live under `spec` instead
|
496
|
+
of `test`, you'd run `whitestone -t spec`. Sometimes you want to focus on one test
|
497
|
+
file, say `test/atoms/test_nucleus.rb`: run `whitestone nucleus`. A single test
|
498
|
+
file may contain many top-level tests, though. If you want to narrow it down
|
499
|
+
further: `whitestone -e display nucleus`. Finally, if you're working on some tests
|
500
|
+
in `etc/scratch.rb` that are not in your test suite (not under `test`): `whitestone
|
501
|
+
-f etc/scratch.rb`.
|
502
|
+
|
503
|
+
Don't forget the `{testdir}/_setup.rb` file. It may usefully contain:
|
504
|
+
|
505
|
+
* `require` statements common to all of your test cases
|
506
|
+
* helper methods for testing
|
507
|
+
* custom assertions
|
508
|
+
|
509
|
+
|
510
|
+
## Custom assertions
|
511
|
+
|
512
|
+
Whitestone allows you to define custom assertions. These are best shown by example.
|
513
|
+
Say your system has a `Person` class, as follows:
|
514
|
+
|
515
|
+
{% highlight ruby %}
|
516
|
+
|
517
|
+
class Person < Struct.new(:first, :middle, :last, :dob)
|
518
|
+
end
|
519
|
+
|
520
|
+
{% endhighlight %}
|
521
|
+
|
522
|
+
Now we create a `Person` object for testing.
|
523
|
+
|
524
|
+
{% highlight ruby %}
|
525
|
+
|
526
|
+
@person = Person.new("John", "William", "Smith", Date.new(1927, 3, 19))
|
527
|
+
|
528
|
+
{% endhighlight %}
|
529
|
+
|
530
|
+
_Without_ a custom assertion, this is how we might test it:
|
531
|
+
|
532
|
+
{% highlight ruby %}
|
533
|
+
|
534
|
+
Eq @person.first, "John"
|
535
|
+
Eq @person.middle, "William"
|
536
|
+
Eq @person.first, "Smith"
|
537
|
+
Eq @person.first, Date.new(1927, 3, 19)
|
538
|
+
|
539
|
+
{% endhighlight %}
|
540
|
+
|
541
|
+
If you need to test a lot of people, you might think to write a method:
|
542
|
+
|
543
|
+
{% highlight ruby %}
|
544
|
+
|
545
|
+
def test_person(person, string)
|
546
|
+
vals = string.split
|
547
|
+
Eq person.first, vals[0]
|
548
|
+
Eq person.middle, vals[1]
|
549
|
+
Eq person.last, vals[2]
|
550
|
+
Eq person.dob, Date.parse(vals[3])
|
551
|
+
end
|
552
|
+
|
553
|
+
test_person @person, "John Henry Smith 1927-03-19"
|
554
|
+
|
555
|
+
{% endhighlight %}
|
556
|
+
|
557
|
+
(The implementation of `test_person` splits up the string to make life easier.)
|
558
|
+
|
559
|
+
That's good, but if one of the assertions fails, as it will above, the message
|
560
|
+
you get is a low-level one, from one of the `Eq` lines, not from the
|
561
|
+
`test_person` line:
|
562
|
+
|
563
|
+
32 vals = string.split
|
564
|
+
33 Eq person.first, vals[0]
|
565
|
+
=> 34 Eq person.middle, vals[1]
|
566
|
+
35 Eq person.last, vals[2]
|
567
|
+
36 Eq person.dob, Date.parse(vals[3])
|
568
|
+
Equality test failed
|
569
|
+
Should be: "Henry"
|
570
|
+
Was: "William"
|
571
|
+
|
572
|
+
That's not as helpful as it could be.
|
573
|
+
|
574
|
+
_With_ a custom assertion, we can test it like this:
|
575
|
+
|
576
|
+
{% highlight ruby %}
|
577
|
+
|
578
|
+
T :person, @person, "John Henry Smith 1927-03-19"
|
579
|
+
|
580
|
+
{% endhighlight %}
|
581
|
+
|
582
|
+
Now the failure message will be:
|
583
|
+
|
584
|
+
47 D "Find the oldest person in the database" do
|
585
|
+
48 @person = OurSystem.db_query(:oldest_person)
|
586
|
+
=> 49 T :person, @person, "John Wiliam Smith 1927-03-19"
|
587
|
+
50 end
|
588
|
+
51
|
589
|
+
Person equality test failed: middle (details below)
|
590
|
+
Equality test failed
|
591
|
+
Should be: "Henry"
|
592
|
+
Was: "William"
|
593
|
+
|
594
|
+
That's much better. It's the _person_ test that failed, and we're told it was
|
595
|
+
the middle name that was the problem. With colourful output, it's even better.
|
596
|
+
|
597
|
+
Of course, we don't get the person custom assertion for free; we have to write
|
598
|
+
it. Here it is:
|
599
|
+
|
600
|
+
{% highlight ruby linenos %}
|
601
|
+
|
602
|
+
Whitestone.custom :person, {
|
603
|
+
:description => "Person equality",
|
604
|
+
:parameters => [ [:person, Person], [:string, String] ],
|
605
|
+
:run => proc {
|
606
|
+
f, m, l, dob = string.split
|
607
|
+
dob = Date.parse(dob)
|
608
|
+
test('first') { Eq person.first, f }
|
609
|
+
test('middle') { Eq person.middle, m }
|
610
|
+
test('last') { Eq person.last, l }
|
611
|
+
test('dob') { Eq person.dob, dob }
|
612
|
+
}
|
613
|
+
}
|
614
|
+
|
615
|
+
{% endhighlight %}
|
616
|
+
|
617
|
+
The method `Whitestone.custom` creates a custom assertion. The first parameter is
|
618
|
+
`:person`, the name of the assertion. The second parameter is a hash with keys
|
619
|
+
`:description`, `:parameters` and `:run` (lines 2--4).
|
620
|
+
|
621
|
+
* `:description` puts the `Person equality` in `Person equality test failed`,
|
622
|
+
the failure message we saw above.
|
623
|
+
* `:parameters` declares that this assertion takes two parameters, named
|
624
|
+
`:person` (of type `Person`) and `:string` (of type `String`).
|
625
|
+
|
626
|
+
{% highlight ruby %}
|
627
|
+
|
628
|
+
T :person, @person, "John Wiliam Smith 1927-03-19"
|
629
|
+
# ------- -------------------------------
|
630
|
+
# :person :string
|
631
|
+
|
632
|
+
{% endhighlight %}
|
633
|
+
|
634
|
+
* `:run` is the block that contains the primitive assertions to check that our
|
635
|
+
Person object is as expected. (Note: it must be a `proc` to work in Ruby 1.9;
|
636
|
+
`lambda` or `proc` will work in Ruby 1.8.)
|
637
|
+
|
638
|
+
* Lines 5--6 split the string into the individual names and date, and convert
|
639
|
+
the date string to a Date object.
|
640
|
+
* Line 7 **test**s the **first** name with the code `Eq person.first, f`.
|
641
|
+
* Line 8 **test**s the **middle** name with the code `Eq person.middle, m`.
|
642
|
+
* Lines 9--10 do likewise with **last** and **dob**.
|
643
|
+
* Notice the values `person` and `string` are available in the run block. The
|
644
|
+
two parameters we declared were passed in. (They are read-only values.)
|
645
|
+
|
646
|
+
The `test` method seen in lines 7--10 is an important part of a custom test. It
|
647
|
+
associates a label (`dob`) with an assertion (`Eq person.dob, dob`),
|
648
|
+
which allows Whitestone to provide a helpful error message if that assertion fails.
|
649
|
+
|
650
|
+
Custom assertions may seem tricky at first, but they're easy enough and
|
651
|
+
definitely worthwhile. In the tests for [my geometry project][rgeom] there are
|
652
|
+
lines like:
|
653
|
+
|
654
|
+
{% highlight ruby %}
|
655
|
+
|
656
|
+
T :circle, circle, [4,1, 3, :M]
|
657
|
+
T :arc, arc, [3,1, 5, nil, 0,180]
|
658
|
+
T :square, square, %w( 3 1 4.5 1 4.5 2.5 3 2.5 )
|
659
|
+
T :vertices, triangle, %w{ A 2 1 B 7 3 _ 2.76795 6.33013 }
|
660
|
+
|
661
|
+
{% endhighlight %}
|
662
|
+
|
663
|
+
[rgeom]:http://rgeom.rubyforge.org
|
664
|
+
|
665
|
+
#### Notes and limitations
|
666
|
+
|
667
|
+
* Custom tests can only be done in the affirmative. That is, while you can do
|
668
|
+
|
669
|
+
{% highlight ruby %}
|
670
|
+
|
671
|
+
T :person, @person, "John Henry Smith 1927-03-19"
|
672
|
+
|
673
|
+
{% endhighlight %}
|
674
|
+
|
675
|
+
the following will cause an error:
|
676
|
+
|
677
|
+
{% highlight ruby %}
|
678
|
+
|
679
|
+
T! :person, @person, "John Henry Smith 1927-03-19"
|
680
|
+
T? :person, @person, "John Henry Smith 1927-03-19"
|
681
|
+
F :person, @person, "John Henry Smith 1927-03-19"
|
682
|
+
F! :person, @person, "John Henry Smith 1927-03-19"
|
683
|
+
F? :person, @person, "John Henry Smith 1927-03-19"
|
684
|
+
|
685
|
+
{% endhighlight %}
|
686
|
+
|
687
|
+
This is an annoying limitation that is hard to avoid, but it has not been a
|
688
|
+
problem for me in practice.
|
689
|
+
|
690
|
+
* A good place to put custom assertions is your `test/_setup.rb` file. The
|
691
|
+
`whitestone` runner will load that file before loading and running other test
|
692
|
+
files.
|
693
|
+
|
694
|
+
* The `Person` class above should, at the very least, allow for a `nil` middle
|
695
|
+
name. The file `test/custom_assertions.rb` in the Whitestone source code has
|
696
|
+
this, but it was omitted for simplicity here.
|
697
|
+
|
698
|
+
|
699
|
+
## Endnotes
|
700
|
+
|
701
|
+
### Credits
|
702
|
+
|
703
|
+
Thanks to Suraj N. Kurapati, who created [Dfect][] and permitted me (explicitly
|
704
|
+
in response to a request and implicitly by its licence) to create and publish
|
705
|
+
this derivative work. Dfect is a wonderful library; I just wanted to add some
|
706
|
+
assertions and tune the terminal output. Several bits of code and prose have
|
707
|
+
made their way from Dfect's manual into this one, too.
|
708
|
+
|
709
|
+
### Motivation
|
710
|
+
|
711
|
+
Having used `test/unit` for a long time I was outgrowing it but failing to warm
|
712
|
+
to other approaches. The world seemed to be moving towards "specs", but I
|
713
|
+
preferred, and still prefer, the unit testing model: create objects with various
|
714
|
+
inputs, then assert that they satisfy various conditions. To me, it's about
|
715
|
+
state, not behaviour.
|
716
|
+
|
717
|
+
In October 2009 I made a list of features I wanted to see. Here is an edited
|
718
|
+
quote from a blog post:
|
719
|
+
|
720
|
+
> I've given some thought to features of my own testing framework, should it ever
|
721
|
+
> eventuate:
|
722
|
+
>
|
723
|
+
> * Simple approach, like test/unit (but also look at dfect and testy).
|
724
|
+
> * Less typing than test/unit.
|
725
|
+
> * Colourful output, drawing the eye to appropriate filenames and line numbers.
|
726
|
+
> * Stacktraces are filtered to get rid of rubbish like RubyGems's
|
727
|
+
> "custom_require" (I do this already with my mods to turn).
|
728
|
+
> * Easy to select the test cases you want to run.
|
729
|
+
> * Output like turn \[a gem that modifies the output of `test/unit`].
|
730
|
+
> * Optional drop-in to debugger or IRB at point of failure.
|
731
|
+
> * Green for expected value, red for actual value.
|
732
|
+
> * Code-based filter of test(s) to be run.
|
733
|
+
>
|
734
|
+
> I'm hoping not to create a testing framework anytime soon, but am saving this
|
735
|
+
> list here in case I want to do so in the future.
|
736
|
+
|
737
|
+
Months later, working on a new project, I finally bit the bullet. Dfect met
|
738
|
+
many of the goals, and I liked it and it started tinkering with it. My goals
|
739
|
+
now don't match that list precisely, but it was a good start.
|
740
|
+
|
741
|
+
### Differences from Dfect (v2.1.0)
|
742
|
+
|
743
|
+
If an error occurs while running an assertion's block, Whitestone considers it an
|
744
|
+
ERROR only, whereas Dfect will report a FAIL in addition.
|
745
|
+
|
746
|
+
Any error or failure will abort the current test (and any nested tests). It is
|
747
|
+
fail-fast; Dfect continues to run assertions after an error or failure.
|
748
|
+
|
749
|
+
Whitestone has removed the "trace" feature from Dfect (a hierarchical structure
|
750
|
+
reporting on the result of each test, and containing logging statements from the
|
751
|
+
**L** method). Consequently:
|
752
|
+
* Whitestone does not have the **L** method
|
753
|
+
* Whitestone does not have the `report` method (it has `stats` instead)
|
754
|
+
|
755
|
+
Whitestone does not offer to drop into a debugger or IRB at the point of failure. I
|
756
|
+
prefer to use the `ruby-debug` gem and set breakpoints using `Whitestone.current_test`.
|
757
|
+
|
758
|
+
Whitestone does not show the value of variables in event of failure or error.
|
759
|
+
|
760
|
+
Whitestone does not provide emulation layers for other testing libraries.
|
761
|
+
|
762
|
+
Whitestone does not allow you to provide a message to assertions. It is hoped that
|
763
|
+
Whitestone's output provides all the information you need. The following code is
|
764
|
+
legitimate in Dfect but not in Whitestone:
|
765
|
+
|
766
|
+
{% highlight ruby %}
|
767
|
+
|
768
|
+
T("string has verve") { "foo".respond_to? :verve }
|
769
|
+
|
770
|
+
{% endhighlight %}
|
771
|
+
|
772
|
+
### Dependencies and requirements
|
773
|
+
|
774
|
+
Dependencies (automatically resolved by RubyGems):
|
775
|
+
* `col` for coloured console output (which depends on `term/ansi-color`)
|
776
|
+
* `differ` for highlighting difference between strings
|
777
|
+
|
778
|
+
Whitestone was initially developed using Ruby 1.8.7, then tested using Ruby
|
779
|
+
1.9.2, and now developed again using Ruby 1.9.3.
|
780
|
+
|
781
|
+
The colours used in the console output were designed for a black background.
|
782
|
+
They are hardcoded and it would be a major effort to customise them!
|
783
|
+
|
784
|
+
### Project details
|
785
|
+
|
786
|
+
* Author: Gavin Sinclair (user name: `gsinclair`; mail server: `gmail.com`)
|
787
|
+
* Licence: MIT licence
|
788
|
+
* Project homepage: [http://gsinclair.github.com/whitestone.html][home]
|
789
|
+
* Source code: [http://github.com/gsinclair/whitestone][code]
|
790
|
+
* Documentation: (project homepage)
|
791
|
+
|
792
|
+
[home]: http://gsinclair.github.com/whitestone.html
|
793
|
+
[code]: http://github.com/gsinclair/whitestone
|
794
|
+
|
795
|
+
### History
|
796
|
+
|
797
|
+
* July 2010: originally developed under the name 'attest' but not released
|
798
|
+
* 1 January 2012: version 1.0.0
|
799
|
+
|
800
|
+
### Future plans
|
801
|
+
|
802
|
+
A lot of work has gone into making Whitestone mature on its initial release. No
|
803
|
+
further features are currently planned. Any bugs found will be fixed promptly
|
804
|
+
and give rise to releases 1.0.1, 1.0.2 etc. Any backwards-compatible feature
|
805
|
+
enhancements will be released under 1.1.0, 1.2.0 etc.
|
806
|
+
|