Clojure单元测试

Kevin Li

Created: 2023-12-18 Mon 17:15

1. 提要

  1. 单元测试的结构
  2. 结构的层级
  3. fixture的执行方式
  4. 执行方式cider、lein test-refresh、lein cloverage
  5. fixture的定义
  6. app state 和数据migrate

2. 单元测试的结构

  • fixture
  • deftest
  • testing
  • is/are

3. 层级

layer.png

4. 执行顺序

order.png

4.1. Fixture顺序演示:once

once.gif

4.2. Fixture顺序演示:each

注意:test case是没有顺序的(本质上是个map),我们不能依赖deftest的声明顺序 each.gif

5. fixture的组合(compose)

  • 有些情况需要对fixture进行组合, 所以有 compose-fixture 函数
  • (compose-fixture f1 f2) 会生成一个新的fixture
  • 执行顺序 f1 befer -> f2 before -> f2 after -> f1 after

6. 单元测试的准备

  • 开发环境的自动测试:cider, 开发环境运行
  • 自动测试监控: lein test-refresh , 开发&测试环境,测试环境为主
  • 测试覆盖率: lein cloverage , 测试环境下运行 覆盖报告

6.1. Cider测试的常用方式

  • cider-auto-test-mode , (C-c C-k) 会触发test-case的自动执行
  • cider-test-run-test(C-c C-t t) 测试某个 deftest
  • cider-test-run-ns-test(C-c C-t n) 测试某个 ns 对应的test-ns下全部 test-case

6.2. cider避免多次执行fixture

有状态的fixture, 比如重置DB,执行时间长, 而且为了避免冲突, 需要独占DB.

  • :once:each 修改需要 (remove-ns 'test-ns), 再进行测试
  • cider-auto-test-mode 不要和手动执行的测试冲突
  • 不要和lein test-refresh冲突

6.3. 避免同时执行:each 和 :once的展示

remove-ns.gif

7. state

程序不可避免的存在依赖性, 比如接口单元测试依赖DB,系统中存在一系列的全局State:

  • db connection
  • sys config
  • scheduler

而且这些资源本身也存在依赖性, db需要从sys config中获得配置信息, 我们希望这些资源 更新的时候(重新编译)之后,依赖他们的其它命名空间也得到更新.

7.1. Fixture & state

  • 纯函数测试不依赖状态,所以不需要fixture
  • 依赖数据库的情况才需要state, 所以也要设置fixture. 这个依赖是有代价的,reset 一次DB (rollback到初始状态再migrate到最新)

    (time (reset-db))
    ;; => "Elapsed time: 2493.961533 msecs"
    

需要2~3秒钟, 测试尽量减少依赖.

7.2. Fixture 管理 state

后台用mount库来管理state. 测试中Fixture保证测试期间state是满足测试要求的:

(defn prepare-before-test []
  (mount/start
   #'custombackend.config/env #'custombackend.db.core/*db*
   #'custombackend.handler/app-routes
   #'custombackend.modules.redis.redis-config/server1-conn)
  (reset-db))
(defn clean-up-after-test []
  (mount/stop
   #'custombackend.config/env #'custombackend.db.core/*db*
   #'custombackend.handler/app-routes
   #'custombackend.modules.redis.redis-config/server1-conn) )
(use-fixtures
  :each (fn [f]
    (prepare-before-test)
    (f)
    (clean-up-after-test)))

8. db migrate

migrate是我们保证测试独立性的方式

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 跳到特定版本数据库         ;;
;; 实质上是reset-db到特定版本 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(user/migrate-to digit-version)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 重置DB是先rollback到底 ;;
;; 再migrate到最新        ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(user/reset-db)