Crafting happiness with Free Software & Hardware

« Test Driven Development by Example » – Chapter 19. Set the Table

Guile Logo

Previously :

The next item on the todo list : Invoke setUp first

So here we will implement the test fixture usually called setup or beforeEach in other testing frameworks. It aims to execute code before any test function.

Let's see how it's done !

Kent adds a new test to his suite :

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

So do I (with a bit of tidying since the previous article) :

(let ([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))))]
      
      [test-setup
       (lambda (test-case)
	 (let ([a-test-case (test-case:init test-case:test-procedure)])
	   (test-case:run a-test-case)
	   (assert (test-case:was-setup a-test-case))))])
  
  (test-case:run (test-case:init test-running))
  (test-case:run (test-case:init test-setup)))

In order to be able to run the tests. We have to define the new function/procedure was-setup. Then it has to be called somewhere.

Kent defined it in the WasRun class :

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

And he calls it in the TestCase class :

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

Then he said :

« That's two steps to get a test case running, which is too many in such ticklish circumstances. We'll see if it will work. Yes, it does pass. However, if you want to learn something, try to figure out how we could have gotten the test to pass by changing no more than one method at a time. »

Alright, let's do it and learn ! Only one procedure has to change… In order to fix the compilation error (remember was-setup does not exists yet?), I have to define was-setup which has one parameter and returns #t.

(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)
  test-case?
  (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 was-setup
  (lambda (test-case)
    #t))

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

Et voilà ! One single procedure ! The test does pass, so I guess I earned a star on the wall ! Now I can refactor. To fit to Kent's version.

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

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

(define-public new
  (case-lambda
    ((a-test-proc)
     (make-test-case #f #f (const #f) a-test-proc))
    ((a-setup-proc a-test-proc)
     (make-test-case #f #f a-setup-proc a-test-proc))))

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

(define setup
  (lambda (test-case)
    ((setup-proc test-case))
    (set-was-run! test-case #f)
    (set-was-setup! test-case #t)))

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

Then Kent leverages the new facility to shorten the tests. His test code now looks like this :

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

Still following the same idea, here is my version :

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

(let ([a-test-case #f])
  
  (define setup
    (lambda ()
      (set! a-test-case (test-case:new test-case:test-procedure))))

  (define test-case-test-init
    (lambda (test-procedure)
      (test-case:new setup test-procedure)))

  (define test-running
    (lambda (this-test-case)
      (test-case:run a-test-case)
      (assert (test-case:was-run? a-test-case))))

  (define test-setup
    (lambda (this-test-case)
      (test-case:run a-test-case)
      (assert (test-case:was-setup? a-test-case))))
  
  (test-case:run (test-case-test-init test-running))
  (test-case:run (test-case-test-init test-setup)))

Converting the OO vision to something which is not has been kind of challenging to me. You can't see that here but I did think about so many complicated things before I finally got to the point I finally used a closure haha !

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