BACKGROUND: In SUnit, resources are handled via the run, run: pattern. The method without
parameter (run) is called to launch the running of one or more tests. A test result object is
created and passed as parameter to calls of run: for test suites and test cases within the top
object on which run was called.
TestResources are integrated into this pattern by being called in run, but not in run:. The top
object checks that all resources are availabble. If they are, it then creates a test result for the
run and calls run: on itself. If any fail, it aborts with an error, as in
TestSuite>>run
| result |
result := self resultClass new.
self resources do: [ :res |
res isAvailable ifFalse: [^res signalInitializationError]].
[self run: result] sunitEnsure: [self resources do: [:each | each reset]].
^result
PROBLEM: TestCase run / run: does not follow the pattern in basic SUnit 3.1. We get
TestCase>>run
| result |
result := self resultClass new.
self run: result.
^result
instead of
TestCase>>run
"Make test cases check their resources as well as test suites (fixes long-standing SUnit
issue)."
| result | result := self resultClass new. self resources do: \[ :res | res isAvailable ifFalse: \[^res signalInitializationError\]\]. \[self run: result\] sunitEnsure: \[self resources do: \[:each | each reset\]\]. ^result
This problem is masked in basic SUnit by the fact that the TestRunner cannot run individual test
cases. However it appears in subclasses that can (e.g. TestSuiteRunner in Cincom Open
Repository). It also appears in programmatic construction and running of test cases. Further, the
intent to have test suites and test cases preent the same running protocol seems evident from the
beginning of SUnit; resources should not break this pattern.
[ASIDE (enhancement request): the above code fixes the problem. However I note one could
evolve the framework to avoid code duplication by having identical briefer implementations of
TestCase>>run, TestSuite>>run
^self resultClass new run: self
with
TestResult>>run: aTestCaseOrSuite
aTestCaseOrSuite resources do:
[ :res | res isAvailable ifFalse: [^res signalInitializationError]].
[aTestCaseOrSuite run: self] sunitEnsure:
[aTestCaseOrSuite resources do: [:each | each reset]].
^self
Starting from this point, one could evolve the code to have non-available resources not raise errors
but instead populate the TestResult with tests whose resources failed as it got to the point where
it would otherwise run those tests. The effect would be to make
MyTestCase class>>resources
^Array with: MyTestResource
be just a more performant way of doing
MyTestCase>>setUp
self assert: MyTestResource isAvailable.
MyTestCase>>tearDown
MyTestResource>>tearDown
in each test that needed a given resource. Users may find this easier to understand.]
Lastly, a minor query: subtle patterns (e.g. to handle competing resources) might change the
resource list during the run. Would
TestSuite>>run
"Get resources once at starting. Specialized patterns may sort resources expensively and
just conceivably may change the list during a run. This ensures the ones we setUp are the ones
we tearDown whatever happens."
| result resourcesForRun | result := self resultClass new. resourcesForRun := self resources. resourcesForRun do: \[ :res | res isAvailable ifFalse: \[^res signalInitializationError\]\]. \[self run: result\] sunitEnsure: \[resourcesForRun do: \[:each | each reset\]\]. ^result
be better?
Logged In: YES
user_id=753646
1) Loss of indenting has made the code examples somewhat harder to read. If you see 'pre' tags below, my
second attempt to preserve indenting also failed :-).
2) In the above, the code
MyTestCase>>tearDown
MyTestResource>>tearDown
should of course have been
<pre>
MyTestCase>>tearDown
MyTestResource reset.
</pre>