whitestone 1.0.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/.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
|
+

|
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
|
+

|
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
|
+
|