Lisp中破坏性函数单元测试的常量修改问题及解决建议
处理Lisp破坏性函数单元测试中常量列表修改的方案
在将非破坏性函数改造为破坏性函数时,单元测试里因Lisp将源码中的引用列表视为常量,修改这类列表会导致测试用例间互相干扰。以下是几种比直接嵌套list更优的处理方案:
1. 用copy-tree生成独立的可变结构
copy-tree会递归复制整个列表结构,生成完全独立的可变对象,既保留了字面量写法的可读性,又彻底避免了常量共享问题:
(define-test suite-1 (let ((start (copy-tree '((1 2) (3 4) (5 6)))) (end '((7 1 2) (3 4) (5 6)))) (is #'equal end (push 7 (car start)))) (let ((start (copy-tree '((1 2) (3 4) (5 6)))) (end '((8 1 2) (3 4) (5 6)))) (is #'equal end (push 8 (car start)))))
每次测试都会基于字面量生成全新的可修改列表,不会出现前一次测试修改常量影响后一次的情况。
2. 封装测试数据生成函数
如果测试用的结构重复出现,写一个专用函数来生成数据,既能提升可读性,又能保证每次调用都返回新的可变列表:
(defun make-test-start-data () (list (list 1 2) (list 3 4) (list 5 6))) (define-test suite-1 (let ((start (make-test-start-data)) (end '((7 1 2) (3 4) (5 6)))) (is #'equal end (push 7 (car start)))) (let ((start (make-test-start-data)) (end '((8 1 2) (3 4) (5 6)))) (is #'equal end (push 8 (car start)))))
函数名make-test-start-data清晰表达了数据用途,比嵌套的list写法更易读,同时隔离了数据生成逻辑,方便后续修改测试结构。
3. 利用Parachute的测试夹具初始化干净数据
Parachute支持测试夹具,可以在每个子测试运行前自动初始化未被修改的测试数据,让测试代码更整洁:
(define-test suite-1 :before (lambda () (setf *test-start-data* (list (list 1 2) (list 3 4) (list 5 6)))) :tests ((test-push-7 (let ((end '((7 1 2) (3 4) (5 6)))) (is #'equal end (push 7 (car *test-start-data*))))) (test-push-8 (let ((end '((8 1 2) (3 4) (5 6)))) (is #'equal end (push 8 (car *test-start-data*)))))))
通过:before钩子,每个子测试都会拿到全新的测试数据,无需在每个let里重复写数据生成代码,适合测试用例较多的场景。
这些方案的核心思路都是确保每个测试用例使用独立的、可修改的列表结构,彻底避免共享常量带来的意外修改问题。
内容的提问来源于stack exchange,提问作者owagh
相关产品推荐
相关产品推荐

