SUnit.st 9.6 KB

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