2 "This defines a basic testing framework in the absence of proper condition-
3 handling and other features needed for a full unit-test suite.
6 - print assertion failure descriptions
9 prototypes ensureNamespace: #testing &delegate: True.
10 "The category for testing-related functionality."
12 testing ensureNamespace: #UnitTests.
13 "Where the actual test data is stored."
15 testing ensureNamespace: #RegressionTests.
16 "Where regression tests are stored."
18 testing ensureNamespace: #BenchmarkTests.
19 "Where timed benchmark tests are stored."
21 testing@(testing) runAllTests
22 "Run all tests in any namespace down from here. (With a strange recursion)"
29 ifTrue: [runnerBlock applyWith: value]
32 (value is: TestCase) ifTrue:
34 Console writer print: ('Running test \'' ; value printName ; '\'') paddingUntil: 40.
36 "Console writer ensureColumnAtLeast: 20."
37 "Console writer print: ('Running test \'' ; 'xxx' ; '\'') paddingUntil: 30."
38 Console writer ; (value runSuite printString).
39 Console writer newLine.
44 runnerBlock applyWith: UnitTests.
45 runnerBlock applyWith: RegressionTests.
48 _@(testing) runAllBenchmarks
50 runnerBlock := [| :ns |
51 ns slotValuesDo: [| :value |
53 ifTrue: [runnerBlock applyWith: value]
54 ifFalse: [(value is: TestCase) ifTrue: [value benchmark].
58 runnerBlock applyWith: BenchmarkTests.
61 testing define: #TestFailure &parents: {Warning}.
62 "TestFailure traits define: #Foo &parents: {Restart}."
64 testing define: #TestCase &parents: {Cloneable} &slots:
67 "A TestCase is a Command representing the future running of a test case.
68 Create one with the method #selector: aSymbol, passing the name of the method
69 to be run when the test case runs.
70 When you discover a new fixture, derive from TestCase, adding slots for the
71 objects in the fixture, override #setUp to initialize the variables, and
72 possibly override #tearDown to deallocate any external resources allocated
74 When you are writing a test case method, send #assert: aBoolean when you want
75 to check for an expected value. For example, you might say
76 `case assert: socket isOpen' to test whether or not a socket is open at a
79 t@(TestCase traits) benchmark
81 Console writer print: ('Running benchmark \'' ; t printName ; '\'') paddingUntil: 40.
82 Console writer `>> [newLine. flush.].
83 t suite tests do: [| :tc |
85 Console writer print: ((tc selector) as: String) paddingUntil: 38.
88 Console writer ; (([tc run] timeToRun) printString &precision: 2) ; ' seconds.'.
89 Console writer newLine.
93 tc1@(TestCase traits) <= tc2@(TestCase traits)
94 "Enable sorting of test cases based on their selector names."
96 ((tc2 selector as: String) lexicographicallyCompare: (tc1 selector as: String)) >= 0
99 tc@(TestCase traits) copy
101 resend `>> [selector := tc selector copy. ]
104 tc@(TestCase traits) newForSelector: selector
106 tc copy `>> [selector := selector. ]
109 tc@(TestCase traits) suiteForSelectors: selectors
111 suite := TestSuite new.
113 addAll: (selectors collect: [| :sel |
114 tc newForSelector: sel]).
118 tc@(TestCase traits) suite
123 tc@(TestCase traits) runSuite
129 tc@(TestCase traits) assert: _@True
132 tc@(TestCase traits) assert: _@False
134 tc signalFailureDescription: 'Assertion failed.'
137 tc@(TestCase traits) assert: bool description: descr
139 bool ifFalse: [tc signalFailureDescription: descr]
142 tc@(TestCase traits) deny: bool description: descr
144 tc assert: bool not description: descr
147 tc@(TestCase traits) deny: bool
152 tc@(TestCase traits) should: block
157 tc@(TestCase traits) executionOf: block raises: cond
158 "Answers whether executing the block raises the given condition."
160 [block on: cond do: [| :c | ^ True]]
162 do: [| :c | ^ False].
166 tc@(TestCase traits) should: block raise: cond
168 tc assert: (tc executionOf: block raises: cond)
171 tc@(TestCase traits) should: block raise: cond description: descr
173 tc assert: (tc executionOf: block raises: cond) description: descr
176 tc@(TestCase traits) should: block description: descr
178 tc assert: block do description: descr
181 tc@(TestCase traits) shouldnt: block
186 tc@(TestCase traits) shouldnt: block raise: cond
188 tc deny: (tc executionOf: block raises: cond)
191 tc@(TestCase traits) shouldnt: block description: descr
193 tc deny: block do description: descr
196 tc@(TestCase traits) signalFailureDescription: descr
198 TestFailure signalSaying: descr
201 tc@(TestCase traits) defaultResources
204 tc@(TestCase traits) resources
206 result := SortedSet new.
207 queue := ExtensibleArray new.
208 queue addAll: tc defaultResources.
209 [queue isEmpty] whileFalse:
211 next := queue removeFirst.
212 result include: next.
213 queue addAll: next resources].
217 tc@(TestCase traits) areResourcesAvailable
219 tc resources allSatisfy: #isAvailable `er
223 tc@(TestCase traits) printOn: s
225 tc selector printOn: s.
232 tc@(TestCase traits) setUp
235 tc@(TestCase traits) tearDown
238 tc@(TestCase traits) performTest
240 tc perform: tc selector
243 tc@(TestCase traits) runCase
244 "TODO: separate tearDown into an ensure: clause."
248 ensure: [tc tearDown]
251 tc@(TestCase traits) failureLog
252 "The WriteStream to send output information to."
255 testing define: #TestResource &parents: {Cloneable}
256 &slots: {#testName -> 'a Resource'.
257 #description -> 'a Resource'}.
259 tr@(TestResource traits) defaultResources
262 tr@(TestResource traits) resources
263 [tr defaultResources].
265 tr@(TestResource traits) isAvailable
266 "Whether the resource is available. Override this as necessary."
269 tr@(TestResource traits) isUnavailable
270 "Whether the resource is not available."
271 [tr isAvailable not].
273 tr@(TestResource traits) setUp
276 tr@(TestResource traits) tearDown
279 testing define: #TestResult &parents: {Cloneable} &slots:
280 {#failures -> Set new.
281 #errors -> ExtensibleArray new.
282 #passed -> ExtensibleArray new}.
283 "A TestResult collects the tallies for a group of tests. This can be
284 overridden and re-specialized as the second argument to run: in order to
285 extend the reporting facilities."
287 tr@(TestResult traits) new
290 failures := tr failures new.
291 errors := tr errors new.
292 passed := tr passed new. ]
295 tr@(TestResult traits) failureCount
298 tr@(TestResult traits) errorCount
301 tr@(TestResult traits) passedCount
304 tr@(TestResult traits) runCount
305 [tr failureCount + tr passedCount + tr errorCount].
307 tr@(TestResult traits) tests
309 (tr errors new &capacity: tr runCount) `>> [
312 addAll: tr failures. ]
315 tr@(TestResult traits) defects
317 (tr errors new &capacity: tr runCount - tr passedCount) `>> [
319 addAll: tr failures. ]
322 tr@(TestResult traits) hasFailures
324 tr failures isNotEmpty
327 tr@(TestResult traits) hasErrors
332 tr@(TestResult traits) hasPassed
334 tr hasErrors not /\ [tr hasFailures not]
337 tr@(TestResult traits) hasAsError: case
339 tr errors includes: case
342 tr@(TestResult traits) hasAsFailure: case
344 tr failures includes: case
347 tr@(TestResult traits) hasAsPassed: case
349 tr passed includes: case
352 tr@(TestResult traits) printOn: s
354 tr runCount printOn: s.
356 tr passedCount printOn: s.
358 tr failureCount printOn: s.
360 tr errorCount printOn: s.
365 tc@(TestCase traits) run: result
366 "Override this for specialized result types in order to handle reporting."
368 passed := ([[tc runCase. True]
371 tc logErrors ifTrue: [failure describeOn: (tc failureLog)].
372 result failures include: tc.
373 failure exit: False]]
374 on: Error do: [| :error |
375 result errors include: tc.
376 tc logErrors ifTrue: [error describeOn: (tc failureLog)].
378 passed ifTrue: [result passed include: tc]
381 tc@(TestCase traits) run
383 result := TestResult new.
388 testing define: #TestSuite &parents: {Cloneable} &slots:
389 {#tests -> SortedSet new.
390 #resources -> Set new.
391 #testName -> 'a TestSuite'}.
392 "A TestSuite is a composite of TestCases and/or other TestSuites. The common
393 entrance protocol is `suite run: result' and dependencies."
395 ts@(TestSuite traits) new
396 "This sets up the collections as necessary. Note that calculating the resources
397 is non-trivial but important."
398 [ts clone `setting: #{#tests. #resources}
399 to: {ts tests new. ts defaultResources}].
401 ts@(TestSuite traits) newNamed: name
403 ts new `>> [testName := name. ]
406 ts@(TestSuite traits) defaultResources
409 inject: ts resources new
410 into: [| :set :case | set addAll: case resources. set]
413 ts@(TestSuite traits) isAvailable
414 "Whether all resources are available."
416 ts resources allSatisfy: #isAvailable `er
419 ts@(TestSuite traits) run: result
423 "ts changed: each. TODO: include with dependency-support."
427 ts@(TestSuite traits) run
429 result := TestResult new.
431 do: [| :each | each isAvailable
432 ifFalse: [error: 'Resource not available: ' ; each]].
434 ensure: [ts resources do: #tearDown `er].
438 ts@(TestSuite traits) printSummaryOn: s