Object Programming in Lisp Observation #1

January 5, 2009

When developing with the object framework, I end up excreting all the side-effect-free (SEF) code from the object. Remember the reason for an object is to encapsulate state.

(defun format-results (result-totals)
  (destructuring-bind (result-totals asserts passes errors elapses) result-totals
    (list
     (format nil "~A Total Test(s)" result-totals)
     (format nil "~A Assertions" asserts)
     (format nil "~A Passes" passes)
     (format nil "~A Failures" (- asserts passes))
     (format nil "~A Errors" errors)
     (format nil "~,3F Second(s)" elapses))))

(defun result-sums (result-totals)
  (loop for (nil assert pass error elapse) in result-totals
        summing assert into asserts
        summing pass into passes
        summing error into errors
        summing elapse into elapses
        finally
        (return
         (list (length result-totals) asserts passes errors elapses))))

(defobj results-model
  (:members ((database nil)))
  (:methods ((:get-result-totals ()
                  (format-results (result-sums (retrieve-totals database))))

             (:connect-db (db)
               (setf database db))

             (:insert-result-data (result-data)
               (add-result database result-data))))

This method really it makes it easier to test the SEF functions. When using the object’s methods for interacting with state during tdd, you have to maintain a reference to the object. By excreting the functionality out of the object, you no longer need the reference. You just test for functionality.

(test-fixture
 :format-results
 (:tests
   (should-return-format-results-empty
    (assert-equal '("0 Total Test(s)" "0 Assertions" "0 Passes" "0 Failures" "0 Errors"
           "0.000 Second(s)") (format-results (list 0 0 0 0 0))))))

(test-fixture
 :result-sums
 (:tests
   (should-return-no-sums
    (assert-equal '(1 0 0 0 0) (result-sums (list (list :no-name 0 0 0 0)))))

   (should-return-sums
    (assert-equal '(2 1 1 1 0.15100001) (result-sums (list (list :t1 0 0 1 0.001) (list :t2 1 1 0 0.15)))))))

(test-fixture
 :results-model
 (:setup ((db (make-database))
          (model (make-results-model)))
   (connect-db model db))
 (:tests
   (should-transmit-result-totals
    (let ((totals '("0 Total Test(s)" "0 Assertions" "0 Passes" "0 Failures" "0 Errors"
           "0.000 Second(s)")))
      (assert-equal totals (get-result-totals model))))

   (should-insert-result-data
    (let ((totals '("1 Total Test(s)" "1 Assertions" "1 Passes" "0 Failures" "0 Errors"
           "0.000 Second(s)")))
      (insert-result-data model (list :no-name 1 1 0 0.0))
      (assert-equal totals (get-result-totals model))))))

This is very conducive to programming incrementally.

The function we want to look at is get-result-totals. This function retrieve the totals from the database. We pass this then to result-sums. It’s results are then passed to format-results.

  1. Retrieve results from database.
  2. Sum results.
  3. Format results.

Happy January!

Advertisements

Test Driven Development As an Introduction

December 19, 2008

I recommend Clarke Ching’s TDD Test Drive for an introduction for TDD.

It is pretty good.

Have Fun!


Recursive Tree Rabbit Holes

December 8, 2008
  1. WHY? When using my own home grown object system, I found out that it could not handle keyword parameters.
  2. Problem. Take a parameter list and remove keyword parameters.
  3. Solution. Parse the argument list to the functions, remove and if needed replace keyword.
  4. Example. Need to remove &key and replace with keyword and variable.
    • (a b) => (a b)
    • (a (b)) => (a (b))
    • (&key a) => (:a a)
    • (&key a b) => (:a a :b b)
    • (a &key b) => (a :b b)
    • (a (&key b)) => (a (:b b))

Read the rest of this entry »