dtest 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +3 -0
- data/LICENSE.txt +202 -0
- data/README.rdoc +26 -0
- data/Rakefile +21 -0
- data/VERSION +1 -0
- data/bin/dtest +4 -0
- data/dtest.gemspec +34 -0
- data/examples/sample.rb +130 -0
- data/lib/dtest.rb +0 -0
- data/lib/dtest/command.rb +33 -0
- data/lib/dtest/core.rb +257 -0
- data/lib/dtest/dsl.rb +19 -0
- data/lib/dtest/failure.rb +27 -0
- data/lib/dtest/global.rb +91 -0
- data/lib/dtest/progress.rb +166 -0
- data/lib/dtest/report.rb +121 -0
- data/lib/dtest/result.rb +189 -0
- data/lib/dtest/runner.rb +22 -0
- data/lib/dtest/test.rb +212 -0
- data/lib/dtest/util.rb +43 -0
- data/lib/dtest/version.rb +5 -0
- data/spec/test_abort_spec.rb +772 -0
- data/spec/test_assert_spec.rb +146 -0
- data/spec/test_exception_spec.rb +303 -0
- data/spec/test_expect_spec.rb +288 -0
- data/spec/test_spec.rb +272 -0
- data/spec/test_value_parameterized_spec.rb +46 -0
- metadata +101 -0
data/lib/dtest/report.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
|
2
|
+
module DTest
|
3
|
+
class Report
|
4
|
+
private
|
5
|
+
TAG_BASE = [
|
6
|
+
'Global', 'TestCase', 'Test', 'RUN', 'OK', 'FAIL',
|
7
|
+
'PASSED', 'FAILED', 'TESTED', 'UNTEST',
|
8
|
+
]
|
9
|
+
TAG_S = TAG_BASE.map {|t| " #{t} "}
|
10
|
+
MAX_LEN = TAG_S.map {|s| s.size}.max
|
11
|
+
|
12
|
+
TAG_MAP = Hash[[TAG_BASE.map {|t| t.downcase.to_sym}, TAG_S].transpose].merge({
|
13
|
+
:line => '-' * MAX_LEN,
|
14
|
+
:empty => ''
|
15
|
+
})
|
16
|
+
|
17
|
+
TAGS_COLOR = {
|
18
|
+
:global => :blue,
|
19
|
+
:testcase => :blue,
|
20
|
+
:test => :blue,
|
21
|
+
|
22
|
+
:run => :yellow,
|
23
|
+
:fail => :red,
|
24
|
+
:ok => :green,
|
25
|
+
|
26
|
+
:passed => :green,
|
27
|
+
:failed => :green,
|
28
|
+
:tested => :green,
|
29
|
+
:untest => :green,
|
30
|
+
}
|
31
|
+
|
32
|
+
# symbol to string
|
33
|
+
def self.tag_s(tag)
|
34
|
+
if TAG_MAP.include?(tag)
|
35
|
+
text = TAG_MAP[tag]
|
36
|
+
else
|
37
|
+
text = tag.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.colored_tag(tag)
|
42
|
+
text = tag_s(tag)
|
43
|
+
|
44
|
+
if TAGS_COLOR.include?(tag)
|
45
|
+
send(TAGS_COLOR[tag], text)
|
46
|
+
else
|
47
|
+
text
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.space(size)
|
52
|
+
if size > 0
|
53
|
+
' ' * size
|
54
|
+
else
|
55
|
+
''
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.s_center(tag)
|
60
|
+
len = MAX_LEN - tag_s(tag).size
|
61
|
+
left = len / 2
|
62
|
+
right = len / 2 + (len.odd? ? 1 : 0)
|
63
|
+
space(left) + colored_tag(tag) + space(right)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.s_left(tag)
|
67
|
+
colored_tag(tag) + space(MAX_LEN - tag_s(tag).size)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.s_right(tag)
|
71
|
+
space(MAX_LEN - tag_s(tag).size) + colored_tag(tag)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.colorize(text, code)
|
75
|
+
if @color_enabled
|
76
|
+
"#{code}#{text}\e[0m"
|
77
|
+
else
|
78
|
+
text
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.red(text)
|
83
|
+
colorize(text, "\e[31m")
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.green(text)
|
87
|
+
colorize(text, "\e[32m")
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.yellow(text)
|
91
|
+
colorize(text, "\e[33m")
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.blue(text)
|
95
|
+
colorize(text, "\e[34m")
|
96
|
+
end
|
97
|
+
|
98
|
+
public
|
99
|
+
def self.color_enabled=(t)
|
100
|
+
@color_enabled = t
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.split(count = 30)
|
104
|
+
puts ''
|
105
|
+
puts '-' * count
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.tag(s, text = '')
|
109
|
+
puts "[#{s_center(s)}] #{text}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.left(s, text = '')
|
113
|
+
puts "[#{s_left(s)}] #{text}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.right(s, text = '')
|
117
|
+
puts "[#{s_right(s)}] #{text}"
|
118
|
+
end
|
119
|
+
|
120
|
+
end # class Report
|
121
|
+
end # module DTest
|
data/lib/dtest/result.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'dtest/failure'
|
2
|
+
|
3
|
+
module DTest
|
4
|
+
|
5
|
+
module Test
|
6
|
+
class Failure
|
7
|
+
def initialize
|
8
|
+
@failure = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(s)
|
12
|
+
@failure << s
|
13
|
+
end
|
14
|
+
|
15
|
+
def empty?
|
16
|
+
@failure.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure
|
20
|
+
@failure
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# before/after result
|
25
|
+
module BAResult
|
26
|
+
attr_accessor :before_failure, :after_failure
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
super
|
30
|
+
@before_failure = Failure.new
|
31
|
+
@after_failure = Failure.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def ba_empty?
|
35
|
+
@before_failure.empty? && @after_failure.empty?
|
36
|
+
end
|
37
|
+
end #module BAResult
|
38
|
+
|
39
|
+
class FailureMessage
|
40
|
+
attr_accessor :parent, :name
|
41
|
+
attr_accessor :file, :line, :error_line
|
42
|
+
|
43
|
+
def initialize(parent, name, message, backtrace)
|
44
|
+
@parent = parent
|
45
|
+
@nane = name
|
46
|
+
@message = message
|
47
|
+
|
48
|
+
@file, @line, @error_line = DTest::failure_line(backtrace)
|
49
|
+
end
|
50
|
+
|
51
|
+
def location
|
52
|
+
if file && line
|
53
|
+
"#{file}:#{line}\n"
|
54
|
+
else
|
55
|
+
""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def all
|
60
|
+
location + @message
|
61
|
+
end
|
62
|
+
|
63
|
+
def print
|
64
|
+
#str += "[#{parent}]" if parent
|
65
|
+
#str += " '#{name}'\n" if name
|
66
|
+
str = @message
|
67
|
+
str += " Failure/Error: #{error_line}\n" if error_line
|
68
|
+
str += " # #{file}:#{line}\n" if file && line
|
69
|
+
puts "#{str}\n"
|
70
|
+
end
|
71
|
+
end # FailureMessage
|
72
|
+
|
73
|
+
|
74
|
+
class Result < Failure
|
75
|
+
include Stopwatch
|
76
|
+
include BAResult
|
77
|
+
|
78
|
+
PASS = 'Pass'
|
79
|
+
FAIL = 'Fail'
|
80
|
+
UNTEST = 'Untested'
|
81
|
+
|
82
|
+
attr_accessor :name, :result
|
83
|
+
|
84
|
+
def initialize(name)
|
85
|
+
super()
|
86
|
+
@name = name
|
87
|
+
@result = FAIL
|
88
|
+
end
|
89
|
+
end # class Result
|
90
|
+
|
91
|
+
class CaseResult
|
92
|
+
include Stopwatch
|
93
|
+
include BAResult
|
94
|
+
|
95
|
+
attr_accessor :name
|
96
|
+
attr_accessor :result
|
97
|
+
attr_accessor :passed, :failed, :executed, :untested
|
98
|
+
|
99
|
+
def initialize(name)
|
100
|
+
super()
|
101
|
+
@name = name
|
102
|
+
@passed = 0
|
103
|
+
@failed = 0
|
104
|
+
@executed = 0
|
105
|
+
@untested = 0
|
106
|
+
# list of Result
|
107
|
+
@result = []
|
108
|
+
end
|
109
|
+
|
110
|
+
def add(result)
|
111
|
+
@result << result
|
112
|
+
end
|
113
|
+
end # class CaseResult
|
114
|
+
|
115
|
+
class GlobalResult
|
116
|
+
include Stopwatch
|
117
|
+
include BAResult
|
118
|
+
|
119
|
+
attr_accessor :result
|
120
|
+
|
121
|
+
def initialize(testcases)
|
122
|
+
super()
|
123
|
+
@result = []
|
124
|
+
@test_size = testcases.inject(0) { |sum, t| sum += t.test.size}
|
125
|
+
end
|
126
|
+
|
127
|
+
def add(res)
|
128
|
+
@result << res
|
129
|
+
end
|
130
|
+
|
131
|
+
def passed
|
132
|
+
@passed = result.inject(0) {|sum, r| sum += r.passed} unless @passed
|
133
|
+
@passed
|
134
|
+
end
|
135
|
+
|
136
|
+
def failed
|
137
|
+
@failed ||= result.inject(0) {|sum, r| sum += r.failed}
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def executed
|
142
|
+
@executed ||= result.inject(0) {|sum, r| sum += r.executed}
|
143
|
+
end
|
144
|
+
|
145
|
+
def untested
|
146
|
+
@untested ||= @test_size - executed
|
147
|
+
end
|
148
|
+
|
149
|
+
def outputxml(output_path)
|
150
|
+
doc = REXML::Document.new
|
151
|
+
root = doc.add_element('testsuites', {
|
152
|
+
'name' => 'Global',
|
153
|
+
'tests' => executed,
|
154
|
+
'failures' => failed,
|
155
|
+
'errors' => 0,
|
156
|
+
'time' => elapsed,
|
157
|
+
})
|
158
|
+
|
159
|
+
result.each do |result|
|
160
|
+
suite = root.add_element('testsuite', {
|
161
|
+
'name' => result.name,
|
162
|
+
'tests' => result.executed,
|
163
|
+
'failures' => result.failed,
|
164
|
+
'errors' => 0,
|
165
|
+
'time' => result.elapsed,
|
166
|
+
})
|
167
|
+
|
168
|
+
result.result.each do |t|
|
169
|
+
test = suite.add_element('testcase', {
|
170
|
+
'name' => t.name,
|
171
|
+
'status' => 'run',
|
172
|
+
'classname' => result.name,
|
173
|
+
'time' => t.elapsed,
|
174
|
+
})
|
175
|
+
t.failure.each do |msg|
|
176
|
+
failure = test.add_element('failure', {
|
177
|
+
'type' => '',
|
178
|
+
})
|
179
|
+
failure.text = REXML::CData.new(msg.all)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
doc.write(REXML::Output.new(File.new(output_path, 'w+'), REXML::Encoding::UTF_8))
|
185
|
+
end
|
186
|
+
end # class GlobalResult
|
187
|
+
|
188
|
+
end # module Test
|
189
|
+
end # module DTest
|
data/lib/dtest/runner.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'dtest/core'
|
4
|
+
require 'dtest/progress'
|
5
|
+
|
6
|
+
module DTest
|
7
|
+
class Runner
|
8
|
+
def self.run(files)
|
9
|
+
files.each do |file|
|
10
|
+
load file
|
11
|
+
end
|
12
|
+
|
13
|
+
test = Test::Manager::instance.cases
|
14
|
+
Global::Manager::instance.harness.start(test)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.report(gresult)
|
18
|
+
Progress.print_result(gresult)
|
19
|
+
end
|
20
|
+
|
21
|
+
end # class Runner
|
22
|
+
end # module DTest
|
data/lib/dtest/test.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
|
2
|
+
require 'dtest/progress'
|
3
|
+
|
4
|
+
module DTest
|
5
|
+
module Test
|
6
|
+
class Case
|
7
|
+
include Hook
|
8
|
+
|
9
|
+
attr_accessor :name
|
10
|
+
attr_accessor :beforeCase, :afterCase
|
11
|
+
attr_accessor :before, :after
|
12
|
+
attr_accessor :test
|
13
|
+
|
14
|
+
def initialize(name, beforeCase, afterCase, before, after, test)
|
15
|
+
@name = name
|
16
|
+
@beforeCase = beforeCase
|
17
|
+
@afterCase = afterCase
|
18
|
+
@before = before
|
19
|
+
@after = after
|
20
|
+
@test = test
|
21
|
+
@defined_values = Object.new
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def execute_after_case(list, context)
|
26
|
+
begin
|
27
|
+
exec(list, context)
|
28
|
+
rescue AbortTest, AbortTestCase
|
29
|
+
# にぎりつぶす
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# execute before/after
|
34
|
+
def execute_after(list, context)
|
35
|
+
begin
|
36
|
+
exec(list, context)
|
37
|
+
rescue AbortTest
|
38
|
+
# にぎりつぶす
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
public
|
43
|
+
def execute(global_result)
|
44
|
+
# TestCase result
|
45
|
+
caseresult = CaseResult.new(@name)
|
46
|
+
global_result.add(caseresult)
|
47
|
+
|
48
|
+
# set result
|
49
|
+
@beforeCase.each {|b| b.result = caseresult.before_failure }
|
50
|
+
@afterCase.each {|b| b.result = caseresult.after_failure }
|
51
|
+
|
52
|
+
Progress.setUpTestCase(name, @test.size)
|
53
|
+
executed = 0
|
54
|
+
passed = 0
|
55
|
+
context = Context.new(@defined_values)
|
56
|
+
|
57
|
+
begin
|
58
|
+
caseresult.timer {
|
59
|
+
# execute beforeCase
|
60
|
+
exec(@beforeCase, context)
|
61
|
+
|
62
|
+
# execute each test
|
63
|
+
@test.each do |test|
|
64
|
+
executed += 1
|
65
|
+
result = Result.new(test.name)
|
66
|
+
caseresult.add(result)
|
67
|
+
execute_test(result, test)
|
68
|
+
passed += 1 if result.result == Result::PASS
|
69
|
+
end
|
70
|
+
} # Stopwatch::timer
|
71
|
+
rescue AbortTestCase
|
72
|
+
# にぎりつぶす
|
73
|
+
ensure
|
74
|
+
# report
|
75
|
+
caseresult.passed = passed
|
76
|
+
caseresult.failed = executed - passed
|
77
|
+
caseresult.executed = executed
|
78
|
+
caseresult.untested = @test.size - executed
|
79
|
+
|
80
|
+
# execute afterCase
|
81
|
+
begin
|
82
|
+
execute_after_case(@afterCase, context)
|
83
|
+
ensure
|
84
|
+
# report testcase finished
|
85
|
+
Progress.tearDownTestCase(name, executed, caseresult.elapsed)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def execute_test(result, test)
|
91
|
+
Progress.test(@name, test.name)
|
92
|
+
|
93
|
+
context = Context.new(@defined_values)
|
94
|
+
|
95
|
+
# set result
|
96
|
+
@before.each {|b| b.result = result.before_failure }
|
97
|
+
@after.each {|b| b.result = result.after_failure }
|
98
|
+
|
99
|
+
begin
|
100
|
+
# execute before blocks
|
101
|
+
exec(@before, context)
|
102
|
+
|
103
|
+
# exeucte test
|
104
|
+
result.timer {
|
105
|
+
test.result = result
|
106
|
+
test.call(context, name)
|
107
|
+
}
|
108
|
+
rescue AbortTest
|
109
|
+
# にぎりつぶす
|
110
|
+
# 次のテストを実行する
|
111
|
+
rescue AbortTestCase, AbortGlobal => e
|
112
|
+
# スルー
|
113
|
+
raise e
|
114
|
+
rescue StandardError, Exception => e
|
115
|
+
# にぎりつぶす
|
116
|
+
ensure
|
117
|
+
begin
|
118
|
+
execute_after(@after, context)
|
119
|
+
ensure
|
120
|
+
if result.failure.empty? && result.ba_empty?
|
121
|
+
result.result = Result::PASS
|
122
|
+
Progress.test_success(@name, test.name)
|
123
|
+
else
|
124
|
+
Progress.test_fail(@name, test.name)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end # class Case
|
130
|
+
|
131
|
+
class Manager
|
132
|
+
include Singleton
|
133
|
+
include Hook
|
134
|
+
|
135
|
+
attr_accessor :cases
|
136
|
+
|
137
|
+
def initialize
|
138
|
+
clear
|
139
|
+
end
|
140
|
+
|
141
|
+
def clear
|
142
|
+
remove_instance_var
|
143
|
+
flush
|
144
|
+
@cases = []
|
145
|
+
end
|
146
|
+
|
147
|
+
def flush
|
148
|
+
@beforeCase = []
|
149
|
+
@afterCase = []
|
150
|
+
@before = []
|
151
|
+
@after = []
|
152
|
+
@test = []
|
153
|
+
end
|
154
|
+
|
155
|
+
def beforeCase(option = {}, &block)
|
156
|
+
@beforeCase << Block.new("beforeCase", option, &block)
|
157
|
+
end
|
158
|
+
|
159
|
+
def afterCase(option = {}, &block)
|
160
|
+
@afterCase << Block.new("afterCase", option, &block)
|
161
|
+
end
|
162
|
+
|
163
|
+
# before test
|
164
|
+
def before(option = {}, &block)
|
165
|
+
@before << Block.new("before", option, &block)
|
166
|
+
end
|
167
|
+
|
168
|
+
# after test
|
169
|
+
def after(option = {}, &block)
|
170
|
+
@after << Block.new("after", option, &block)
|
171
|
+
end
|
172
|
+
|
173
|
+
# return product or args for value-parameterized test
|
174
|
+
def combine(*args)
|
175
|
+
if args.all? {|x| x.is_a? Array}
|
176
|
+
para = args.shift
|
177
|
+
args.each do |x|
|
178
|
+
para = para.product(x)
|
179
|
+
end
|
180
|
+
para.map {|x| x.flatten(1)}
|
181
|
+
else
|
182
|
+
raise ArgumentError, 'All arguments must be Array'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def test(name, option = {}, &block)
|
187
|
+
if option && option[:params]
|
188
|
+
# value-parameterized test
|
189
|
+
params = option[:params]
|
190
|
+
count = 0
|
191
|
+
params.each do |param|
|
192
|
+
test = Block.new("#{name}/#{count}", option, &block)
|
193
|
+
test.parameter = param
|
194
|
+
@test << test
|
195
|
+
count += 1
|
196
|
+
end
|
197
|
+
else
|
198
|
+
# normal test
|
199
|
+
@test << Block.new(name, option, &block)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def add(name)
|
204
|
+
(@beforeCase + @afterCase + @before + @after + @test).each do |block|
|
205
|
+
block.parent = name
|
206
|
+
end
|
207
|
+
@cases << Case.new(name, @beforeCase, @afterCase, @before, @after, @test)
|
208
|
+
flush
|
209
|
+
end
|
210
|
+
end # class Manager
|
211
|
+
end # module Test
|
212
|
+
end # module DTest
|