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.
@@ -0,0 +1 @@
1
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in col.gemspec
4
+ gemspec
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.
@@ -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
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
+