« Test Driven Development by Example » – Chapter 22. Dealing with Failure
Previously :
- Kick-off
- Chapter 18. First Steps to xUnit
- Chapter 19. Set the Table
- Chapter 20. Cleaning up After
- Chapter 21. Counting
The next item on the todo list: Report failed tests. The last chapter let the state of the test suite in a shaky state. Indeed, the last one is still red!
According to Kent Beck, the following chapter is like going one step deeper to deal with a more specific – smaller grained – as he said. To ensure our learning base (the tests) is rock solid!
Kent properly started with a new test testFailedResultFormatting
. Which was intended to force him to set in place the mecanics required to make our last, still failing, test testFailedResult
. So the point here is to see the logic producing the right message when we call the right methods in the right order.
Kent add an error counter and the testFailed
method to the TestResult
class. Kent admitted the counting part would have been worth a test for itself.
Then a try-catch
block to trigger the testFailed
method.
Another interesting test, added to the todo list, would be to try to spot errors during the setup.
class TestCase:
def __init__(self, name):
self.name = name
def setUp(self):
pass
def tearDown(self):
pass
def run(self):
result = TestResult()
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()
return result
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 testTemplateMethod(self):
test = WasRun("testMethod")
test.run()
assert("setUp testMethod tearDown " == test.log)
def testResult(self):
test = WasRun("testMethod")
result = test.run()
assert("1 run, 0 failed" == result.summary())
def testFailedResult(self):
test = WasRun("testBrokenMethod")
result = test.run()
assert("1 run, 1 failed", result.summary)
def testFailedResultFormatting(self):
result = TestResult()
result.testStarted()
result.testFailed()
assert("1 run, 1 failed" == result.summary())
TestCaseTest("testTemplateMethod").run()
TestCaseTest("testResult").run()
TestCaseTest("testFailedResult").run()
TestCaseTest("testFailedResultFormatting").run()
On my side, I started with the new test. Which forced me to implement the test-failed
procedure in order to compile. Just after fixing the compilation issue, I make it to pass. Baby steps are so convenient. I really like it. So with the test-failed-result-formatting
to green, I had me the confidence that my notifiers were OK.
All that was left to do was to call the test-failed
procedure when the test actuallu run.
With the refactoring I did in the previous chapter, I feel my code is able to catch exceptions from setup as well but! I would count multiple errors if setup and the procedure under test both fail.
(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 test-case)
(let ([result (test-result:new)])
(test-result:test-started result)
(for-each
(lambda (proc)
(with-exception-handler
(lambda (e)
(test-result:test-failed result))
(lambda ()
(if (procedure? (proc test-case))
((proc test-case) test-case)))
#:unwind? #t))
(list setup-proc test-proc teardown-proc))
result))
(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 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:))
(define (test-template-method this-test-case)
(let ([a-test-case (was-run:new was-run:test-procedure)])
(test-case:run a-test-case)
(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)])
(assert (string=? "1 run, 0 failed"
(test-result:summary (test-case:run a-test-case))))))
(define (test-failed-result this-test-case)
(let ([a-test-case (was-run:new was-run:test-broken-procedure)])
(assert (string=? "1 run, 1 failed"
(test-result:summary (test-case:run a-test-case))))))
(define (test-failed-result-formatting this-test-case)
(let ([a-test-result (test-result:new)])
(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)))))
(test-case:run (test-case:new #f test-template-method #f ""))
(test-case:run (test-case:new #f test-result #f ""))
(test-case:run (test-case:new #f test-failed-result #f ""))
(test-case:run (test-case:new #f test-failed-result-formatting #f ""))
I'm not sure but I think we don't need testFailedResultFormatting
or test-failed-result-formatting
anymore.
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