Crafting happiness with Free Software & Hardware

« Test Driven Development by Example » – Chapter 18. First Steps to xUnit

Guile Logo

Previously :

This first step, to an xUnit implementation, was pretty fun. You can taste it with the introductory sentence :

« Driving a testing tool using the testing tool itself to run the tests may seem a bit like performing brain surgery on yourself. »

Let's see how far can I push this adaptation in the Guile programming language.

So this is a bootstrapping process. Kent moves meticulously. Baby steps are the way to go ! It seems to be helpful to him as the one who write code, and it is also useful to me as the one who read and learn. I really like to understand how more experienced developers think, how they make decisions.

The chapter ends with the following test :

class TestCaseTest(TestCase):
    def testRunning(self):
        test = WasRun("testMethod")
        assert(not test.wasRun)
        test.run()
        assert(test.wasRun)
TestCaseTest("testRunning").run()

The TestCaseTest, the class which contains the driving tests, inherits from the TestCase class.

class TestCase:
    def __init__(self, name):
        self.name = name
    def run(self):
        method = getattr(self, self.name)
        method()

TestCase class has another subclass which is WasRun :

class WasRun(TestCase):
    def __init__(self, name):
        self.wasRun = None
        TestCase.__init__(self, name)
    def testMethod(self):
      self.wasRun = 1

You already can see a scaffolding of classes – the design – emerging. Thanks to inheritance, encapsulation… And of course some design principles (I refer to the SOLID principles).

In my experiment, I won't have those properties in my tool belt. At first, what I ended up writing felt to lack sophistication, as if I missed a refactoring. I only have one module containing a data structure and procedures to apply onto.

Then, I read a quote from Alan Perlis' Epigrams on Programming (1982) :

« It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures. »

It kind of helped me to balance my feelings and I was able to continue my adaptation.

Here is my test :

(define-module (tests case-test)
  #:use-module ((rnrs) #:version (6) #:select (assert))
  #:use-module ((xunit-tdd test-case) #:prefix test-case:))

(define test-running
  (lambda (test-case)
    (let ([a-test-case (test-case:init test-case:test-procedure)])
      (assert (not (test-case:was-run a-test-case)))
      (test-case:run a-test-case)
      (assert (test-case:was-run a-test-case)))))

(let ([test-case-test (test-case:init test-running)])
  (test-case:run test-case-test))

And here is the code under test :

(define-module (xunit-tdd test-case)
  #:use-module (srfi srfi-9)
  #:export (was-run))

(define-record-type <test-case>
  (make-test-case was-run proc-name)
  was-run?
  (was-run was-run set-was-run!)
  (proc-name proc-name))

(define-public init
  (lambda (proc-name)
    (make-test-case #f proc-name)))

(define-public run
  (lambda (test-case)
    ((proc-name test-case) test-case)))

(define-public test-procedure
  (lambda (test-case)
    (set-was-run! test-case #t)))

My implementation has its limit in terms of functional compliance : my <test-case> data structure is mutable to hold the was-run information. I wish I could have found something else.

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 !

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