Problem
I’m reading TDD By Example by Kent Beck (which I highly recommend, if you haven’t read it!) In The xUnit Example, he discusses how to collect the results of multiple tests in a single TestResult
. His code looks like this:
# in TestCase
def run(self, result):
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
# in TestSuite
def run(self, result):
for test in self.tests:
test.run(result)
In short, Beck passes in the currently running TestResult
and asks the tests to add their results to it; the TestSuite
acts in much the same way. (Beck says the TestSuite
is a Composite, and the pattern of passing in the TestResult
is called Collecting Parameter.) In other words, somewhere at the top of the call stack, something is creating a single TestResult
which gets passed all the way down to the TestCase
.
I find the single TestResult
object aesthetically distasteful – I generally don’t like sharing state between different components. It smacks of C#’s out
keyword (which is not a compliment).
My approach would be to return a TestResult
from every TestCase
, and allow TestResult
s to be added to one another:
# in TestResult
def __init__(self, runCount=0, errorCount=0):
self.runCount = runCount
self.errorCount = errorCount
def __add__(self, other):
runCount = self.runCount + other.runCount
errorCount = self.errorCount + other.errorCount
return TestResult(runCount, errorCount)
# in TestCase
def run(self):
result = TestResult()
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
return result
# in TestSuite
def run(self):
result = TestResult()
for test in self.tests:
result += test.run()
return result
How would you weigh up these two approaches? The state of any given instance of TestResult
is now local to the methods. On the other hand, there’s more duplication in mine – every method now creates and returns a new TestResult
, which might make writing a new implementation of run
harder work.
Solution
One benefit of a collecting parameter is writers can add multiple items to the list. The collecting parameter can descend deeper into the stack and each method is free to add as many items to the list as necessary.
If what was added to the list was restricted to the return value of the method, then it becomes the sender’s responsibility to know how to add the receiver’s return values to the list, and the receiver cannot return another value other than what would have been added to it.
The collecting parameter seems more flexible because any number of receivers may write to it and still be able to return values.