单元测试也是一个开发中最常见的需求,在Java里我们用JUnit或者TestNG,在clojure里也内置了单元测试的库。标准库的clojure.test,以及第三方框架midje。这里我将主要介绍clojure.test这个标准库,midje是个更加强大的测试框架,广告下,midje的介绍在第二次cn-clojure聚会上将有个Topic,我就不画蛇添足了。通常来说,clojure.test足够让你对付日常的测试。

首先看一个最简单的例子,定义一个函数square来计算平方,然后我们测试这个函数:

;;引用clojure.test
(ns example
  (:use [clojure.test :only [deftest is run-tests]]))
;;定义函数
(defn square [x]
  (* x x))
;;测试函数
(deftest test-square
  (is (= 4 (square 2)))
  (is (= 9 (square -3))))
;;运行测试
(run-tests 'example)

执行输出:


Testing example

Ran 1 tests containing 2 assertions.
0 failures, 0 errors.

这个小例子基本说明了clojure.test的主要功能。首先是断言is,类似JUnit里的assertTrue,用来判断form是否为true,它还可以接受一个额外的msg参数来描述断言:

 (is (= 4 (square 2)) "a test")

它还有两种变形,专门用来判断测试是否抛出异常:

 (is (thrown? RuntimeException (square "a")))
 (is (thrown-with-msg? RuntimeException #"java.lang.String cannot be cast to java.lang.Number"  (square "a")))

上面的例子故意求"a"的平方,这会抛出一个java.lang.ClassCastException,一个运行时异常,并且异常信息为java.lang.String cannot be cast to java.lang.Number。我们可以通过上面的方式来测试这种意外情况。clojure.test还提供了另一个断言are,用来判断多个form:

 (testing "test zero or one"
    (are
     (= 0 (square 0))
     (= 1 (square 1))))

are接受多个form并判断是否正确。这里还用了testing这个宏来添加一段字符串来描述测试的内容。

其次,我们用deftest宏定义了一个测试用例,deftest定义的测试用例也可以组合起来:

   (deftest addition
     (is (= 4 (+ 2 2)))
     (is (= 7 (+ 3 4))))
   (deftest subtraction
     (is (= 1 (- 4 3)))
     (is (= 3 (- 7 4))))
   (deftest arithmetic
     (addition)
     (subtraction))

但是组合后的tests运行就不能简单地传入一个ns,而需要定义一个test-ns-hook指定要跑的测试用例,否则组合的用例如上面的addition和subtraction会运行两次。我们马上谈到。

定义完用例后是运行测试,运行测试使用run-tests,可以指定要跑测试的ns,run-tests接受可变参数个的ns。刚才提到,组合tests的时候会有重复运行的问题,要防止重复运行,可以定义一个test-ns-hook的函数:

(defn test-ns-hook []
  (test-square)
  (arithmetic))

这样run-tests就会调用test-ns-hook按照给定的顺序执行指定的用例,避免了重复执行。

在你的测试代码里明确调用run-tests执行测试是一种方式,不过我们在开发中更经常使用的是lein来管理project,lein会将src和test分开,将你的测试代码组织在专门的test目录,类似使用maven的时候我们将main和test分开一样。这时候就可以简单地调用:

lein test

命令来执行单元测试,而不需要明确地在测试代码里调用run-tests并指定ns。更实用的使用例子可以看一些开源项目的组织。

单元测试里做mock也是比较常见的需求,在clojure里做mock很容易,原来clojure.contrib有个mock库,基本的原理都是利用binding来动态改变被mock对象的功能,但是在clojure 1.3里,binding只能改变标注为dynamic的变量,并且clojure.contrib被废弃,部分被合并到core里面,Allen Rohner编译了一个可以用于clojure 1.3的clojure.contrib,不过需要你自己install到本地仓库,具体看这里。不过clojure.contrib.mock哪怕使用1.2的编译版本其实也是可以的。

clojure.contrib最重要的是expect宏,它类似EasyMock里的expect方法,看一个例子:

(use [clojure.contrib.mock :only [times returns has-args expect]])

(deftest test-square2
  (expect [square (has-args [number?] (times 2 (returns 9)))]
          (is (= 9 (square 4)))))

has-args用来检测square的参数是不是number,times用来指定预期调用的次数,而returns用来返回mock值,是不是很像EasyMock?因为我们这个测试只调用了square一次,所以这个用例将失败:

Testing example
"Unexpected invocation count. Function name: square expected: 2 actual: 1"

这个例子要在Clojure 1.3里运行,需要将square定义成dynamic:

(defn ^:dynamic square [x]
  (* x x))

否则会告诉你没办法绑定square:

actual: java.lang.IllegalStateException: Can't dynamically bind non-dynamic var: example/square

额外提下,还有个轻量级的测试框架expections可以看一下,类似Ruby Facker的facker库提供一些常见的模拟数据,如名称地址等

文章转自庄周梦蝶  ,原文发布时间2012-02-15

Clojure世界:单元测试相关推荐

  1. 技术篇——使用 Junit 实现单元测试

    有很多测试技术和工具可以在敏捷测试中进行单元测试,传统单元测试工具包括 JUnit 和 XUnit,最近几年出来的 BDD 单元测试工具有 Spock 和 Spec2.Spock 和 Spec2 可以 ...

  2. 程序员为什么需要框架?

    Clojure 是一种运行在 Java 平台上的 Lisp 方言,它的出现有助于将比较复杂的表述定义成简洁的呈现.与此同时,Clojure 也具备诸多的工具来解决程序员经常需要面对的难题. 不过随着对 ...

  3. java CMS gc解析

    转载: http://www.blogjava.net/killme2008/archive/2009/09/22/295931.html     CMS,全称Concurrent Low Pause ...

  4. Java与Clojure思维定势

    在本次演讲中,我们将以一个真实的应用程序为例,学习如何仅使用核心Java来构建和设计遵循Clojure功能原理的Java应用程序,而无需使用任何库,lambda,流或怪异的语法. 我们将看到这些功能原 ...

  5. 程序员的职业素养:向世界宣告“我是专业人士”

    点击关注 异步图书,置顶公众号 每天与你分享 IT好书 技术干货 职场知识 Tips参与文末话题讨论,即有机会获得异步图书一本. "噢,笑吧,科廷,老伙计.这是上帝,或者也可以说是命运或自然 ...

  6. 单元测试 Mocking 类库需具备的特性

    一个优秀的单元测试 Mocking 类库,需要具备如下几个特性: 易用性:有非常明确的 API ,易于使用并易于记忆. 健壮性:行为结果始终一致,并保持准确. 帮助性:当程序出错时,给出尽可能明确的原 ...

  7. 在疯狂的前端世界,为什么选择学习React

    题图 | https://github.com/react-icons/react-icons React Native 和 Prettier的作者之一.前端大牛.Twitter大V @Vjeux(C ...

  8. Java程序员必备秘籍 Scala与Clojure函数式编程语言

    编程世界就好比江湖,各种技术与思想有如各种内外家功夫在历史的舞台上纷呈登场,各领风骚.如今,自C.C++传承而来的以Java为代表的命令式语言一派可谓如日中天.门徒万千.多年来,这几门语言一直占据着T ...

  9. 带您走进七周七语言的程序世界

    编者按 这是一本2011年Jolt大奖图书,在本文中,截选了七门各不相同的语言的概况,这七门语言,无论教还是学,对我们而言都是一个宏伟目标.书中的代码足以深刻阐释每一门语言的精髓.这七门语言都有非常优 ...

最新文章

  1. 【万级并发】电商库存扣减如何设计?不超卖!
  2. 多继承的构造函数和析构函数
  3. php采集 纠正一下
  4. 第一章 TensorFlow基础——python语法(一)
  5. java中用byte[]数组实现的队列和用Byte[]实现的队列实际占用空间对比
  6. C# vb.net 分别引用C++编译DLL
  7. 56PY宿迁味道这么可口
  8. 业务 T+1 T+2
  9. 是时候表演真正的技术了——11个Git面试题目,你会多少?
  10. 计算机二级c语言编程题库100题下载,计算机二级C语言编程题库100题.doc
  11. 用NE555的延时电路
  12. 加壳、脱壳以及如何病毒免杀技术与原理
  13. 解决mysql登录出现10061的问题
  14. Android图片加载框架最全解析(五),Glide强大的图片变换功能
  15. 微博解析:微博揭晓年度百大视频号!视频号规模超50万的背后透露着什么讯息?
  16. L1正则化及其稀疏性的傻瓜解释
  17. 新加坡汇丰个人账户分析
  18. php自动填表单,vb实现网页自动填表
  19. MATLAB高光谱图像处理基础
  20. 快手只发作品不直播的赚钱方法

热门文章

  1. 量化网络训练--Towards Effective Low-bitwidth Convolutional Neural Networks
  2. 快速人脸验证--MobileFaceNets: Efficient CNNs for Accurate Real-time Face Verification on Mobile Devices
  3. GAN人脸修复--Generative Face Completion
  4. svn 命令行创建和删除 分支和tags
  5. CGBitmapContextCreate参数详解
  6. python把文件读成字节流_Python中对字节流/二进制流的操作:struct
  7. Spring Boot 2.x基础教程:使用@Scheduled实现定时任务
  8. python爬虫换电脑不能运行_python爬虫程序运行失败,求原因
  9. ldap 统一认证 java_如何在你的系统里集成LDAP统一认证
  10. mysql sa密码是什么_忘记mysql数据库root密码