« Test Driven Development by Example » – Chapter 21. Counting

Guile Logo

Previously :

The next item on the todo list: Report collected results. So we are going to implement a reporting feature.

Kent started to write a test to have TestCase.run() return a TestResult object (which is responsible for, well you get it). Then fixing the red state with a fake implementation. Then, instead of writing another test to force him to implement a real implementation (like I would have done), he simply generalized it. It prevented Kent to write one more test (to do triangulation). But it does not prevent him to forget it and leave an hard coded feature on the go. However, he kind of mitigated the risk by focusing on one specific value of the feature, counting run tests, and wrote another test to count failed tests. I would have been more explicit when writing the first test... I also noticed Kent had not refactored the WasRun("testMethod") step as he did in a previous chapter.

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()
        method = getattr(self, self.name)
        method()
        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
    def testStarted(self):
        self.runCount = self.runCount + 1
    def summary(self):
        return "%d run, 0 failed" % self.runCount

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)
    TestCaseTest("testTemplateMethod").run()
    TestCaseTest("testResult").run()
    TestCaseTest("testFailedResult").run()

There the last test does not pass, but it's intended to stay that way until the next chapter ! And before that, lets see my own code:

(define-module (xunit-tdd test-result)
  #:use-module (srfi srfi-9))

(define-record-type <test-result>
  (make-test-result run fail)
  test-result?
  (run run set-run!)
  (fail fail set-fail!))

(define-public (new)
  (make-test-result 0 0))

(define-public (summary a-test-result)
  (format #f "~A run, 0 failed" (run a-test-result)))

(define-public (test-started a-test-result)
  (set-run! a-test-result (+ 1 (run 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)
       (if (procedure? (proc test-case))
	   ((proc test-case) test-case)))
     (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))))))

(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 ""))

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