whitestone 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+