« Test Driven Development by Example » – Chapter 23. How Suite It Is
Previously :
- Kick-off
- Chapter 18. First Steps to xUnit
- Chapter 19. Set the Table
- Chapter 20. Cleaning up After
- Chapter 21. Counting
- Chapter 22. Dealing with Failure
The next item on the todo list: Run multiple tests. As we have written four tests since the beginning of this serie, speaking about suite is going to be handy.
Here are the four tests:
TestCaseTest("testTemplateMethod").run().summary()
TestCaseTest("testResult").run().summary()
TestCaseTest("testFailedResultFormatting").run().summary()
TestCaseTest("testFailedResult").run().summary()
Notice that Kent has kept the testFailedResultFormatting
test.
Composition (with the Composite pattern) ! This is the technique Kent had chosen to design his suite feature. He wanted to be able to treat single tests and groups of tests exactly the same.
A test suite specification was given as follow :
« We would like to be able to create a TestSuite, add a few tests to it, and then get
collective results from running it »
First of all, a test (testSuite
) which brung its share of new features : the TestSuite
class with a add()
method, a run()
method.
The result became a parameter for the run()
method in the TestSuite
class and in the TestCase
class (it is a prerequisite to implement the Composite pattern).
Then, the tests were refactored.
That being done, Kent had to fix the four failing tests (which use the old no-argument run interface) and refactored the duplicated TestResult
instantiation.
class TestSuite:
def __init__(self):
self.tests = []
def add(self, test):
self.tests.append(test)
def run(self, result):
for test in tests:
test.run(result)
class TestCase:
def __init__(self, name):
self.name = name
def setUp(self):
pass
def tearDown(self):
pass
def run(self, result):
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()
class WasRun(TestCase):
def __init__(self, name):
TestCase.__init__(self, name)
def setUp(self):
self.log = "setUp "
def testMethod(self):
self.log = self.log + "testMethod "
def testBrokenMethod(self):
raise Exception
def tearDown(self):
self.log = self.log + "tearDown "
class TestResult:
def __init__(self):
self.runCount = 0
self.errorCount = 0
def testStarted(self):
self.runCount = self.runCount + 1
def testFailed(self):
self.errorCount= self.errorCount + 1
def summary(self):
return "%d run, %d failed" % (self.runCount, self.failureCount)
class TestCaseTest(TestCase):
def setUp(self):
self.result= TestResult()
def testTemplateMethod(self):
test = WasRun("testMethod")
test.run(result)
assert("setUp testMethod tearDown " == test.log)
def testResult(self):
test = WasRun("testMethod")
test.run(result)
assert("1 run, 0 failed" == result.summary())
def testFailedResult(self):
test = WasRun("testBrokenMethod")
test.run(result)
assert("1 run, 1 failed", result.summary)
def testFailedResultFormatting(self):
result.testStarted()
result.testFailed()
assert("1 run, 1 failed" == result.summary())
def testSuite(self):
suite = TestSuite()
suite.add(WasRun("testMethod"))
suite.add(WasRun("testBrokenMethod"))
suite.run(result)
assert("2 run, 1 failed" == result.summary())
suite = TestSuite()
suite.add(TestCaseTest("testTemplateMethod"))
suite.add(TestCaseTest("testResult"))
suite.add(TestCaseTest("testFailedResult"))
suite.add(TestCaseTest("testFailedResultFormatting"))
suite.add(TestCaseTest("testSuite"))
result = TestResult()
suite.run(result)
print result.summary
There come my Guile version:
(define-module (xunit-tdd test-result)
#:use-module (srfi srfi-9))
(define-record-type <test-result>
(make-test-result run fail)
test-result?
(run get-run set-run!)
(fail get-fail set-fail!))
(define-public (new)
(make-test-result 0 0))
(define-public (summary a-test-result)
(format #f "~A run, ~A failed" (get-run a-test-result) (get-fail a-test-result)))
(define-public (test-started a-test-result)
(set-run! a-test-result (+ 1 (get-run a-test-result))))
(define-public (test-failed a-test-result)
(set-fail! a-test-result (+ 1 (get-fail a-test-result))))
(define-module (xunit-tdd test-case)
#:use-module (srfi srfi-9)
#:use-module ((xunit-tdd test-result) #:prefix test-result:))
(define-record-type <test-case>
(make-test-case setup-proc test-proc teardown-proc log)
test-case?
(setup-proc setup-proc)
(test-proc test-proc)
(teardown-proc teardown-proc)
(log log set-log!))
(define-public (new a-setup-proc a-test-proc a-teardown-proc a-log)
(make-test-case a-setup-proc a-test-proc a-teardown-proc a-log))
(define-public (run this-test-case this-test-result)
(test-result:test-started this-test-result)
(for-each
(lambda (proc)
(with-exception-handler
(lambda (e)
(test-result:test-failed this-test-result))
(lambda ()
(if (procedure? (proc this-test-case))
((proc this-test-case) this-test-case)))
#:unwind? #t))
(list setup-proc test-proc teardown-proc)))
(define-public (read-log test-case)
(log test-case))
(define-public (append-to-log! test-case a-log)
(set-log! test-case (string-append (log test-case) a-log)))
(define-module (xunit-tdd test-suite)
#:use-module (srfi srfi-9)
#:use-module ((xunit-tdd test-case) #:prefix test-case:))
(define-record-type <test-suite>
(make-test-suite tests)
test-suite?
(tests tests set-tests!))
(define-public (new)
(make-test-suite '()))
(define-public (add test-suite test-case)
(set-tests! test-suite (cons test-case (tests test-suite))))
(define-public (run test-suite test-result)
(for-each
(lambda (test)
(test-case:run test test-result))
(tests test-suite)))
(define-module (xunit-tdd was-run)
#:use-module ((xunit-tdd test-case) #:prefix test-case:))
(define-public (new proc)
(test-case:new test-setup proc test-teardown ""))
(define (test-setup test-case)
(test-case:append-to-log! test-case "test-setup "))
(define (test-teardown test-case)
(test-case:append-to-log! test-case "test-teardown "))
(define-public (test-procedure test-case)
(test-case:append-to-log! test-case "test-procedure "))
(define-public (test-broken-procedure test-case)
(raise-exception (make-exception)))
(define-module (tests test-case-test)
#:use-module ((rnrs) #:version (6) #:select (assert))
#:use-module ((xunit-tdd test-case) #:prefix test-case:)
#:use-module ((xunit-tdd was-run) #:prefix was-run:)
#:use-module ((xunit-tdd test-result) #:prefix test-result:)
#:use-module ((xunit-tdd test-suite) #:prefix test-suite:))
(let ([a-test-result #f])
(define (setup _)
(set! a-test-result (test-result:new)))
(define (test-case-test:new test-proc)
(test-case:new setup test-proc #f ""))
(define (test-template-method this-test-case)
(let ([a-test-case (was-run:new was-run:test-procedure)])
(test-case:run a-test-case a-test-result)
(assert (string=? "test-setup test-procedure test-teardown "
(test-case:read-log a-test-case)))))
(define (test-result this-test-case)
(let ([a-test-case (was-run:new was-run:test-procedure)])
(test-case:run a-test-case a-test-result)
(assert (string=? "1 run, 0 failed"
(test-result:summary a-test-result)))))
(define (test-failed-result this-test-case)
(let ([a-test-case (was-run:new was-run:test-broken-procedure)])
(test-case:run a-test-case a-test-result)
(assert (string=? "1 run, 1 failed"
(test-result:summary a-test-result)))))
(define (test-failed-result-formatting this-test-case)
(test-result:test-started a-test-result)
(test-result:test-failed a-test-result)
(assert (string=? "1 run, 1 failed"
(test-result:summary a-test-result))))
(define (test-suite this-test-case)
(let ([a-test-suite (test-suite:new)])
(test-suite:add a-test-suite (was-run:new was-run:test-procedure))
(test-suite:add a-test-suite (was-run:new was-run:test-broken-procedure))
(test-suite:run a-test-suite a-test-result)
(assert (string=? "2 run, 1 failed"
(test-result:summary a-test-result)))))
(let ([the-test-suite (test-suite:new)]
[the-test-result (test-result:new)])
(test-suite:add the-test-suite (test-case-test:new test-template-method))
(test-suite:add the-test-suite (test-case-test:new test-result))
(test-suite:add the-test-suite (test-case-test:new test-failed-result))
(test-suite:add the-test-suite (test-case-test:new test-failed-result-formatting))
(test-suite:add the-test-suite (test-case-test:new test-suite))
(test-suite:run the-test-suite the-test-result)
(test-result:summary the-test-result)))
So this is the final chapter of this serie. As I was discovering xUnit design philosophy I did not care about functional programming as much as I thought I would. For this reason, there could be an extra chapter (or serie) in the futur where I show you my very own version of xUnit in Guile !
Thank you very much for reading this article!
Don't hesitate to give me your opinion, suggest an idea for improvement, report an error, or ask a question ! I would be so glad to discuss about the topic covered here with you ! You can reach me here.
Don't miss out on the next ones ! Either via RSS or via e-mail !
And more importantly, share this blog and tell your friends it's the best blog in the history of Free Software! No kidding!
#gnu #guile #tdd #book #english
GPG: 036B 4D54 B7B4 D6C8 DA62 2746 700F 5E0C CBB2 E2D1