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