SUnit.st 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. Smalltalk current createPackage: 'SUnit' properties: #{}!
  2. Object subclass: #ResultAnnouncement
  3. instanceVariableNames: 'result'
  4. package: 'SUnit'!
  5. !ResultAnnouncement methodsFor: 'accessing'!
  6. result
  7. ^result
  8. !
  9. result: aTestResult
  10. result := aTestResult
  11. ! !
  12. Object subclass: #TestCase
  13. instanceVariableNames: 'testSelector asyncTimeout context'
  14. package: 'SUnit'!
  15. !TestCase commentStamp!
  16. A TestCase is an implementation of the command pattern to run a test.
  17. `TestCase` instances are created with the class method `#selector:`,
  18. passing the symbol that names the method to be executed when the test case runs.
  19. When you discover a new fixture, subclass `TestCase` and create a `#test...` method for the first test.
  20. As that method develops and more `#test...` methods are added, you will find yourself refactoring temps
  21. into instance variables for the objects in the fixture and overriding `#setUp` to initialize these variables.
  22. As required, override `#tearDown` to nil references, release objects and deallocate.!
  23. !TestCase methodsFor: 'accessing'!
  24. context: aRunningTestContext
  25. context := aRunningTestContext
  26. !
  27. selector
  28. ^testSelector
  29. !
  30. setTestSelector: aSelector
  31. testSelector := aSelector
  32. ! !
  33. !TestCase methodsFor: 'async'!
  34. async: aBlock
  35. | c |
  36. self errorIfNotAsync: '#async'.
  37. c := context.
  38. ^ [ self isAsync ifTrue: [ c execute: aBlock ] ]
  39. !
  40. finished
  41. self errorIfNotAsync: '#finished'.
  42. asyncTimeout := nil
  43. !
  44. graceTime: millis
  45. asyncTimeout ifNotNil: [ asyncTimeout clearTimeout ].
  46. asyncTimeout := true. "to allow async:"
  47. asyncTimeout :=
  48. (self async: [ self assert: false description: 'SUnit grace time exhausted' ])
  49. valueWithTimeout: millis
  50. ! !
  51. !TestCase methodsFor: 'error handling'!
  52. errorIfNotAsync: aString
  53. self isAsync ifFalse: [
  54. self error: aString, ' used without prior #graceTime:' ]
  55. ! !
  56. !TestCase methodsFor: 'private'!
  57. signalFailure: aString
  58. TestFailure new
  59. messageText: aString;
  60. signal
  61. ! !
  62. !TestCase methodsFor: 'running'!
  63. performTest
  64. asyncTimeout := nil.
  65. self perform: self selector
  66. !
  67. runCase
  68. "Runs a test case in isolated context, leaking all errors."
  69. (TestContext testCase: self) start
  70. !
  71. setUp
  72. !
  73. tearDown
  74. ! !
  75. !TestCase methodsFor: 'testing'!
  76. assert: aBoolean
  77. self assert: aBoolean description: 'Assertion failed'
  78. !
  79. assert: aBoolean description: aString
  80. aBoolean ifFalse: [self signalFailure: aString]
  81. !
  82. assert: expected equals: actual
  83. ^ self assert: (expected = actual) description: 'Expected: ', expected asString, ' but was: ', actual asString
  84. !
  85. deny: aBoolean
  86. self assert: aBoolean not
  87. !
  88. isAsync
  89. ^asyncTimeout notNil
  90. !
  91. should: aBlock
  92. self assert: aBlock value
  93. !
  94. should: aBlock raise: anExceptionClass
  95. self assert: ([aBlock value. false]
  96. on: anExceptionClass
  97. do: [:ex | true])
  98. !
  99. shouldnt: aBlock raise: anExceptionClass
  100. self assert: ([aBlock value. true]
  101. on: anExceptionClass
  102. do: [:ex | false])
  103. ! !
  104. !TestCase class methodsFor: 'accessing'!
  105. allTestSelectors
  106. | selectors |
  107. selectors := self testSelectors.
  108. self shouldInheritSelectors ifTrue: [
  109. selectors addAll: self superclass allTestSelectors].
  110. ^selectors
  111. !
  112. buildSuite
  113. ^self allTestSelectors collect: [:each | self selector: each]
  114. !
  115. lookupHierarchyRoot
  116. ^TestCase
  117. !
  118. selector: aSelector
  119. ^self new
  120. setTestSelector: aSelector;
  121. yourself
  122. !
  123. testSelectors
  124. ^self methodDictionary keys select: [:each | each match: '^test']
  125. ! !
  126. !TestCase class methodsFor: 'testing'!
  127. isAbstract
  128. ^ self name = 'TestCase'
  129. !
  130. shouldInheritSelectors
  131. ^self ~= self lookupHierarchyRoot
  132. ! !
  133. Object subclass: #TestContext
  134. instanceVariableNames: 'testCase'
  135. package: 'SUnit'!
  136. !TestContext commentStamp!
  137. TestContext governs running a particular test case.
  138. It's main added value is `#execute:` method which runs a block
  139. as a part of test case (restores context, nilling it afterwards,
  140. cleaning/calling tearDown as appropriate for sync/async scenario).!
  141. !TestContext methodsFor: 'accessing'!
  142. testCase: aTestCase
  143. testCase := aTestCase
  144. ! !
  145. !TestContext methodsFor: 'running'!
  146. execute: aBlock
  147. | failed |
  148. testCase context: self.
  149. [
  150. failed := true.
  151. aBlock value.
  152. failed := false
  153. ]
  154. ensure: [
  155. testCase context: nil.
  156. (failed and: [ testCase isAsync ]) ifTrue: [
  157. testCase finished ].
  158. testCase isAsync ifFalse: [
  159. testCase tearDown ] ]
  160. !
  161. start
  162. self execute: [
  163. testCase setUp.
  164. testCase performTest ]
  165. ! !
  166. !TestContext class methodsFor: 'instance creation'!
  167. testCase: aTestCase
  168. ^self new
  169. testCase: aTestCase;
  170. yourself
  171. ! !
  172. TestContext subclass: #ReportingTestContext
  173. instanceVariableNames: 'finished result'
  174. package: 'SUnit'!
  175. !ReportingTestContext commentStamp!
  176. ReportingTestContext adds `TestResult` reporting
  177. to `TestContext`.
  178. Errors are caught and save into a `TestResult`,
  179. When test case is finished (which can be later for async tests),
  180. a callback block is executed; this is used by a `TestSuiteRunner`.!
  181. !ReportingTestContext methodsFor: 'accessing'!
  182. finished: aBlock
  183. finished := aBlock
  184. !
  185. result: aTestResult
  186. result := aTestResult
  187. ! !
  188. !ReportingTestContext methodsFor: 'private'!
  189. withErrorReporting: aBlock
  190. [ aBlock
  191. on: TestFailure
  192. do: [ :ex | result addFailure: testCase ]
  193. ]
  194. on: Error
  195. do: [ :ex | result addError: testCase ]
  196. ! !
  197. !ReportingTestContext methodsFor: 'running'!
  198. execute: aBlock
  199. [
  200. self withErrorReporting: [ super execute: aBlock ]
  201. ]
  202. ensure: [
  203. testCase isAsync ifFalse: [
  204. result increaseRuns. finished value ] ]
  205. ! !
  206. !ReportingTestContext class methodsFor: 'instance creation'!
  207. testCase: aTestCase result: aTestResult finished: aBlock
  208. ^(super testCase: aTestCase)
  209. result: aTestResult;
  210. finished: aBlock;
  211. yourself
  212. ! !
  213. Error subclass: #TestFailure
  214. instanceVariableNames: ''
  215. package: 'SUnit'!
  216. !TestFailure commentStamp!
  217. The test framework distinguishes between failures and errors.
  218. A failure is an event whose possibiity is explicitly anticipated and checked for in an assertion,
  219. whereas an error is an unanticipated problem like a division by 0 or an index out of bounds.
  220. TestFailure is raised when the boolean parameter of an #`assert:` or `#deny:` call is the opposite of what the assertion claims.!
  221. Object subclass: #TestResult
  222. instanceVariableNames: 'timestamp runs errors failures total'
  223. package: 'SUnit'!
  224. !TestResult commentStamp!
  225. A TestResult implements the collecting parameter pattern for running a bunch of tests.
  226. A TestResult holds tests that have run, sorted into the result categories of passed, failures and errors.
  227. TestResult is an interesting object to subclass or substitute. `#runCase:` is the external protocol you need to reproduce!
  228. !TestResult methodsFor: 'accessing'!
  229. addError: anError
  230. self errors add: anError
  231. !
  232. addFailure: aFailure
  233. self failures add: aFailure
  234. !
  235. errors
  236. ^errors
  237. !
  238. failures
  239. ^failures
  240. !
  241. increaseRuns
  242. runs := runs + 1
  243. !
  244. runs
  245. ^runs
  246. !
  247. status
  248. ^self errors isEmpty
  249. ifTrue: [
  250. self failures isEmpty
  251. ifTrue: ['success']
  252. ifFalse: ['failure']]
  253. ifFalse: ['error']
  254. !
  255. timestamp
  256. ^timestamp
  257. !
  258. total
  259. ^total
  260. !
  261. total: aNumber
  262. total := aNumber
  263. ! !
  264. !TestResult methodsFor: 'initialization'!
  265. initialize
  266. super initialize.
  267. timestamp := Date now.
  268. runs := 0.
  269. errors := Array new.
  270. failures := Array new.
  271. total := 0
  272. ! !
  273. Object subclass: #TestSuiteRunner
  274. instanceVariableNames: 'suite result announcer runNextTest'
  275. package: 'SUnit'!
  276. !TestSuiteRunner methodsFor: 'accessing'!
  277. announcer
  278. ^announcer
  279. !
  280. result
  281. ^result
  282. !
  283. suite: aCollection
  284. suite := aCollection
  285. ! !
  286. !TestSuiteRunner methodsFor: 'actions'!
  287. resume
  288. runNextTest fork.
  289. announcer announce: (ResultAnnouncement new result: result)
  290. !
  291. run
  292. result total: suite size.
  293. self resume
  294. ! !
  295. !TestSuiteRunner methodsFor: 'initialization'!
  296. initialize
  297. super initialize.
  298. announcer := Announcer new.
  299. result := TestResult new.
  300. runNextTest := [ | runs | runs := result runs. runs < result total ifTrue: [ (self contextOf: runs + 1) start ]].
  301. ! !
  302. !TestSuiteRunner methodsFor: 'private'!
  303. contextOf: anInteger
  304. ^ReportingTestContext testCase: (suite at: anInteger) result: result finished: [ self resume ]
  305. ! !
  306. !TestSuiteRunner class methodsFor: 'instance creation'!
  307. new
  308. self shouldNotImplement
  309. !
  310. on: aCollection
  311. ^super new suite: aCollection
  312. ! !