简体   繁体   中英

Unit testing of destructive functions in lisp

The background here is that I have a non-destructive version of a function that I want to make destructive for performance reasons. However, writing unit tests gets challenging because lisp considers all quoted lists in the source code to be constants and changing those leads to undefined behaviour.

I'm using parachute as the unit testing framework

eg,

(define-test suite-1
 (let ((start '((1 2) (3 4) (5 6)))
       (end '((7 1 2) (3 4) (5 6))))
  (is #'equal end (push 7 (car start))))
 (let ((start '((1 2) (3 4) (5 6)))
       (end '((8 1 2) (3 4) (5 6))))
  (is #'equal end (push 8 (car start)))))

This kinda fails because we end up pushing 8 onto the constant list (1 2) which eventually causes (push 8 (car start)) to result in (8 7 1 2) instead of the expected (8 1 2)

This isn't a problem with testing non-destructive functions because they don't modify constants. This is also not a problem outside of unit tests because I know that the original structure will no longer be needed.

I could replace the above with this ugly thing:-

(let ((start (list (list 1 2) (list 3 4) (list 5 6))) ...

which then creates a proper non-constant list but it sure does make the code unreadable...

Any suggestions on how other people approach this?

Use COPY-TREE to make a deep copy of the quoted list structure.

(let ((start (copy-tree '((1 2) (3 4) (5 6)))))
  ...
)

Your example has several mistakes.

(define-test suite-1
 (let ((start '((1 2) (3 4) (5 6)))
       (end '((7 1 2) (3 4) (5 6))))
  (is #'equal end (push 7 (car start))))
 (let ((start '((1 2) (3 4) (5 6)))
       (end '((8 1 2) (3 4) (5 6))))
  (is #'equal end (push 8 (car start)))))

These are two independent let expressions. Meaning, the start in the first expression - no matter what destructive function you apply on it - can't affect the start in the second expression. Since inbetween, it got newly created using the same quoted list expression.

Second, (push 7 (car start)) doesn't return the entire start content, thus just it returns only the modified car of start . Therefore, you can't compare it with end but you should compare it to (car end) for equal ity. If you do that, your code should run through. I tested it by:

  (defun test ()
    (let ((start '((1 2) (3 4) (5 6)))
          (end '((7 1 2) (3 4) (5 6))))
      (assert (equal (car end) (push 7 (car start)))))
    (let ((start '((1 2) (3 4) (5 6)))
          (end '((8 1 2) (3 4) (5 6))))
      (assert (equal (car end) (push 8 (car start)))))
    (print "successfully run through."))
;; and then run:
(test)
;; 
;; "successfully run through." 
'' "successfully run through."

So the assert s were fulfilled.

SBCL warns about applying destructive function on constant data. But the constant data applies only wihin the let expression you are using. But outside of it, in a new let expression, a '((1 2) (3 4) (5 6)) would produce the same list always.

I couldn't believe when I saw your example that - if you would use (car end) to test for equal ity, that it would give in the second expression anything else than T .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM