技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 其他 --> Clojure世界:单元测试

Clojure世界:单元测试

浏览:1444次  出处信息
    单元测试也是一个开发中最常见的需求,在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库提供一些常见的模拟数据,如名称地址

建议继续学习:

  1. web项目和单元测试    (阅读:3389)
  2. Clojure世界:XML处理    (阅读:1965)
  3. Linux下c/c++项目代码覆盖率的产生方法    (阅读:2041)
  4. Clojure世界:Http Client    (阅读:1951)
  5. Clojure世界:文件IO    (阅读:1850)
  6. 单元测试中的Fluent Interface    (阅读:1722)
  7. Clojure世界:如何做性能测试    (阅读:1729)
  8. Clojure世界:使用rlwrap增强REPL    (阅读:1552)
  9. Clojure世界:静态代码分析    (阅读:1610)
  10. Clojure世界:API文档生成    (阅读:1622)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1