oktest 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c60ec7ce654d0f50f7f2cb6523e2f27f4e843b7e88e37d886a694553805f2d7e
4
+ data.tar.gz: 559d192eaa5a321a59db95defbc2e7e4ec271bc8d3b97aeba2b8658a61a1b0d9
5
+ SHA512:
6
+ metadata.gz: 73e39ea5796c682696127b34bff655870458b509aec487b2420276e8bd892a6a60c82a74895c0be89aa2160a231ef08eb92aff8b5a998ca335e735efbb410487
7
+ data.tar.gz: 8795c2227f46c7137d3456fa1040aaff0de3d020748c553439d39c7852a66cd0af7d81150cd26ef4c68907d503501de5c7cd3ec3eb3bfa6bdfe1bf2e4c9d5435
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2011-2021 kuwata-lab.com all rights reserved
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.md ADDED
@@ -0,0 +1,1856 @@
1
+ <!-- # -*- coding: utf-8 -*- -->
2
+ # Oktest.rb README
3
+
4
+
5
+ Oktest.rb is a new-style testing library for Ruby.
6
+
7
+ * `ok {actual} == expected` style assertion.
8
+ * **Fixture injection** inspired by dependency injection.
9
+ * Structured test specifications like RSpec.
10
+ * Filtering testcases by pattern or tags.
11
+ * Blue/red color instead of green/red for accesability.
12
+ * Small code size (about 2400 lines) and good performance.
13
+
14
+ ```ruby
15
+ ### Oktest ### Test::Unit
16
+ require 'oktest' # require 'test/unit'
17
+ #
18
+ Oktest.scope do #
19
+ #
20
+ topic "Example" do # class ExampleTest < Test::Unit::TestCase
21
+ #
22
+ spec "...description..." do # def test_1 # ...description...
23
+ ok {1+1} == 2 # assert_equal 2, 1+1
24
+ not_ok {1+1} == 3 # assert_not_equal 3, 1+1
25
+ ok {3*3} < 10 # assert 3*3 < 10
26
+ not_ok {3*4} < 10 # assert 3*4 >= 10
27
+ ok {@var}.nil? # assert_nil @var
28
+ not_ok {123}.nil? # assert_not_nil 123
29
+ ok {3.14}.in_delta?(3.1, 0.1) # assert_in_delta 3.1, 3.14, 0.1
30
+ ok {'aaa'}.is_a?(String) # assert_kind_of String, 'aaa'
31
+ ok {'123'} =~ (/\d+/) # assert_match /\d+/, '123'
32
+ ok {:sym}.same?(:sym) # assert_same? :sym, :sym
33
+ ok {'README.md'}.file_exist? # assert File.file?('README.md')
34
+ ok {'/tmp'}.dir_exist? # assert File.directory?('/tmp')
35
+ ok {'/blabla'}.not_exist? # assert !File.exist?('/blabla')
36
+ pr = proc { .... } # exc = assert_raise(Error) { .... }
37
+ ok {pr}.raise?(Error, "mesg") # assert_equal "mesg", exc.message
38
+ end # end
39
+ #
40
+ end # end
41
+ #
42
+ end #
43
+ ```
44
+
45
+ Oktest.rb requires Ruby 2.3 or later.
46
+
47
+
48
+
49
+ ## Table of Contents
50
+
51
+ <!-- TOC -->
52
+
53
+ * <a href="#quick-tutorial">Quick Tutorial</a>
54
+ * <a href="#install">Install</a>
55
+ * <a href="#basic-example">Basic Example</a>
56
+ * <a href="#assertion-failure-and-error">Assertion Failure, and Error</a>
57
+ * <a href="#skip-and-todo">Skip, and Todo</a>
58
+ * <a href="#reporting-style">Reporting Style</a>
59
+ * <a href="#run-all-test-scripts-under-directory">Run All Test Scripts Under Directory</a>
60
+ * <a href="#tag-and-filtering">Tag and Filtering</a>
61
+ * <a href="#case_when-and-case_else"><code>case_when</code> and <code>case_else</code></a>
62
+ * <a href="#optional-unary-operators">Optional: Unary Operators</a>
63
+ * <a href="#generate-test-code-skeleton">Generate Test Code Skeleton</a>
64
+ * <a href="#defining-methods-in-topics">Defining Methods in Topics</a>
65
+ * <a href="#assertions">Assertions</a>
66
+ * <a href="#basic-assertions">Basic Assertions</a>
67
+ * <a href="#predicate-assertions">Predicate Assertions</a>
68
+ * <a href="#negative-assertion">Negative Assertion</a>
69
+ * <a href="#exception-assertion">Exception Assertion</a>
70
+ * <a href="#custom-assertion">Custom Assertion</a>
71
+ * <a href="#fixtures">Fixtures</a>
72
+ * <a href="#setup-and-teardown">Setup and Teardown</a>
73
+ * <a href="#at_end-crean-up-handler"><code>at_end()</code>: Crean-up Handler</a>
74
+ * <a href="#named-fixtures">Named Fixtures</a>
75
+ * <a href="#fixture-injection">Fixture Injection</a>
76
+ * <a href="#global-scope">Global Scope</a>
77
+ * <a href="#helpers">Helpers</a>
78
+ * <a href="#capture_sio"><code>capture_sio()</code></a>
79
+ * <a href="#dummy_file"><code>dummy_file()</code></a>
80
+ * <a href="#dummy_dir"><code>dummy_dir()</code></a>
81
+ * <a href="#dummy_values"><code>dummy_values()</code></a>
82
+ * <a href="#dummy_attrs"><code>dummy_attrs()</code></a>
83
+ * <a href="#dummy_ivars"><code>dummy_ivars()</code></a>
84
+ * <a href="#recorder"><code>recorder()</code></a>
85
+ * <a href="#tips">Tips</a>
86
+ * <a href="#ok--in-minitest"><code>ok {}</code> in MiniTest</a>
87
+ * <a href="#testing-rack-application">Testing Rack Application</a>
88
+ * <a href="#traverser-class">Traverser Class</a>
89
+ * <a href="#benchmarks">Benchmarks</a>
90
+ * <a href="#--faster-option"><code>--faster</code> Option</a>
91
+ * <a href="#change-log">Change Log</a>
92
+ * <a href="#license-and-copyright">License and Copyright</a>
93
+
94
+ <!-- /TOC -->
95
+
96
+
97
+
98
+ ## Quick Tutorial
99
+
100
+
101
+ ### Install
102
+
103
+ ```terminal
104
+ ### install
105
+ $ gem install oktest
106
+ $ oktest --help
107
+
108
+ ### create test directory
109
+ $ mkdir test
110
+
111
+ ### create test script
112
+ $ oktest --create > test/example_test.rb
113
+ $ less test/example_test.rb
114
+
115
+ ### run test script
116
+ $ oktest -s verbose test
117
+ * Class
118
+ * #method_name()
119
+ - [pass] 1+1 should be 2.
120
+ - [pass] fixture injection examle.
121
+ ## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.001s
122
+ ```
123
+
124
+
125
+ ### Basic Example
126
+
127
+ test/example01_test.rb:
128
+
129
+ ```ruby
130
+ # coding: utf-8
131
+
132
+ require 'oktest'
133
+
134
+ class Hello
135
+ def hello(name="world")
136
+ return "Hello, #{name}!"
137
+ end
138
+ end
139
+
140
+
141
+ Oktest.scope do
142
+
143
+ topic Hello do
144
+
145
+ topic '#hello()' do
146
+
147
+ spec "returns greeting message." do
148
+ actual = Hello.new.hello()
149
+ ok {actual} == "Hello, world!"
150
+ end
151
+
152
+ spec "accepts user name." do
153
+ actual = Hello.new.hello("RWBY")
154
+ ok {actual} == "Hello, RWBY!"
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+
161
+ end
162
+ ```
163
+
164
+ Result:
165
+
166
+ ```terminal
167
+ $ oktest test/example01_test.rb # or: ruby test/example01_test.rb
168
+ * Hello
169
+ * #hello()
170
+ - [pass] returns greeting message.
171
+ - [pass] accepts user name.
172
+ ## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
173
+ ```
174
+
175
+
176
+ ### Assertion Failure, and Error
177
+
178
+ test/example02_test.rb:
179
+
180
+ ```ruby
181
+ require 'oktest'
182
+
183
+ Oktest.scope do
184
+
185
+ topic 'other examples' do
186
+
187
+ spec "example of assertion failure" do
188
+ ok {1+1} == 2 # pass
189
+ ok {1+1} == 0 # FAIL
190
+ end
191
+
192
+ spec "example of something error" do
193
+ x = foobar # NameError
194
+ end
195
+
196
+ end
197
+
198
+ end
199
+ ```
200
+
201
+ Result:
202
+
203
+ ```terminal
204
+ $ oktest test/example02_test.rb # or: ruby test/example02_test.rb
205
+ * other examples
206
+ - [Fail] example of assertion failure
207
+ - [ERROR] example of something error
208
+ ----------------------------------------------------------------------
209
+ [Fail] other examples > example of assertion failure
210
+ tmp/test/example02_test.rb:9:in `block (3 levels) in <main>'
211
+ ok {1+1} == 0 # FAIL
212
+ $<actual> == $<expected>: failed.
213
+ $<actual>: 2
214
+ $<expected>: 0
215
+ ----------------------------------------------------------------------
216
+ [ERROR] other examples > example of something error
217
+ tmp/test/example02_test.rb:13:in `block (3 levels) in <main>'
218
+ x = foobar # NameError
219
+ NameError: undefined local variable or method `foobar' for #<#<Class:...>:...>
220
+ ----------------------------------------------------------------------
221
+ ## total:2 (pass:0, fail:1, error:1, skip:0, todo:0) in 0.000s
222
+ ```
223
+
224
+
225
+ ### Skip, and Todo
226
+
227
+ test/example03_test.rb:
228
+
229
+ ```ruby
230
+ require 'oktest'
231
+
232
+ Oktest.scope do
233
+
234
+ topic 'other examples' do
235
+
236
+ spec "example of skip" do
237
+ skip_when RUBY_VERSION < "3.0", "requires Ruby3"
238
+ ok {1+1} == 2
239
+ end
240
+
241
+ spec "example of todo" # spec without block means TODO
242
+
243
+ spec "example of todo (when passed unexpectedly)" do
244
+ TODO() # this spec should be failed,
245
+ # because not implemented yet.
246
+ ok {1+1} == 2 # thefore if all assesions passed,
247
+ # it means 'unexpected success'.
248
+ end
249
+
250
+ end
251
+
252
+ end
253
+ ```
254
+
255
+ Result:
256
+
257
+ ```terminal
258
+ $ oktest test/example03_test.rb # or: ruby test/example03_test.rb
259
+ * other examples
260
+ - [Skip] example of skip (reason: requires Ruby3)
261
+ - [TODO] example of todo
262
+ - [Fail] example of todo (when passed unexpectedly)
263
+ ----------------------------------------------------------------------
264
+ [Fail] other examples > example of todo (when passed unexpectedly)
265
+ test/example03_test.rb:14:in `block (2 levels) in <top (required)>'
266
+ spec "example of todo (when passed unexpectedly)" do
267
+ spec should be failed (because not implemented yet), but passed unexpectedly.
268
+ ----------------------------------------------------------------------
269
+ ## total:2 (pass:0, fail:1, error:0, skip:1, todo:1) in 0.000s
270
+ ```
271
+
272
+
273
+ ### Reporting Style
274
+
275
+ Verbose mode (default):
276
+
277
+ ```terminal
278
+ $ oktest test/example01_test.rb -s verbose # or -sv
279
+ * Hello
280
+ * #hello()
281
+ - [pass] returns greeting message.
282
+ - [pass] accepts user name.
283
+ ## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
284
+ ```
285
+
286
+ Simple mode:
287
+
288
+ ```terminal
289
+ $ oktest test/example01_test.rb -s simple # or -ss
290
+ test/example01_test.rb: ..
291
+ ## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
292
+ ```
293
+
294
+ Plain mode:
295
+
296
+ ```terminal
297
+ $ oktest test/example01_test.rb -s simple # or -ss
298
+ ..
299
+ ## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
300
+ ```
301
+
302
+ Quiet mode:
303
+
304
+ ```terminal
305
+ $ oktest test/example01_test.rb -s quiet # or -sq
306
+
307
+ ## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
308
+ ```
309
+
310
+ Quiet mode reports progress only of failed or error test cases (and doesn't
311
+ report progress of passed ones), so it's output is very compact. This is
312
+ very useful for large project which contains large number of test cases.
313
+
314
+
315
+ ### Run All Test Scripts Under Directory
316
+
317
+ How to run test scripts under `test` directory:
318
+
319
+ ```terminal
320
+ $ ls test
321
+ example01_test.rb example02_test.rb example03_test.rb
322
+
323
+ $ oktest -s simple test # or: ruby -r oktest -e 'Oktest.main' -- test -s simple
324
+ tmp/test/example01_test.rb: ..
325
+ tmp/test/example02_test.rb: fE
326
+ ----------------------------------------------------------------------
327
+ [Fail] other examples > example of assertion failure
328
+ tmp/test/example02_test.rb:9:in `block (3 levels) in <top (required)>'
329
+ ok {1+1} == 0 # FAIL
330
+ -e:1:in `<main>'
331
+ $<actual> == $<expected>: failed.
332
+ $<actual>: 2
333
+ $<expected>: 0
334
+ ----------------------------------------------------------------------
335
+ [ERROR] other examples > example of something error
336
+ tmp/test/example02_test.rb:13:in `block (3 levels) in <top (required)>'
337
+ x = foobar # NameError
338
+ -e:1:in `<main>'
339
+ NameError: undefined local variable or method `foobar' for #<#<Class:...>:...>
340
+ ----------------------------------------------------------------------
341
+ tmp/test/example03_test.rb: st
342
+ ## total:6 (pass:2, fail:1, error:1, skip:1, todo:1) in 0.000s
343
+ ```
344
+
345
+ Test script filename should be `test_xxx.rb` or `xxx_test.rb`
346
+ (not `test-xxx.rb` nor `xxx-test.rb`).
347
+
348
+
349
+ ### Tag and Filtering
350
+
351
+ `topic()` and `spec()` accepts tag name, for example 'obsolete' or 'experimental'.
352
+
353
+ test/example04_test.rb:
354
+
355
+ ```ruby
356
+ require 'oktest'
357
+
358
+ Oktest.scope do
359
+
360
+ topic 'Example topic' do
361
+
362
+ topic Integer do
363
+ spec "example #1" do
364
+ ok {1+1} == 2
365
+ end
366
+ spec "example #2", tag: 'old' do # tag name: 'old'
367
+ ok {1-1} == 0
368
+ end
369
+ end
370
+
371
+ topic Float, tag: 'exp' do # tag name: 'exp'
372
+ spec "example #3" do
373
+ ok {1.0+1.0} == 2.0
374
+ end
375
+ spec "example #4" do
376
+ ok {1.0-1.0} == 0.0
377
+ end
378
+ end
379
+
380
+ topic String, tag: ['exp', 'old'] do # tag name: 'old' and 'exp'
381
+ spec "example #5" do
382
+ ok {'a'*3} == 'aaa'
383
+ end
384
+ end
385
+
386
+ end
387
+
388
+ end
389
+ ```
390
+
391
+ It is possible to filter topics and specs by tag name (pattern).
392
+
393
+ ```terminal
394
+ $ oktest -F tag=exp tests/ # filter by tag name
395
+ $ oktest -F tag='*exp*' tests/ # filter by tag name pattern
396
+ $ oktest -F tag='{exp,old}' tests/ # filter by multiple tag names
397
+ ```
398
+
399
+ It is also possible to filter topics or specs by name.
400
+ Pattern (!= regular expression) supports `*`, `?`, `[]`, and `{}`.
401
+
402
+ ```terminal
403
+ $ oktest -F topic='*Integer*' test/ # filter topics by pattern
404
+ $ oktest -F spec='*#[1-3]' test/ # filter specs by pattern
405
+ ```
406
+
407
+ If you need negative filter, use `!=` instead of `=`.
408
+
409
+ ```terminal
410
+ $ oktest -F spec!='*#5' tests/ # exclude spec 'example #5'
411
+ $ oktest -F tag!='{exp,old}' tests/ # exclude tag='exp' or tag='old'
412
+ ```
413
+
414
+
415
+ ### `case_when` and `case_else`
416
+
417
+ `case_when` and `case_else` represents conditional spec.
418
+
419
+ test/example05_test.rb:
420
+
421
+ ```ruby
422
+ require 'oktest'
423
+
424
+ Oktest.scope do
425
+ topic Integer do
426
+ topic '#abs()' do
427
+
428
+ case_when "value is negative..." do
429
+ spec "converts value into positive." do
430
+ ok {-123.abs()} == 123
431
+ end
432
+ end
433
+
434
+ case_when "value is zero..." do
435
+ spec "returns zero." do
436
+ ok {0.abs()} == 0
437
+ end
438
+ end
439
+
440
+ case_else do
441
+ spec "returns itself." do
442
+ ok {123.abs()} == 123
443
+ end
444
+ end
445
+
446
+ end
447
+ end
448
+ end
449
+ ```
450
+
451
+ Result:
452
+
453
+ ```terminal
454
+ $ ruby test/example05_test.rb
455
+ * Integer
456
+ * #abs()
457
+ - When value is negative...
458
+ - [pass] converts value into positive.
459
+ - When value is zero...
460
+ - [pass] returns zero.
461
+ - Else
462
+ - [pass] returns itself.
463
+ ## total:3 (pass:3, fail:0, error:0, skip:0, todo:0) in 0.001s
464
+ ```
465
+
466
+
467
+ ### Optional: Unary Operators
468
+
469
+ `topic()` accepts unary `+` operator and `spec()` accepts unary `-` operator.
470
+ This makes test scripts more readable.
471
+
472
+ <!--
473
+ test/example06_test.rb:
474
+ -->
475
+
476
+ ```ruby
477
+ require 'oktest'
478
+
479
+ Oktest.scope do
480
+
481
+ + topic('example') do # unary `+` operator
482
+
483
+ + topic('example') do # unary `+` operator
484
+
485
+ - spec("1+1 is 2.") do # unary `-` operator
486
+ ok {1+1} == 2
487
+ end
488
+
489
+ - spec("1*1 is 1.") do # unary `-` operator
490
+ ok {1*1} == 1
491
+ end
492
+
493
+ end
494
+
495
+ end
496
+
497
+ end
498
+ ```
499
+
500
+
501
+ ### Generate Test Code Skeleton
502
+
503
+ `oktest -G` (or `oktest --generate`) generates test code skeleton from ruby file.
504
+ Comment line starting with `#;` is regarded as spec description.
505
+
506
+ hello.rb:
507
+
508
+ ```ruby
509
+ class Hello
510
+
511
+ def hello(name=nil)
512
+ #; default name is 'world'.
513
+ if name.nil?
514
+ name = "world"
515
+ end
516
+ #; returns greeting message.
517
+ return "Hello, #{name}!"
518
+ end
519
+
520
+ end
521
+ ```
522
+
523
+ Generate test code skeleton:
524
+
525
+ ```terminal
526
+ $ oktest -G hello.rb > test/hello_test.rb
527
+ ```
528
+
529
+ test/hello_test.rb:
530
+
531
+ ```ruby
532
+ # coding: utf-8
533
+
534
+ require 'oktest'
535
+
536
+ Oktest.scope do
537
+
538
+
539
+ topic Hello do
540
+
541
+
542
+ topic '#hello()' do
543
+
544
+ spec "default name is 'world'."
545
+
546
+ spec "returns greeting message."
547
+
548
+ end # #hello()
549
+
550
+
551
+ end # Hello
552
+
553
+
554
+ end
555
+ ```
556
+
557
+ (Experimental) `--generate=unaryop` generates test skeleton with unary operator `+` and `-`.
558
+
559
+ ```terminal
560
+ $ oktest --generate=unaryop hello.rb > test/hello2_test.rb
561
+ ```
562
+
563
+ test/hello2_test.rb:
564
+
565
+ ```ruby
566
+ # coding: utf-8
567
+
568
+ require 'oktest'
569
+
570
+ Oktest.scope do
571
+
572
+
573
+ + topic(Hello) do
574
+
575
+
576
+ + topic('#hello()') do
577
+
578
+ - spec("default name is 'world'.")
579
+
580
+ - spec("returns greeting message.")
581
+
582
+ end # #hello()
583
+
584
+
585
+ end # Hello
586
+
587
+
588
+ end
589
+ ```
590
+
591
+
592
+ ### Defining Methods in Topics
593
+
594
+ Methods defined in topics can be called in specs.
595
+
596
+ <!--
597
+ test/example08a_test.rb:
598
+ -->
599
+
600
+ ```ruby
601
+ require 'oktest'
602
+ Oktest.scope do
603
+
604
+ topic "Method example" do
605
+
606
+ def hello() # define method in topic block
607
+ return "Hello!"
608
+ end
609
+
610
+ spec "example" do
611
+ s = hello() # call method in spec block
612
+ ok {s} == "Hello!"
613
+ end
614
+
615
+ end
616
+
617
+ end
618
+ ```
619
+
620
+ * It is OK to call methods defined in parent topics.
621
+ * It will be ERROR to call methods defined in child topics.
622
+
623
+ <!--
624
+ test/example08b_test.rb:
625
+ -->
626
+
627
+ ```ruby
628
+ require 'oktest'
629
+ Oktest.scope do
630
+
631
+ + topic('Outer') do
632
+
633
+ + topic('Middle') do
634
+
635
+ def hello() # define method in topic block
636
+ return "Hello!"
637
+ end
638
+
639
+ + topic('Inner') do
640
+
641
+ - spec("inner spec") do
642
+ s = hello() # OK: call method defined in parent topic
643
+ ok {s} == "Hello!"
644
+ end
645
+
646
+ end
647
+
648
+ end
649
+
650
+ - spec("outer spec") do
651
+ s = hello() # ERROR: call method defined in child topic
652
+ ok {x} == "Hello!"
653
+ end
654
+
655
+ end
656
+
657
+ end
658
+ ```
659
+
660
+
661
+
662
+ ## Assertions
663
+
664
+
665
+ ### Basic Assertions
666
+
667
+ In the following example, `a` means actual value and `e` means expected value.
668
+
669
+ <!--
670
+ test/example11_test.rb:
671
+ -->
672
+
673
+ ```ruby
674
+ ok {a} == e # fail unless a == e
675
+ ok {a} != e # fail unless a != e
676
+ ok {a} === e # fail unless a === e
677
+ ok {a} !== e # fail unless a !== e
678
+
679
+ ok {a} > e # fail unless a > e
680
+ ok {a} >= e # fail unless a >= e
681
+ ok {a} < e # fail unless a < e
682
+ ok {a} <= e # fail unless a <= e
683
+
684
+ ok {a} =~ e # fail unless a =~ e
685
+ ok {a} !~ e # fail unless a !~ e
686
+
687
+ ok {a}.same?(e) # fail unless a.equal?(e)
688
+ ok {a}.in?(e) # fail unless e.include?(a)
689
+ ok {a}.in_delta?(e, x) # fail unless e-x < a < e+x
690
+ ok {a}.truthy? # fail unless !!a == true
691
+ ok {a}.falsy? # fail unless !!a == false
692
+
693
+ ok {a}.file_exist? # fail unless File.file?(a)
694
+ ok {a}.dir_exist? # fail unless File.directory?(a)
695
+ ok {a}.symlink_exist? # fail unless File.symlink?(a)
696
+ ok {a}.not_exist? # fail unless ! File.exist?(a)
697
+
698
+ ok {a}.attr(name, e) # fail unless a.__send__(name) == e
699
+ ok {a}.keyval(key, e) # fail unless a[key] == e
700
+ ok {a}.item(key, e) # alias of `ok {a}.keyval(key, e)`
701
+ ok {a}.length(e) # fail unless a.length == e
702
+ ```
703
+
704
+ It is possible to chan method call of `.attr()` and `.keyval()`.
705
+
706
+ <!--
707
+ test/example11b_test.rb:
708
+ -->
709
+
710
+ ```ruby
711
+ ok {a}.attr(:name1, 'val1').attr(:name2, 'val2').attr(:name3, 'val3')
712
+ ok {a}.keyval(:key1, 'val1').keyval(:key2, 'val2').keyval(:key3, 'val3')
713
+ ```
714
+
715
+
716
+ ### Predicate Assertions
717
+
718
+ `ok {}` handles predicate methods (such as `.nil?`, `.empty?`, or `.key?`) automatically.
719
+
720
+ <!--
721
+ test/example12_test.rb:
722
+ -->
723
+
724
+ ```ruby
725
+ ok {a}.nil? # same as ok {a.nil?} == true
726
+ ok {a}.empty? # same as ok {a.empty?} == true
727
+ ok {a}.key?(e) # same as ok {a.key?(e)} == true
728
+ ok {a}.is_a?(e) # same as ok {a.is_a?(e)} == true
729
+ ok {a}.include?(e) # same as ok {a.include?(e)} == true
730
+ ok {a}.between?(x, y) # same as ok {a.between?(x, y)} == true
731
+ ```
732
+
733
+ `Pathname()` is a good example of predicate methods.
734
+ See [pathname.rb](https://ruby-doc.org/stdlib-2.7.0/libdoc/pathname/rdoc/Pathname.html)
735
+ document for details about `Pathname()`.
736
+
737
+ <!--
738
+ test/example12b_test.rb:
739
+ -->
740
+
741
+ ```ruby
742
+ require 'pathname' # !!!!!
743
+
744
+ ok {Pathname(a)}.owned? # same as ok {Pathname(a).owned?} == true
745
+ ok {Pathname(a)}.readable? # same as ok {Pathname(a).readable?} == true
746
+ ok {Pathname(a)}.writable? # same as ok {Pathname(a).writable?} == true
747
+ ok {Pathname(a)}.absolute? # same as ok {Pathname(a).absolute?} == true
748
+ ok {Pathname(a)}.relative? # same as ok {Pathname(a).relative?} == true
749
+ ```
750
+
751
+
752
+ ### Negative Assertion
753
+
754
+ <!--
755
+ test/example13_test.rb:
756
+ -->
757
+
758
+ ```ruby
759
+ not_ok {a} == e # fail if a == e
760
+ ok {a}.NOT == e # fail if a == e
761
+
762
+ not_ok {a}.file_exist? # fail if File.file?(a)
763
+ ok {a}.NOT.file_exist? # fail if File.file?(a)
764
+ ```
765
+
766
+
767
+ ### Exception Assertion
768
+
769
+ If you want to assert whether exception raised or not:
770
+
771
+ <!--
772
+ test/example14_test.rb:
773
+ -->
774
+
775
+ ```ruby
776
+ pr = proc do
777
+ "abc".len() # raises NoMethodError
778
+ end
779
+ ok {pr}.raise?(NoMethodError)
780
+ ok {pr}.raise?(NoMethodError, "undefined method `len' for \"abc\":String")
781
+ ok {pr}.raise?(NoMethodError, /^undefined method `len'/)
782
+ ok {pr}.raise? # pass if any exception raised, fail if nothing raised
783
+
784
+ ## get exception object
785
+ ok {pr}.raise?(NoMethodError) {|exc|
786
+ ok {exc.class} == NoMethodError
787
+ ok {exc.message} == "undefined method `len' for \"abc\":String"
788
+ }
789
+
790
+ ## assert that procedure does NOT raise any exception
791
+ ok {pr}.NOT.raise? # no exception class nor error message
792
+ not_ok {pr}.raise? # same as above
793
+
794
+ ## assert that procedure throws symbol.
795
+ pr2 = proc do
796
+ throw :quit
797
+ end
798
+ ok {pr2}.throw?(:quit) # pass if :quit thrown, fail if other or nothing thrown
799
+ ```
800
+
801
+ If procedure contains `raise "errmsg"` instead of `raise ErrorClass, "errmsg"`,
802
+ you can omit exception class such as `ok {pr}.raise?("errmsg")`.
803
+
804
+ <!--
805
+ test/example14b_test.rb:
806
+ -->
807
+
808
+ ```ruby
809
+ pr = proc do
810
+ raise "something wrong" # !!! error class not specified !!!
811
+ end
812
+ ok {pr}.raise?("something wrong") # !!! error class not specified !!!
813
+ ```
814
+
815
+ Notice that `ok().raise?()` compares error class by `==` operator, not `.is_a?` method.
816
+
817
+ <!--
818
+ test/example14c_test.rb:
819
+ -->
820
+
821
+ ```ruby
822
+ pr = proc { 1/0 } # raises ZeroDivisionError
823
+
824
+ ok {pr}.raise?(ZeroDivisoinError) # pass
825
+ ok {pr}.raise?(StandardError) # ERROR: ZeroDivisionError raised
826
+ ok {pr}.raise?(Exception) # ERROR: ZeroDivisionError raised
827
+ ```
828
+
829
+ This is an intended design to avoid unexpected assertion success.
830
+ For example, `assert_raises(NameError) { .... }` in MiniTest will result in
831
+ success unexpectedly even if `NoMethodError` raised in the block, because
832
+ `NoMethodError` is a subclass of `NameError`.
833
+
834
+ <!--
835
+ test/example14d_test.rb:
836
+ -->
837
+
838
+ ```ruby
839
+ require 'minitest/spec'
840
+ require 'minitest/autorun'
841
+
842
+ describe "assert_raise()" do
843
+ it "results in success unexpectedly" do
844
+ ## catches NameError and it's subclasses, including NoMethodError.
845
+ assert_raises(NameError) do # catches NoMethodError, too.
846
+ "str".foobar() # raises NoMethodError.
847
+ end
848
+ end
849
+ end
850
+ ```
851
+
852
+ Oktest.rb can avoid this pitfall, because `.raise?()` compares error class
853
+ by `==` operator, not `.is_a?` method.
854
+
855
+ <!--
856
+ test/example14e_test.rb:
857
+ -->
858
+
859
+ ```ruby
860
+ require 'oktest'
861
+
862
+ Oktest.scope do
863
+ topic 'ok().raise?' do
864
+ spec "doesn't catch subclasses." do
865
+ pr = proc do
866
+ "str".foobar() # raises NoMethodError
867
+ end
868
+ ok {pr}.raise?(NoMethodError) # pass
869
+ ok {pr}.raise?(NameError) # NoMethodError raised intendedly
870
+ end
871
+ end
872
+ end
873
+ ```
874
+
875
+ To catch subclass of error class, invoke `.raise!` instead of `.raise?`.
876
+ For example: `ok {pr}.raise!(NameError, /foobar/, subclass: true)`.
877
+
878
+ <!--
879
+ test/example14f_test.rb:
880
+ -->
881
+
882
+ ```ruby
883
+ require 'oktest'
884
+
885
+ Oktest.scope do
886
+ topic 'ok().raise!' do
887
+ spec "catches subclasses." do
888
+ pr = proc do
889
+ "str".foobar() # raises NoMethodError
890
+ end
891
+ ok {pr}.raise!(NoMethodError) # pass
892
+ ok {pr}.raise!(NameError) # pass !!!!!
893
+ end
894
+ end
895
+ end
896
+ ```
897
+
898
+
899
+ ### Custom Assertion
900
+
901
+ How to define custom assertion:
902
+
903
+ <!--
904
+ test/example15_test.rb:
905
+ -->
906
+ ```ruby
907
+ require 'oktest'
908
+
909
+ Oktest::AssertionObject.class_eval do
910
+ def readable? # custom assertion: file readable?
911
+ _done()
912
+ result = File.readable?(@actual)
913
+ __assert(result == @bool) {
914
+ "File.readable?($<actual>) == #{@bool}: failed.\n" +
915
+ " $<actual>: #{@actual.inspect}"
916
+ }
917
+ self
918
+ end
919
+ end
920
+
921
+ Oktest.scope do
922
+
923
+ topic "Custom assertion" do
924
+
925
+ spec "example spec" do
926
+ ok {__FILE__}.readable? # custom assertion
927
+ end
928
+
929
+ end
930
+
931
+ end
932
+ ```
933
+
934
+
935
+
936
+ ## Fixtures
937
+
938
+
939
+ ### Setup and Teardown
940
+
941
+ test/example21a_test.rb:
942
+
943
+ ```ruby
944
+ require 'oktest'
945
+
946
+ Oktest.scope do
947
+
948
+ topic "Fixture example" do
949
+
950
+ before do # equivarent to setUp()
951
+ puts "=== before() ==="
952
+ end
953
+
954
+ after do # equivarent to tearDown()
955
+ puts "=== after() ==="
956
+ end
957
+
958
+ before_all do # equivarent to setUpAll()
959
+ puts "*** before_all() ***"
960
+ end
961
+
962
+ after_all do # equvarent to tearDownAll()
963
+ puts "*** after_all() ***"
964
+ end
965
+
966
+ spec "example spec #1" do
967
+ puts "---- example spec #1 ----"
968
+ end
969
+
970
+ spec "example spec #2" do
971
+ puts "---- example spec #2 ----"
972
+ end
973
+
974
+ end
975
+
976
+ end
977
+ ```
978
+
979
+ Result:
980
+
981
+ ```terminal
982
+ $ oktest -s plain test/example21_test.rb
983
+ *** before_all() ***
984
+ === before() ===
985
+ ---- example spec #1 ----
986
+ === after() ===
987
+ .=== before() ===
988
+ ---- example spec #2 ----
989
+ === after() ===
990
+ .*** after_all() ***
991
+
992
+ ## total:2 (pass:2, fail:0, error:0, skip:0, todo:0) in 0.000s
993
+ ```
994
+
995
+ These blocks can be defined per `topic()` or `scope()`.
996
+
997
+ * `before` block in outer topic/scope is called prior to `before` block in inner topic.
998
+ * `after` block in inner topic is called prior to `after` block in outer topic/scope.
999
+
1000
+ test/example21b_test.rb:
1001
+
1002
+ ```ruby
1003
+ require 'oktest'
1004
+
1005
+ Oktest.scope do
1006
+
1007
+ topic 'Outer' do
1008
+ before { puts "=== Outer: before ===" } # !!!!!
1009
+ after { puts "=== Outer: after ===" } # !!!!!
1010
+
1011
+ topic 'Middle' do
1012
+ before { puts "==== Middle: before ====" } # !!!!!
1013
+ after { puts "==== Middle: after ====" } # !!!!!
1014
+
1015
+ topic 'Inner' do
1016
+ before { puts "===== Inner: before =====" } # !!!!!
1017
+ after { puts "===== Inner: after =====" } # !!!!!
1018
+
1019
+ spec "example" do
1020
+ ok {1+1} == 2
1021
+ end
1022
+
1023
+ end
1024
+
1025
+ end
1026
+
1027
+ end
1028
+
1029
+ end
1030
+ ```
1031
+
1032
+ Result:
1033
+
1034
+ ```terminal
1035
+ $ oktest -s plain test/example21b_test.rb
1036
+ === Outer: before ===
1037
+ ==== Middle: before ====
1038
+ ===== Inner: before =====
1039
+ ===== Inner: after =====
1040
+ ==== Middle: after ====
1041
+ === Outer: after ===
1042
+ .
1043
+ ## total:1 (pass:1, fail:0, error:0, skip:0, todo:0) in 0.000s
1044
+ ```
1045
+
1046
+ If something error raised in `before`/`after`/`before_all`/`after_all` blocks,
1047
+ test script execution will be stopped instantly.
1048
+
1049
+
1050
+ ### `at_end()`: Crean-up Handler
1051
+
1052
+ It is possible to register clean-up operation with `at_end()`.
1053
+
1054
+ test/example22_test.rb:
1055
+
1056
+ ```ruby
1057
+ require 'oktest'
1058
+
1059
+ Oktest.scope do
1060
+
1061
+ topic "Auto clean-up" do
1062
+
1063
+ spec "example spec" do
1064
+ tmpfile = "tmp123.txt"
1065
+ File.write(tmpfile, "foobar\n")
1066
+ at_end do # register clean-up operation
1067
+ File.unlink(tmpfile)
1068
+ end
1069
+ #
1070
+ ok {tmpfile}.file_exist?
1071
+ end
1072
+
1073
+ end
1074
+
1075
+ end
1076
+ ```
1077
+
1078
+ * `at_end()` can be called multiple times.
1079
+ * Registered blocks are invoked in reverse order at end of test case.
1080
+ * Registered blocks of `at_end()` are invoked prior to block of `after()`.
1081
+ * If something error raised in `at_end()`, test script execution will be
1082
+ stopped instantly.
1083
+
1084
+
1085
+ ### Named Fixtures
1086
+
1087
+ `fixture() { ... }` in topic or scope block defines fixture builder,
1088
+ and `fixture()` in scope block returns fixture data.
1089
+
1090
+ test/example23_test.rb:
1091
+
1092
+ ```ruby
1093
+ require 'oktest'
1094
+
1095
+ Oktest.scope do
1096
+
1097
+ fixture :alice do # define fixture
1098
+ {name: "Alice"}
1099
+ end
1100
+
1101
+ fixture :bob do # define fixture
1102
+ {name: "Bob"}
1103
+ end
1104
+
1105
+ topic "Named fixture" do
1106
+
1107
+ spec "example spec" do
1108
+ alice = fixture(:alice) # create fixture object
1109
+ bob = fixture(:bob) # create fixture object
1110
+ ok {alice[:name]} == "Alice"
1111
+ ok {bob[:name]} == "Bob"
1112
+ end
1113
+
1114
+ end
1115
+
1116
+ end
1117
+ ```
1118
+
1119
+ Fixture block can have parameters.
1120
+
1121
+ test/example24_test.rb:
1122
+
1123
+ ```ruby
1124
+ require 'oktest'
1125
+
1126
+ Oktest.scope do
1127
+
1128
+ fixture :team do |mem1, mem2| # define fixture with block params
1129
+ {members: [mem1, mem2]}
1130
+ end
1131
+
1132
+ topic "Named fixture with args" do
1133
+
1134
+ spec "example spec" do
1135
+ alice = {name: "Alice"}
1136
+ bob = {name: "Bob"}
1137
+ team = fixture(:team, alice, bob) # create fixture with args
1138
+ ok {team[:members][0][:name]} == "Alice"
1139
+ ok {team[:members][1][:name]} == "Bob"
1140
+ end
1141
+
1142
+ end
1143
+
1144
+ end
1145
+ ```
1146
+
1147
+ * Fixtures can be defined in block of `topic()` as well as block of `Object.scope()`.
1148
+ * If fixture requires clean-up operation, call `at_end()` in `fixture()` block.
1149
+
1150
+ ```ruby
1151
+ fixture :tmpfile do
1152
+ tmpfile = "tmp#{rand().to_s[2..5]}.txt"
1153
+ File.write(tmpfile, "foobar\n", encoding: 'utf-8')
1154
+ at_end { File.unlink(tmpfile) if File.exist?(tmpfile) } # !!!!!
1155
+ tmpfile
1156
+ end
1157
+ ```
1158
+
1159
+
1160
+ ### Fixture Injection
1161
+
1162
+ Block parameters of `spec()` or `fixture()` represents fixture name, and
1163
+ Oktest.rb injects fixture objects into that parameters automatically.
1164
+
1165
+ test/example25_test.rb:
1166
+
1167
+ ```ruby
1168
+ require 'oktest'
1169
+
1170
+ Oktest.scope do
1171
+
1172
+ fixture :alice do # define fixture
1173
+ {name: "Alice"}
1174
+ end
1175
+
1176
+ fixture :bob do # define fixture
1177
+ {name: "Bob"}
1178
+ end
1179
+
1180
+ fixture :team do |alice, bob| # !!! fixture injection !!!
1181
+ {members: [alice, bob]}
1182
+ end
1183
+
1184
+ topic "Fixture injection" do
1185
+
1186
+ spec "example spec" do
1187
+ |alice, bob, team| # !!! fixture injection !!!
1188
+ ok {alice[:name]} == "Alice"
1189
+ ok {bob[:name]} == "Bob"
1190
+ #
1191
+ ok {team[:members]}.length(2)
1192
+ ok {team[:members][0]} == {name: "Alice"}
1193
+ ok {team[:members][1]} == {name: "Bob"}
1194
+ end
1195
+
1196
+ end
1197
+
1198
+ end
1199
+ ```
1200
+
1201
+ <!--
1202
+ * Special fixture name `this_topic` represents the first argument of `topic()`.
1203
+ * Special fixture name `this_spec` represents the description of `spec()`.
1204
+ -->
1205
+
1206
+
1207
+ ### Global Scope
1208
+
1209
+ It is a good idea to separate common fixtures into dedicated file.
1210
+ In this case, use `Oktest.global_scope()` instead of `Oktest.scope()`.
1211
+
1212
+ test/common_fixtures.rb:
1213
+
1214
+ ```ruby
1215
+ require 'oktest'
1216
+
1217
+ ## define common fixtures in global scope
1218
+ Oktest.global_scope do # !!!!!
1219
+
1220
+ fixture :alice do
1221
+ {name: "Alice"}
1222
+ end
1223
+
1224
+ fixture :bob do
1225
+ {name: "Bob"}
1226
+ end
1227
+
1228
+ fixture :team do |alice, bob|
1229
+ {members: [alice, bob]}
1230
+ end
1231
+
1232
+ end
1233
+ ```
1234
+
1235
+
1236
+
1237
+ ## Helpers
1238
+
1239
+
1240
+ ### `capture_sio()`
1241
+
1242
+ `capture_sio()` captures standard I/O.
1243
+
1244
+ test/example31_test.rb:
1245
+
1246
+ ```ruby
1247
+ require 'oktest'
1248
+
1249
+ Oktest.scope do
1250
+
1251
+ topic "Capturing" do
1252
+
1253
+ spec "example spec" do
1254
+ data = nil
1255
+ sout, serr = capture_sio("blabla") do # !!!!!
1256
+ data = $stdin.read() # read from stdin
1257
+ puts "fooo" # write into stdout
1258
+ $stderr.puts "baaa" # write into stderr
1259
+ end
1260
+ ok {data} == "blabla"
1261
+ ok {sout} == "fooo\n"
1262
+ ok {serr} == "baaa\n"
1263
+ end
1264
+
1265
+ end
1266
+
1267
+ end
1268
+ ```
1269
+
1270
+ * First argument of `capture_sio()` represents data from `$stdin`.
1271
+ If it is not necessary, you can omit it like `caputre_sio() do ... end`.
1272
+ * If you need `$stdin.tty? == true` and `$stdout.tty? == true`,
1273
+ call `capture_sio(tty: true) do ... end`.
1274
+
1275
+
1276
+ ### `dummy_file()`
1277
+
1278
+ `dummy_file()` creates dummy file temporarily.
1279
+
1280
+ test/example32_test.rb:
1281
+
1282
+ ```ruby
1283
+ require 'oktest'
1284
+
1285
+ Oktest.scope do
1286
+
1287
+ topic "dummy_file()" do
1288
+
1289
+ spec "usage #1: without block" do
1290
+ tmpfile = dummy_file("_tmp_file.txt", "blablabla") # !!!!!
1291
+ ok {tmpfile} == "_tmp_file.txt"
1292
+ ok {tmpfile}.file_exist?
1293
+ ## dummy file will be removed automatically at end of spec block.
1294
+ end
1295
+
1296
+ spec "usage #2: with block" do
1297
+ result = dummy_file("_tmp_file.txt", "blabla") do |tmpfile| # !!!!!
1298
+ ok {tmpfile} == "_tmp_file.txt"
1299
+ ok {tmpfile}.file_exist?
1300
+ ## dummy file will be removed automatically at end of this block.
1301
+ ## last value of block will be the return value of dummy_file().
1302
+ 1234
1303
+ end
1304
+ ok {result} == 1234
1305
+ ok {"_tmp_file.txt"}.not_exist?
1306
+ end
1307
+
1308
+ end
1309
+
1310
+ end
1311
+ ```
1312
+
1313
+ * If first argument of `dummy_file()` is nil, then it generates temporary file name automatically.
1314
+
1315
+
1316
+ ### `dummy_dir()`
1317
+
1318
+ `dummy_dir()` creates dummy directory temporarily.
1319
+
1320
+ test/example33_test.rb:
1321
+
1322
+ ```ruby
1323
+ require 'oktest'
1324
+
1325
+ Oktest.scope do
1326
+
1327
+ topic "dummy_dir()" do
1328
+
1329
+ spec "usage #1: without block" do
1330
+ tmpdir = dummy_dir("_tmp_dir") # !!!!!
1331
+ ok {tmpdir} == "_tmp_dir"
1332
+ ok {tmpdir}.dir_exist?
1333
+ ## dummy directory will be removed automatically at end of spec block
1334
+ ## even if it contais other files or directories.
1335
+ end
1336
+
1337
+ spec "usage #2: with block" do
1338
+ result = dummy_dir("_tmp_dir") do |tmpdir| # !!!!!
1339
+ ok {tmpdir} == "_tmp_dir"
1340
+ ok {tmpdir}.dir_exist?
1341
+ ## dummy directory will be removed automatically at end of this block
1342
+ ## even if it contais other files or directories.
1343
+ ## last value of block will be the return value of dummy_dir().
1344
+ 2345
1345
+ end
1346
+ ok {result} == 2345
1347
+ ok {"_tmp_dir"}.not_exist?
1348
+ end
1349
+
1350
+ end
1351
+
1352
+ end
1353
+ ```
1354
+
1355
+ * If first argument of `dummy_dir()` is nil, then it generates temorary directory name automatically.
1356
+
1357
+
1358
+ ### `dummy_values()`
1359
+
1360
+ `dummy_values()` changes hash values temporarily.
1361
+
1362
+ test/example34_test.rb:
1363
+
1364
+ ```ruby
1365
+ require 'oktest'
1366
+
1367
+ Oktest.scope do
1368
+
1369
+ topic "dummy_values()" do
1370
+
1371
+ spec "usage #1: without block" do
1372
+ hashobj = {:a=>1, 'b'=>2, :c=>3} # `:x` is not a key
1373
+ ret = dummy_values(hashobj, :a=>100, 'b'=>200, :x=>900) # !!!!!
1374
+ ok {hashobj[:a]} == 100
1375
+ ok {hashobj['b']} == 200
1376
+ ok {hashobj[:c]} == 3
1377
+ ok {hashobj[:x]} == 900
1378
+ ok {ret} == {:a=>100, 'b'=>200, :x=>900}
1379
+ ## values of hash object are recovered at end of spec block.
1380
+ end
1381
+
1382
+ spec "usage #2: with block" do
1383
+ hashobj = {:a=>1, 'b'=>2, :c=>3} # `:x` is not a key
1384
+ ret = dummy_values(hashobj, :a=>100, 'b'=>200, :x=>900) do |keyvals| # !!!!!
1385
+ ok {hashobj[:a]} == 100
1386
+ ok {hashobj['b']} == 200
1387
+ ok {hashobj[:c]} == 3
1388
+ ok {hashobj[:x]} == 900
1389
+ ok {keyvals} == {:a=>100, 'b'=>200, :x=>900}
1390
+ ## values of hash object are recovered at end of this block.
1391
+ ## last value of block will be the return value of dummy_values().
1392
+ 3456
1393
+ end
1394
+ ok {hashobj[:a]} == 1
1395
+ ok {hashobj['b']} == 2
1396
+ ok {hashobj[:c]} == 3
1397
+ not_ok {hashobj}.key?(:x) # because `:x` was not a key
1398
+ ok {ret} == 3456
1399
+ end
1400
+
1401
+ end
1402
+
1403
+ end
1404
+ ```
1405
+
1406
+ * `dummy_values()` is very useful to change envirnment variables temporarily,
1407
+ such as `dummy_values(ENV, 'LANG'=>'en_US.UTF-8')`.
1408
+
1409
+
1410
+ ### `dummy_attrs()`
1411
+
1412
+ `dummy_attrs()` changes object attribute values temporarily.
1413
+
1414
+ test/example35_test.rb:
1415
+
1416
+ ```ruby
1417
+ require 'oktest'
1418
+
1419
+ class User
1420
+ def initialize(id, name)
1421
+ @id = id
1422
+ @name = name
1423
+ end
1424
+ attr_accessor :id, :name
1425
+ end
1426
+
1427
+ Oktest.scope do
1428
+
1429
+ topic "dummy_attrs()" do
1430
+
1431
+ spec "usage #1: without block" do
1432
+ user = User.new(123, "alice")
1433
+ ok {user.id} == 123
1434
+ ok {user.name} == "alice"
1435
+ ret = dummy_attrs(user, :id=>999, :name=>"bob") # !!!!!
1436
+ ok {user.id} == 999
1437
+ ok {user.name} == "bob"
1438
+ ok {ret} == {:id=>999, :name=>"bob"}
1439
+ ## attribute values are recovered at end of spec block.
1440
+ end
1441
+
1442
+ spec "usage #2: with block" do
1443
+ user = User.new(123, "alice")
1444
+ ok {user.id} == 123
1445
+ ok {user.name} == "alice"
1446
+ ret = dummy_attrs(user, :id=>999, :name=>"bob") do |keyvals| # !!!!!
1447
+ ok {user.id} == 999
1448
+ ok {user.name} == "bob"
1449
+ ok {keyvals} == {:id=>999, :name=>"bob"}
1450
+ ## attribute values are recovered at end of this block.
1451
+ ## last value of block will be the return value of dummy_attrs().
1452
+ 4567
1453
+ end
1454
+ ok {user.id} == 123
1455
+ ok {user.name} == "alice"
1456
+ ok {ret} == 4567
1457
+ end
1458
+
1459
+ end
1460
+
1461
+ end
1462
+ ```
1463
+
1464
+
1465
+ ### `dummy_ivars()`
1466
+
1467
+ `dummy_ivars()` changes instance variables in object with dummy values temporarily.
1468
+
1469
+ test/example36_test.rb:
1470
+
1471
+ ```ruby
1472
+ require 'oktest'
1473
+
1474
+ class User
1475
+ def initialize(id, name)
1476
+ @id = id
1477
+ @name = name
1478
+ end
1479
+ attr_reader :id, :name # setter, not accessor
1480
+ end
1481
+
1482
+ Oktest.scope do
1483
+
1484
+ topic "dummy_attrs()" do
1485
+
1486
+ spec "usage #1: without block" do
1487
+ user = User.new(123, "alice")
1488
+ ok {user.id} == 123
1489
+ ok {user.name} == "alice"
1490
+ ret = dummy_ivars(user, :id=>999, :name=>"bob") # !!!!!
1491
+ ok {user.id} == 999
1492
+ ok {user.name} == "bob"
1493
+ ok {ret} == {:id=>999, :name=>"bob"}
1494
+ ## attribute values are recovered at end of spec block.
1495
+ end
1496
+
1497
+ spec "usage #2: with block" do
1498
+ user = User.new(123, "alice")
1499
+ ok {user.id} == 123
1500
+ ok {user.name} == "alice"
1501
+ ret = dummy_ivars(user, :id=>999, :name=>"bob") do |keyvals| # !!!!!
1502
+ ok {user.id} == 999
1503
+ ok {user.name} == "bob"
1504
+ ok {keyvals} == {:id=>999, :name=>"bob"}
1505
+ ## attribute values are recovered at end of this block.
1506
+ ## last value of block will be the return value of dummy_attrs().
1507
+ 6789
1508
+ end
1509
+ ok {user.id} == 123
1510
+ ok {user.name} == "alice"
1511
+ ok {ret} == 6789
1512
+ end
1513
+
1514
+ end
1515
+
1516
+ end
1517
+ ```
1518
+
1519
+
1520
+ ### `recorder()`
1521
+
1522
+ `recorder()` returns Benry::Recorder object.
1523
+ See [Benry::Recorder README](https://github.com/kwatch/benry-ruby/blob/ruby/benry-recorder/README.md)
1524
+ for detals.
1525
+
1526
+ test/example37_test.rb:
1527
+
1528
+ ```ruby
1529
+ require 'oktest'
1530
+
1531
+ class Calc
1532
+ def total(*nums)
1533
+ t = 0; nums.each {|n| t += n } # or: nums.sum()
1534
+ return t
1535
+ end
1536
+ def average(*nums)
1537
+ return total(*nums).to_f / nums.length
1538
+ end
1539
+ end
1540
+
1541
+
1542
+ Oktest.scope do
1543
+
1544
+ topic 'recorder()' do
1545
+
1546
+ spec "records method calls." do
1547
+ ## target object
1548
+ calc = Calc.new
1549
+ ## record method call
1550
+ rec = recorder() # !!!!!
1551
+ rec.record_method(calc, :total)
1552
+ ## method call
1553
+ v = calc.average(1, 2, 3, 4) # calc.average() calls calc.total() internally
1554
+ p v #=> 2.5
1555
+ ## show method call info
1556
+ p rec.length #=> 1
1557
+ p rec[0].obj == calc #=> true
1558
+ p rec[0].name #=> :total
1559
+ p rec[0].args #=> [1, 2, 3, 4]
1560
+ p rec[0].ret #=> 2.5
1561
+ end
1562
+
1563
+ spec "defines fake methods." do
1564
+ ## target object
1565
+ calc = Calc.new
1566
+ ## define fake methods
1567
+ rec = recorder() # !!!!!
1568
+ rec.fake_method(calc, :total=>20, :average=>5.5)
1569
+ ## call fake methods
1570
+ v1 = calc.total(1, 2, 3) # fake method returns dummy value
1571
+ p v1 #=> 20
1572
+ v2 = calc.average(1, 2, 'a'=>3) # fake methods accepts any args
1573
+ p v2 #=> 5.5
1574
+ ## show method call info
1575
+ puts rec.inspect
1576
+ #=> 0: #<Calc:0x00007fdb5482c968>.total(1, 2, 3) #=> 20
1577
+ # 1: #<Calc:0x00007fdb5482c968>.average(1, 2, {"a"=>3}) #=> 5.5
1578
+ end
1579
+
1580
+ end
1581
+
1582
+ end
1583
+ ```
1584
+
1585
+
1586
+
1587
+ ## Tips
1588
+
1589
+
1590
+ ### `ok {}` in MiniTest
1591
+
1592
+ If you want to use `ok {actual} == expected` style assertion in MiniTest,
1593
+ install `minitest-ok` gem instead of `otest` gem.
1594
+
1595
+ test/example41_test.rb:
1596
+
1597
+ ```ruby
1598
+ require 'minitest/spec'
1599
+ require 'minitest/autorun'
1600
+ require 'minitest/ok' # !!!!!
1601
+
1602
+ describe 'MiniTest::Ok' do
1603
+
1604
+ it "helps to write assertions" do
1605
+ ok {1+1} == 2 # !!!!!
1606
+ end
1607
+
1608
+ end
1609
+ ```
1610
+
1611
+ See [minitest-ok README](https://github.com/kwatch/minitest-ok) for details.
1612
+
1613
+
1614
+ ### Testing Rack Application
1615
+
1616
+ `rack-test_app` gem will help you to test Rack application very well.
1617
+
1618
+ test/example42_test.rb:
1619
+
1620
+ ```ruby
1621
+ require 'rack'
1622
+ require 'rack/lint'
1623
+ require 'rack/test_app' # !!!!!
1624
+ require 'oktest'
1625
+
1626
+ app = proc {|env| # sample Rack application
1627
+ text = '{"status":"OK"}'
1628
+ headers = {"Content-Type" => "application/json",
1629
+ "Content-Length" => text.bytesize.to_s}
1630
+ [200, headers, [text]]
1631
+ }
1632
+
1633
+ http = Rack::TestApp.wrap(Rack::Lint.new(app)) # wrap Rack app
1634
+
1635
+ Oktest.scope do
1636
+
1637
+ + topic("GET /api/hello") do
1638
+
1639
+ - spec("returns JSON data.") do
1640
+ response = http.GET("/api/hello") # call Rack app
1641
+ ok {response.status} == 200
1642
+ ok {response.content_type} == "application/json"
1643
+ ok {response.body_json} == {"status"=>"OK"}
1644
+ end
1645
+
1646
+ end
1647
+
1648
+ end
1649
+ ```
1650
+
1651
+ Defining helper method per topic may help you.
1652
+
1653
+ ```ruby
1654
+ $http = http # !!!!
1655
+
1656
+ Oktest.scope do
1657
+
1658
+ + topic("GET /api/hello") do
1659
+
1660
+ def api_call(**kwargs) # !!!!!
1661
+ $http.GET("/api/hello", **kwargs) # !!!!!
1662
+ end # !!!!!
1663
+
1664
+ - spec("returns JSON data.") do
1665
+ response = api_call() # !!!!!
1666
+ ok {response.status} == 200
1667
+ ok {response.content_type} == "application/json"
1668
+ ok {response.body_json} == {"status"=>"OK"}
1669
+ end
1670
+
1671
+ end
1672
+
1673
+ + topic("POST /api/hello") do
1674
+
1675
+ def api_call(**kwargs) # !!!!!
1676
+ $http.POST("/api/hello", **kwargs) # !!!!!
1677
+ end # !!!!!
1678
+
1679
+ ....
1680
+
1681
+ end
1682
+
1683
+ end
1684
+ ```
1685
+
1686
+
1687
+ ### Traverser Class
1688
+
1689
+ Oktest.rb provides `Traverser` class which implements Visitor pattern.
1690
+
1691
+ test/example44_test.rb:
1692
+
1693
+ ```ruby
1694
+ require 'oktest'
1695
+
1696
+ Oktest.scope do
1697
+ + topic('Example Topic') do
1698
+ - spec("sample #1") do ok {1+1} == 2 end
1699
+ - spec("sample #2") do ok {1-1} == 0 end
1700
+ + case_when('some condition...') do
1701
+ - spec("sample #3") do ok {1*1} == 1 end
1702
+ end
1703
+ + case_else() do
1704
+ - spec("sample #4") do ok {1/1} == 1 end
1705
+ end
1706
+ end
1707
+ end
1708
+
1709
+ ## custom traverser class
1710
+ class MyTraverser < Oktest::Traverser # !!!!!
1711
+ def on_scope(filename, tag, depth) # !!!!!
1712
+ print " " * depth
1713
+ print "# scope: #{filename}"
1714
+ print " (tag: #{tag})" if tag
1715
+ print "\n"
1716
+ yield # should yield !!!
1717
+ end
1718
+ def on_topic(target, tag, depth) # !!!!!
1719
+ print " " * depth
1720
+ print "+ topic: #{target}"
1721
+ print " (tag: #{tag})" if tag
1722
+ print "\n"
1723
+ yield # should yield !!!
1724
+ end
1725
+ def on_case(cond, tag, depth) # !!!!!
1726
+ print " " * depth
1727
+ print "+ case: #{cond}"
1728
+ print " (tag: #{tag})" if tag
1729
+ print "\n"
1730
+ yield # should yield !!!
1731
+ end
1732
+ def on_spec(desc, tag, depth) # !!!!!
1733
+ print " " * depth
1734
+ print "- spec: #{desc}"
1735
+ print " (tag: #{tag})" if tag
1736
+ print "\n"
1737
+ end
1738
+ end
1739
+
1740
+ ## run custom traverser
1741
+ Oktest::Config.auto_run = false # stop running test cases
1742
+ MyTraverser.new.start()
1743
+ ```
1744
+
1745
+ Result:
1746
+
1747
+ ```terminal
1748
+ $ ruby test/example44_test.rb
1749
+ # scope: test/example44_test.rb
1750
+ + topic: Example Topic
1751
+ - spec: sample #1
1752
+ - spec: sample #2
1753
+ + case: When some condition...
1754
+ - spec: sample #3
1755
+ + case: Else
1756
+ - spec: sample #4
1757
+ ```
1758
+
1759
+
1760
+ ### Benchmarks
1761
+
1762
+ Oktest.rb gem file contains benchmark script.
1763
+ It shows that Oktest.rb runs more than three times faster than RSpec.
1764
+
1765
+ ```terminal
1766
+ $ gem install oktest # ver 1.0.0
1767
+ $ gem install rspec # ver 3.10.0
1768
+ $ gem install minitest # ver 5.14.4
1769
+ $ gem install test-unit # ver 3.4.4
1770
+
1771
+ $ cp -pr $GEM_HOME/gems/oktest-1.0.0/benchmark .
1772
+ $ cd benchmark/
1773
+ $ rake -T
1774
+ $ ruby --version
1775
+ ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin18]
1776
+
1777
+ $ rake benchmark:all
1778
+ ```
1779
+
1780
+ Example result:
1781
+
1782
+ ```
1783
+ ==================== oktest ====================
1784
+ oktest -sq run_all.rb
1785
+
1786
+ ## total:100000 (pass:100000, fail:0, error:0, skip:0, todo:0) in 4.86s
1787
+
1788
+ 8.536 real 8.154 user 0.245 sys
1789
+
1790
+ ==================== oktest:faster ====================
1791
+ oktest -sq --faster run_all.rb
1792
+
1793
+ ## total:100000 (pass:100000, fail:0, error:0, skip:0, todo:0) in 1.64s
1794
+
1795
+ 5.068 real 4.819 user 0.202 sys
1796
+
1797
+ ==================== rspec ====================
1798
+ rspec run_all.rb | tail -4
1799
+
1800
+ Finished in 14.44 seconds (files took 15.81 seconds to load)
1801
+ 100000 examples, 0 failures
1802
+
1803
+
1804
+ 30.798 real 26.565 user 4.392 sys
1805
+
1806
+ ==================== minitest ====================
1807
+ ruby run_all.rb | tail -4
1808
+
1809
+ Finished in 5.190405s, 19266.3193 runs/s, 19266.3193 assertions/s.
1810
+
1811
+ 100000 runs, 100000 assertions, 0 failures, 0 errors, 0 skips
1812
+
1813
+ 8.767 real 8.157 user 0.761 sys
1814
+
1815
+ ==================== testunit ====================
1816
+ ruby run_all.rb | tail -5
1817
+ -------------------------------------------------------------------------------
1818
+ 100000 tests, 100000 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
1819
+ 100% passed
1820
+ -------------------------------------------------------------------------------
1821
+ 8957.19 tests/s, 8957.19 assertions/s
1822
+
1823
+ 17.838 real 17.201 user 0.879 sys
1824
+ ```
1825
+
1826
+
1827
+ ### `--faster` Option
1828
+
1829
+ `ok {}` is slightly slower than `assert()` in MiniTest.
1830
+ In almost case, you don't need to care about it. But if you are working in
1831
+ very larget project and you want to run test scripts as fast as possible,
1832
+ try `--faster` option of `oktest` command.
1833
+
1834
+ ```terminal
1835
+ $ oktest -s quiet --faster test/ ## only for very large project
1836
+ ```
1837
+
1838
+ Or set `Oktest::Config.ok_location = false` in your test script.
1839
+
1840
+ ```ruby
1841
+ require 'oktest'
1842
+ Oktest::Config.ok_location = false ## only for very large project
1843
+ ```
1844
+
1845
+
1846
+
1847
+ ## Change Log
1848
+
1849
+ See [CHANGES.md](https://github.com/kwatch/oktest/blob/ruby/ruby/CHANGES.md).
1850
+
1851
+
1852
+
1853
+ ## License and Copyright
1854
+
1855
+ * $License: MIT License $
1856
+ * $Copyright: copyright(c) 2011-2021 kuwata-lab.com all rights reserved $