regression-test,别名rtest、rt,基本API为def-test、rem-test、do-test、do-tests。整个框架只有一个文件,2百多行,不依赖其他工程,非常轻便。用列表保存所有的测试用例,不支持用例分组。支持增量测试,即只执行失败的用例。
fiveam,强大的测试框架,支持很多特性,输出的结果更直观。依赖alexandria工程。
【加载过程】
(require ‘asdf)
(load “alexandria.asd”)
(load “fiveam.asd”)
(asdf:load :fiveam)
【测试用例】
定义简单的测试用例,测试1+1=2。宏test用于定义测试用例,第一个参数add-1是用例名称,后面是语句列表,宏is检查输入的表达式是否为真。
(fiveam:test add-1
(fiveam:is (= (+ 1 1) 2)))
接下来运行这个用例:(fiveam:run!) 或者 (fiveam:run ‘add-1)。有三种执行结果:成功、失败、跳过。用fiveam:run!执行用例后,以检查点为单位进行统计,而不是以用例为单位。执行过程会显示进度,每执行一个检查点输出一个字符,执行成功输出小圆点,失败则输出其它字符。一个用例中可以做多个检查,例如,检查1+1=2 并且 1+2=3。
(fiveam:test add-2
(fiveam:is (= (+ 1 1) 2))
(fiveam:is (= (+ 1 2) 3)))
每次都要写fiveam:is应该很烦人,解决的办法是把宏fiveam:is导入到当前的package中,有很多种方式。执行 (import ‘(fiveam:is) ‘用例所在的包名) 即可,fiveam专门包装了一个函数(fiveam:import-testing-symbols ‘用例所在的包名)。如果定义用例时忘记了,执行时报错找不到函数is,可以用(fiveam:rem-test ‘用例名)删除用例,然后重新定义。
【测试套】
测试套,就是对用例进行分组。测试套与用例的关系,不像文件夹和文件。所有用例、测试套都不能重名,因为fiveam用一个哈希表fiveam::*test*保存所有的用例和测试套,Key为它们的名称。一个测试套也可以属于多个测试套。测试套和用例一样,都是可以执行的,在实现中两者都是testable-object的子类。下面定义几个测试套,A包含B和C。
(fiveam:def-suite a)
(fiveam:def-suite b :in a)
(fiveam:def-suite c :in a)
没有很好的方法查看测试套的包含关系,有需要的话,可以执行下面语句,显示一个测试套包含的子对象。(fiveam::tests (fiveam:get-test 'a))
接下来在测试套中定义用例,执行用例时需要指定测试套名称,会执行测试套中所有的用例,包括下级测试套。如果不指定测试套名称,则执行不了这些用例。
(fiveam:test (list-1 :suite a) (is (eql 1 1)))
(fiveam:test (list-2 :suite b) (is (eql 2 2)))
(fiveam:test (list-3 :suite b) (is (eql 3 3)))
系统有一个匿名测试套,没有名称,或者说名称为Nil。如果定义用例时不指定测试套,就会放到匿名套中。如果定义测试套时没有指定父测试套,按理也应该放到匿名套中,但fiveam的实现(文件src/suite.lisp中的函数make-suite)有点问题,没有放进去,导致执行(fiveam:run!)时不能执行指定名称的测试套中的用例。修改make-suite函数,增加下面一句即可解决问题,如果没有指定父测试套,并且已经存在匿名测试套,则将新建的测试套加到匿名测试套中。
(unless in
(when-let ((groot (get-test nil)))
(setf (gethash name (tests groot)) suite)))
每次定义用例都要指定测试套,有点麻烦。可以用宏fiveam:def-suite*来创建测试套。或者执行宏fiveam:in-suite设置默认的测试套。
【执行时所在的package】
如果在package A中定义用例,在package B中执行用例,用例中使用的A中的函数,还是B中的函数?答案是A中的函数。fiveam框架在定义用例时保存了*package*变量,而在执行用例时恢复了*package*变量。
(make-instance 'test-case
:name ',name
:runtime-package (find-package ,(package-name *package*)) 定义时保存*package*变量
...)
(let ((*readtable* (copy-readtable))
(*package* (runtime-package test))) 执行时恢复*package*变量
(funcall (test-lambda test))
...)
【依赖关系】
可以根据其他用例的执行结果,决定是否执行某个用例,称为依赖关系。可以依赖一个用例,也可以依赖多个用例,还可以用and、or、not组成复杂的条件。执行一个用例时,会自动执行所依赖的用例。例如:
(fiveam:test (list-4 :depends-on (and list-1 list-4 list-3)) (is (eql 4 4)))
用例可以依赖测试套,但测试套不能依赖用例或测试套。
【Test Fixture测试环境、测试上下文】
运行测试用例时,经常要构建一个环境,例如准备数据库连接、模拟http请求等等,然后才能执行具体的测试。这些准备工作,对很多测试用例都是一样的,应该避免写重复的代码,可以用宏。
fiveam提供了一个方法,管理这些构建测试环境的宏,这个特性被称为fixture,fixture是可以带输入参数的。def-fixture用于定义fixture。rem-fixture用于删除fixture。定义用例时可以指定fixture和输入参数,例如(fiveam:test (test-name :fixture fixture-name) ...)或(fiveam:test (test-name :fixture (fixture-name arg1 arg2)) ...)。
例如:
(fiveam:def-fixture fix-1 (x y)
(let ((z (* x y))) (&body))) 这里的&body会被测试用例的body替换掉
(fiveam:test (fix-1.1 :fixture (fix-1 3 4))
(is (eql z 12)))
【执行用例】
(fiveam:run!) 执行所有用例
(fiveam:run! 用例名或测试套名)
(fiveam:!) 重复上一次执行过的命令
(fiveam:!!) 重复上上次执行过的命令
(fiveam:!!!) 重复上三次执行过的命令