想要很简单就对写的代码进行测试?本章将介绍如何编写自动化测试并查看代码的覆盖率。同时也介绍了在现在的持续集成服务(如 Travis CI 和 Coveralls)中插入自己的测试。

我们将使用一个成熟且设计良好的回归测试框架,叫做 Prove。Prove 不是唯一的测试框架,FiveAM也很受欢迎(参见 Turotial-Working-with-FiveAM) 这篇博客),同时还有很多其他的测试框架。我们喜欢 Prove 是因为它的文档及其可拓展报告(prove 的报告风格非同一般,而且可以拓展)。

warning: Prove has a couple limitations and will soon be obsolete. We advise to start with another test framework.

使用 Prove 进行测试

安装加载

Prove 在 quicklisp 中可以找到:

  1. (ql:quickload :prove)

上面这条命令将会安装并加载 prove

编写测试文件

  1. (in-package :cl-user)
  2. (defpackage my-test
  3. (:use :cl
  4. :prove))
  5. (in-package :my-test)
  6. (subtest "Showing off Prove"
  7. (ok (not (find 4 '(1 2 3))))
  8. (is 4 4)
  9. (isnt 1 #\1))

Prove 的 API 包括下列测试函数: okisisntis-valuesis-typelike(检查正则表达式), is-print(检查是否是标准输出),is-erroris-expandpassfailskipsubtest

运行测试文件

  1. (prove:run #P"myapp/tests/my-test.lisp")
  2. (prove:run #P"myapp/tests/my-test.lisp" :reporter :list)

将会得到以下的结果:

26. Testing - 图1

运行一个测试

可以直接通过将测试文件编译来运行测试。在 Slime 中,通常是 C-c C-c 快捷键。

更多

Prove 还可以:

  • Travis CI 上运行,
  • 修改输出的颜色
  • 报告测试时间
  • 修改默认测试函数,
  • 设置阈值来减缓测试的运行,
  • 一旦遇到异常,调出 CL debugger
  • ASDF 集成,因此可以在 REPL 中执行 (asdf:test-system)(prove:run)(由同一个作者提供的 cl-project有相应的配置)

更多参见 Prove’s documentation!

交互式修复测试单元

Common Lisp 天然就是可交互的(所以才会有很多的解释器),并且测试框架也可以使用这个特性。这样的话,在测试失败是测试框架可以打开调试器(debugger),这样就可以检查堆栈追踪并能立即跳转到出错的那一行,对其进行修复,然后通过 restart 从之前出错的地方重新运行测试。

在 Prove 中,将 prove:*debug-on-error* 设为 t

下面简短的视频展示了所有的一系列操作(使用的是 FiveAM):
注意,在 Debugger 中的操作如下:

  • <enter> 展示更多的追踪(backtrace)信息
  • v 在追踪信息中跳转的相对应的行或函数
  • 更多选项可以查看菜单

代码覆盖率

代码覆盖率工具会输出个图形界面,通过这个界面可以知道代码哪部分被测试了,哪部分没有被测试:

26. Testing - 图2

Generating an html test coverage output

SBCL comes with a built-in module to do code coverage analysis:
sb-cover.

Coverage reports are only generated for code compiled using
compile-file with the value of the sb-cover:store-coverage-data
optimization quality set to 3.

  1. ;;; Load SB-COVER
  2. (require :sb-cover)
  3. ;;; Turn on generation of code coverage instrumentation in the compiler
  4. (declaim (optimize sb-cover:store-coverage-data))
  5. ;;; Load some code, ensuring that it's recompiled with the new optimization
  6. ;;; policy.
  7. (asdf:oos 'asdf:load-op :cl-ppcre-test :force t)
  8. ;;; Run the test suite.
  9. (prove:run :yoursystem-test)

Produce a coverage report, set the output directory:

  1. (sb-cover:report "coverage/")

Finally, turn off instrumentation:

  1. (declaim (optimize (sb-cover:store-coverage-data 0)))

You can open your browser at
../yourproject/t/coverage/cover-index.html to see the report like
the capture above or like
this code coverage of cl-ppcre.

持续集成(continuous integration)

Travis CI 和 Coveralls

Travis 是在云端运行测试单元的服务,而 Coveralls 展示的是覆盖范围随时间的演变,同时也会展示 pull 请求将对覆盖范围缠身什么影响。

多亏了 cl-travis,我们可以轻松的在一个或多个解释器(ABCL、Allegro CL,SBCL、CMUCL、CCL以及ECL)进行测试。cl-coveralls 用来将输出的报告发不到服务上。cl-coveralls 支持在 SBCL 和Clozure CL 使用 Travis CI 和 Circle CI。

推荐阅读 lisp-lang.org 上关于“持续集成”]) 的详细说明。

在上述的链接中可以找到很多使用持续集成的例子,但如果只是想快速了解 Travis CI 和 Coveralls 的话,可以阅读:

Gitlab CI

Gitlab CI 是 Gitlab 的一部分,而且也能在 Gitlab.com 上使用,不管是共有库还是私有库。直接来看个简单的 .gitlab-ci.yml 吧:

  1. image: daewok/lisp-devel
  2. before_script:
  3. - apt-get update -qy
  4. - apt-get install -y git-core
  5. - git clone https://github.com/foo/bar ~/quicklisp/local-projects/
  6. test:
  7. script:
  8. - make test

Gitlab CI 是基于 Docker 的。通过 image 来告诉 Gitlab CI 使用 daewok/lisp-devel 镜像。这个镜像包含了 SBCL、ECL、CCL和BCL,同时也将 Quicklisp 安装在 home 目录下(/home/lisp/),因此可以直接使用 quickload。如果有兴趣的话,还有个更简单的选择。Gitlab 将加载镜像,克隆项目然后以管理员的权限进入到项目根目录下,之后在运行其他的命令。

test 是自定义的一个“任务”,script 关键词可以接受命令列表然后执行列表中的命令。

假设需要在运行测试前安装依赖库,before_scipt 会在任务开始前运行。在这,我们克隆一个 Quicklisp 中的一个库,要这么做的话首先的安装 git(Docker 镜像都很简单)。

可以定位自己所在的目录。如果已经安装了 Docker 并启动了 docker 的后台(sudo service docker start),可以运行下面的命令:

  1. docker run --rm -it -v /path/to/local/code:/usr/local/share/common-lisp/source daewok/lisp-devel:latest bash

上面的命令会下载 Lisp 镜像(400Mb左右),将本地额代码挂在到镜像中的指定位置,同时切换到 lisp 这个容器的 bash 中,之后就可以试试 make test 了。

以下 Dockerfile 完整的示例:

  1. image: daewok/lisp-devel
  2. stages:
  3. - test
  4. - build
  5. before_script:
  6. - apt-get update -qy
  7. - apt-get install -y git-core
  8. - git clone https://github.com/foo/bar ~/quicklisp/local-projects/
  9. test:
  10. stage: test
  11. script:
  12. - make test
  13. build:
  14. stage: build
  15. only:
  16. - tags
  17. script:
  18. - make build
  19. artifacts:
  20. paths:
  21. - some-file-name

在上面的 Dockerfile 中,定义了两个 步骤(stages)(参考环境):“test” 和 “build”,这两个步骤是一个接一个运行的。”build” 步骤只有在 “test” 步骤执行成功之后才会执行。

”build“ 只在推送新标签时才会运行,而不是每提交一次就运行一次。当 build 成功后,artifacts 中的 paths 中列出的文件就可以下载。可以从 Gitlab 的 Pipelines UI 或是一个 url 下在这些文件。这个操作只会下载最近一次 build 任务生成的文件:

  1. https://gitlab.com/username/project-name/-/jobs/artifacts/master/raw/some-file-name?job=build

当管道穿过去之后,将会看到:

26. Testing - 图3

现在你已经可以使用 Gitlab CI 了。