2
0

SUnit.st 8.5 KB

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