SUnit.st 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. Smalltalk createPackage: 'SUnit'!
  2. Object subclass: #ResultAnnouncement
  3. slots: {#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: #Teachable
  16. slots: {#learnings}
  17. package: 'SUnit'!
  18. !Teachable commentStamp!
  19. An object you can teach how to behave. Have a look at the
  20. class side for an example.
  21. For more infos have a look at: http://lists.squeakfoundation.org/pipermail/squeak-dev/2002-April/038170.html!
  22. !Teachable methodsFor: 'private'!
  23. doesNotUnderstand: aMessage
  24. | learning |
  25. learning := self learnings
  26. at: aMessage selector
  27. ifAbsent:[ ^super doesNotUnderstand: aMessage ].
  28. ^ learning class == Association
  29. ifTrue: [learning value]
  30. ifFalse: [learning valueWithPossibleArguments: aMessage arguments]
  31. !
  32. learnings
  33. learnings isNil ifTrue: [learnings := Dictionary new].
  34. ^learnings
  35. ! !
  36. !Teachable methodsFor: 'teaching'!
  37. acceptSend: aSymbol
  38. self whenSend: aSymbol return: self
  39. !
  40. whenSend: aSymbol evaluate: aBlock
  41. self learnings at: aSymbol put: aBlock
  42. !
  43. whenSend: aSymbol return: anObject
  44. self learnings at: aSymbol put: (#return -> anObject)
  45. ! !
  46. !Teachable class methodsFor: 'examples'!
  47. example
  48. | teachable |
  49. teachable := self new.
  50. teachable
  51. whenSend: #help return: 'ok';
  52. whenSend: #doit evaluate: [1 inspect];
  53. acceptSend: #noDebugger;
  54. whenSend: #negate: evaluate: [:num | num negated].
  55. teachable help.
  56. teachable doit.
  57. teachable noDebugger.
  58. teachable negate: 120
  59. ! !
  60. Object subclass: #TestCase
  61. slots: {#testSelector. #asyncTimeout. #context}
  62. package: 'SUnit'!
  63. !TestCase commentStamp!
  64. I am an implementation of the command pattern to run a test.
  65. ## API
  66. My instances are created with the class method `#selector:`,
  67. passing the symbol that names the method to be executed when the test case runs.
  68. When you discover a new fixture, subclass `TestCase` and create a `#test...` method for the first test.
  69. As that method develops and more `#test...` methods are added, you will find yourself refactoring temps
  70. into instance variables for the objects in the fixture and overriding `#setUp` to initialize these variables.
  71. As required, override `#tearDown` to nil references, release objects and deallocate.!
  72. !TestCase methodsFor: 'accessing'!
  73. context: aRunningTestContext
  74. context := aRunningTestContext
  75. !
  76. selector
  77. ^ testSelector
  78. !
  79. setTestSelector: aSelector
  80. testSelector := aSelector
  81. ! !
  82. !TestCase methodsFor: 'async'!
  83. async: aBlock
  84. | c |
  85. self errorIfNotAsync: '#async'.
  86. c := context.
  87. ^ [ self isAsync ifTrue: [ c execute: aBlock ] ]
  88. !
  89. finished
  90. self errorIfNotAsync: '#finished'.
  91. asyncTimeout := nil
  92. !
  93. timeout: aNumber
  94. "Set a grace time timeout in milliseconds to run the test asynchronously"
  95. asyncTimeout ifNotNil: [ asyncTimeout clearTimeout ].
  96. "to allow #async: message send without throwing an error"
  97. asyncTimeout := 0.
  98. asyncTimeout := (self async: [
  99. self assert: false description: 'SUnit grace time exhausted' ])
  100. valueWithTimeout: aNumber
  101. ! !
  102. !TestCase methodsFor: 'error handling'!
  103. errorIfNotAsync: aString
  104. self isAsync ifFalse: [
  105. self error: aString, ' used without prior #timeout:' ]
  106. ! !
  107. !TestCase methodsFor: 'private'!
  108. signalFailure: aString
  109. TestFailure new
  110. messageText: aString;
  111. signal
  112. ! !
  113. !TestCase methodsFor: 'running'!
  114. debugCase
  115. "Runs a test case in isolated context, debugging all errors."
  116. (DebugTestContext testCase: self) start
  117. !
  118. performTest
  119. asyncTimeout := nil.
  120. self perform: self selector
  121. !
  122. runCase
  123. "Runs a test case in isolated context, leaking all errors."
  124. (TestContext testCase: self) start
  125. !
  126. setUp
  127. !
  128. tearDown
  129. ! !
  130. !TestCase methodsFor: 'testing'!
  131. assert: aBoolean
  132. self assert: aBoolean description: 'Assertion failed'
  133. !
  134. assert: aBoolean description: aString
  135. aBoolean ifFalse: [ self signalFailure: aString ]
  136. !
  137. assert: actual equals: expected
  138. ^ self assert: (actual = expected) description: 'Expected: ', expected printString, ' but was: ', actual printString
  139. !
  140. deny: aBoolean
  141. self assert: aBoolean not
  142. !
  143. isAsync
  144. ^ asyncTimeout notNil
  145. !
  146. should: aBlock
  147. self assert: aBlock value
  148. !
  149. should: aBlock raise: anExceptionClass
  150. self assert: ([ aBlock value. false ]
  151. on: anExceptionClass
  152. do: [ :ex | true ])
  153. !
  154. shouldnt: aBlock raise: anExceptionClass
  155. self assert: ([ aBlock value. true ]
  156. on: anExceptionClass
  157. do: [ :ex | false ])
  158. ! !
  159. !TestCase class methodsFor: 'accessing'!
  160. allTestSelectors
  161. | selectors |
  162. selectors := self testSelectors.
  163. self shouldInheritSelectors ifTrue: [
  164. selectors addAll: self superclass allTestSelectors ].
  165. ^ selectors asSet
  166. !
  167. buildSuite
  168. ^ self allTestSelectors collect: [ :each | self selector: each ]
  169. !
  170. classTag
  171. "Returns a tag or general category for this class.
  172. Typically used to help tools do some reflection.
  173. Helios, for example, uses this to decide what icon the class should display."
  174. ^ 'test'
  175. !
  176. lookupHierarchyRoot
  177. ^ TestCase
  178. !
  179. selector: aSelector
  180. ^ self new
  181. setTestSelector: aSelector;
  182. yourself
  183. !
  184. testSelectors
  185. ^ self methodDictionary keys select: [ :each | each match: '^test' ]
  186. ! !
  187. !TestCase class methodsFor: 'testing'!
  188. isAbstract
  189. ^ self name = TestCase name
  190. !
  191. isTestClass
  192. ^ self isAbstract not
  193. !
  194. shouldInheritSelectors
  195. ^ self ~= self lookupHierarchyRoot
  196. ! !
  197. Object subclass: #TestContext
  198. slots: {#testCase}
  199. package: 'SUnit'!
  200. !TestContext commentStamp!
  201. I govern running a particular test case.
  202. 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).!
  203. !TestContext methodsFor: 'accessing'!
  204. testCase: aTestCase
  205. testCase := aTestCase
  206. ! !
  207. !TestContext methodsFor: 'running'!
  208. execute: aBlock
  209. | failed |
  210. testCase context: self.
  211. [
  212. failed := true.
  213. aBlock value.
  214. failed := false
  215. ]
  216. ensure: [
  217. testCase context: nil.
  218. (failed and: [ testCase isAsync ]) ifTrue: [
  219. testCase finished ].
  220. testCase isAsync ifFalse: [
  221. testCase tearDown ] ]
  222. !
  223. start
  224. self execute: [
  225. testCase setUp.
  226. testCase performTest ]
  227. ! !
  228. !TestContext class methodsFor: 'instance creation'!
  229. testCase: aTestCase
  230. ^ self new
  231. testCase: aTestCase;
  232. yourself
  233. ! !
  234. TestContext subclass: #DebugTestContext
  235. slots: {#finished. #result}
  236. package: 'SUnit'!
  237. !DebugTestContext commentStamp!
  238. I add error debugging to `TestContext`.
  239. Errors are caught and explicitly passed to `ErrorHandler`.
  240. I am used in `TestCase >> debugCase`.!
  241. !DebugTestContext methodsFor: 'private'!
  242. withErrorDebugging: aBlock
  243. aBlock
  244. on: Error
  245. do: [ :ex | ErrorHandler handleError: ex ]
  246. ! !
  247. !DebugTestContext methodsFor: 'running'!
  248. execute: aBlock
  249. self withErrorDebugging: [ super execute: aBlock ]
  250. ! !
  251. !DebugTestContext class methodsFor: 'instance creation'!
  252. testCase: aTestCase result: aTestResult finished: aBlock
  253. ^ (super testCase: aTestCase)
  254. result: aTestResult;
  255. finished: aBlock;
  256. yourself
  257. ! !
  258. TestContext subclass: #ReportingTestContext
  259. slots: {#finished. #result}
  260. package: 'SUnit'!
  261. !ReportingTestContext commentStamp!
  262. I add `TestResult` reporting to `TestContext`.
  263. Errors are caught and save into a `TestResult`,
  264. When test case is finished (which can be later for async tests), a callback block is executed; this is used by a `TestSuiteRunner`.!
  265. !ReportingTestContext methodsFor: 'accessing'!
  266. finished: aBlock
  267. finished := aBlock
  268. !
  269. result: aTestResult
  270. result := aTestResult
  271. ! !
  272. !ReportingTestContext methodsFor: 'private'!
  273. withErrorReporting: aBlock
  274. [ aBlock
  275. on: TestFailure
  276. do: [ :ex | result addFailure: testCase ]
  277. ]
  278. on: Error
  279. do: [ :ex | result addError: testCase ]
  280. ! !
  281. !ReportingTestContext methodsFor: 'running'!
  282. execute: aBlock
  283. [
  284. self withErrorReporting: [ super execute: aBlock ]
  285. ]
  286. ensure: [
  287. testCase isAsync ifFalse: [
  288. result increaseRuns. finished value ] ]
  289. ! !
  290. !ReportingTestContext class methodsFor: 'instance creation'!
  291. testCase: aTestCase result: aTestResult finished: aBlock
  292. ^ (super testCase: aTestCase)
  293. result: aTestResult;
  294. finished: aBlock;
  295. yourself
  296. ! !
  297. Error subclass: #TestFailure
  298. slots: {}
  299. package: 'SUnit'!
  300. !TestFailure commentStamp!
  301. I am raised when the boolean parameter of an #`assert:` or `#deny:` call is the opposite of what the assertion claims.
  302. The test framework distinguishes between failures and errors.
  303. A failure is an event whose possibiity is explicitly anticipated and checked for in an assertion,
  304. whereas an error is an unanticipated problem like a division by 0 or an index out of bounds.!
  305. Object subclass: #TestResult
  306. slots: {#timestamp. #runs. #errors. #failures. #total}
  307. package: 'SUnit'!
  308. !TestResult commentStamp!
  309. I implement the collecting parameter pattern for running a bunch of tests.
  310. My instances hold tests that have run, sorted into the result categories of passed, failures and errors.
  311. `TestResult` is an interesting object to subclass or substitute. `#runCase:` is the external protocol you need to reproduce!
  312. !TestResult methodsFor: 'accessing'!
  313. addError: anError
  314. self errors add: anError
  315. !
  316. addFailure: aFailure
  317. self failures add: aFailure
  318. !
  319. errors
  320. ^ errors
  321. !
  322. failures
  323. ^ failures
  324. !
  325. increaseRuns
  326. runs := runs + 1
  327. !
  328. runs
  329. ^ runs
  330. !
  331. status
  332. ^ self errors ifNotEmpty: [ 'error' ] ifEmpty: [
  333. self failures ifNotEmpty: [ 'failure' ] ifEmpty: [
  334. 'success' ]]
  335. !
  336. timestamp
  337. ^ timestamp
  338. !
  339. total
  340. ^ total
  341. !
  342. total: aNumber
  343. total := aNumber
  344. ! !
  345. !TestResult methodsFor: 'initialization'!
  346. initialize
  347. super initialize.
  348. timestamp := Date now.
  349. runs := 0.
  350. errors := Array new.
  351. failures := Array new.
  352. total := 0
  353. ! !
  354. !TestResult methodsFor: 'running'!
  355. nextRunDo: aBlock
  356. "Runs aBlock with index of next run or does nothing if no more runs"
  357. ^ self runs == self total
  358. ifFalse: [ aBlock value: self runs + 1 ]
  359. !
  360. runCase: aTestCase
  361. [ [ self increaseRuns.
  362. aTestCase runCase ]
  363. on: TestFailure do: [ :ex | self addFailure: aTestCase ]]
  364. on: Error do: [ :ex | self addError: aTestCase ]
  365. ! !
  366. Object subclass: #TestSuiteRunner
  367. slots: {#suite. #result. #announcer. #runNextTest}
  368. package: 'SUnit'!
  369. !TestSuiteRunner commentStamp!
  370. I am responsible for running a collection (`suite`) of tests.
  371. ## API
  372. Instances should be created using the class-side `#on:` method, taking a collection of tests to run as parameter.
  373. To run the test suite, use `#run`.!
  374. !TestSuiteRunner methodsFor: 'accessing'!
  375. announcer
  376. ^ announcer
  377. !
  378. result
  379. ^ result
  380. !
  381. suite: aCollection
  382. suite := aCollection
  383. ! !
  384. !TestSuiteRunner methodsFor: 'actions'!
  385. resume
  386. runNextTest fork.
  387. announcer announce: (ResultAnnouncement new result: result)
  388. !
  389. run
  390. result total: suite size.
  391. self resume
  392. ! !
  393. !TestSuiteRunner methodsFor: 'initialization'!
  394. initialize
  395. super initialize.
  396. announcer := Announcer new.
  397. result := TestResult new.
  398. runNextTest := [ | runs | runs := result runs. runs < result total ifTrue: [ (self contextOf: runs + 1) start ] ].
  399. ! !
  400. !TestSuiteRunner methodsFor: 'private'!
  401. contextOf: anInteger
  402. ^ ReportingTestContext testCase: (suite at: anInteger) result: result finished: [ self resume ]
  403. ! !
  404. !TestSuiteRunner class methodsFor: 'instance creation'!
  405. new
  406. self shouldNotImplement
  407. !
  408. on: aCollection
  409. ^ super new suite: aCollection
  410. ! !
  411. !Package methodsFor: '*SUnit'!
  412. isTestPackage
  413. ^ self classes anySatisfy: [ :each | each isTestClass ]
  414. ! !
  415. !TBehaviorDefaults methodsFor: '*SUnit'!
  416. isTestClass
  417. ^ false
  418. ! !