iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 29
0
Software Development

擁抱 Clojure系列 第 29

[第 29 天] 擁抱 Clojure:測試

測試

你是否有過這樣的經驗:本來只是修改了 A 部分的程式,結果改完之後 B 部分的程式竟然不正常;或者是以前早就改好的問題,在這次改版之後又出現了呢?當有這些情形出現,你需要的是透過測試來提早檢查可能出錯的地方,並找出會導致出錯的條件。

除了人工測試之外,最應該加入的就是有一組編寫好的測試程式。有了這些測試程式,你可以在正式上線或提交程式碼之前,利用這些測試程式檢查新修改的程式是否符合規定,也可以保證過往的問題不再出現。

本文就跟大家介紹在 Clojure 專案中撰寫測試的方法。使用的工具是內建的 clojure.test

事前準備

使用 clojure.test 之前,要記得在測試程式中將 clojure.test 的命名空間引入,如下所示:

(ns clj-test.core-test
  (:require [clojure.test :refer :all]))

或在 REPL 中載入 clojure.test 命名空間:

(require '[clojure.test :refer :all])

如果使用 leiningen 建立專案,專案中已經有一個 test 目錄,其中有自動產生的空白測試程式碼範本,把測試程式寫在那裏就對了。

斷言

is

像其他的測試框架一樣,clojure.test 提供了斷言 (Assertion) 用來判斷一段程式的結果是否符合預期。它提供了 is 這個巨集判斷結果是否爲真,以下是它的使用範例:

(is (= 4 (+ 2 2)))
;; => true
(is (.startsWith "abcde" "ab"))
;; => true
(is (instance? Integer 256))
;; => FAIL in () (form-init542440103815122923.clj:1)
;; => expected: (instance? Integer 256)
;; =>   actual: java.lang.Long
;; => false

are

除了 is 之外,clojure.test 還提供了 are 這個巨集,幫助你將許多相似的判斷整理起來,使用方法如下:

(are [x y] (= x y)
  4 (+ 2 2)
  2 (+ 1 1))
;; => true

上面的範例跟以下的範例是一樣的,但是透過 are 就可以彙整衆多的 is 程式:

(is (= 4 (+ 2 2)))
(is (= 2 (+ 1 1)))

thrown?

因爲 Clojure 是一個依靠 JVM 的語言,除了測試要執行的程式是否符合預期,也會有需要測試是否出現例外的時候。提供了 thrown? 巨集來完成這項工作:

(is (thrown? ArithmeticException (/ 1 0)))

測試案例

撰寫

測試框架 clojure.test 提供 deftest 給使用者定義自己的測試案例,透過 deftest 中一個個寫好的判斷程式,來檢查欲執行的程式是否正確。

(deftest addition
  (is (= 4 (+ 2 2)))
  (is (= 7 (+ 3 4))))

不同的測試案例也可以再由另一個 deftest 包覆起來成爲一個更高階的測試案例。

(deftest arithmetic
  (addition)
  (subtraction))

說明文字

要清楚講明測試案例的作用,除了把命名儘量寫的容易理解之外,另一個方式就是在測試案例的說明註解中寫清楚。在 clojure.test 中,想利用文字清楚說明測試的意圖,可以在 is 中加上說明文字。當測試出錯時,該處的文字會出現在錯誤報告中:

(is (= 5 (+ 2 2)) "Crazy arithmetic")
;; => FAIL in () (form-init542440103815122923.clj:1)
;; => Crazy arithmetic
;; => expected: (= 5 (+ 2 2))
;; =>   actual: (not (= 5 4))
;; => false

另外也提供了 testing 巨集,讓撰寫測試者可以將幾個斷言聚集在一起加上說明文字,不至於散亂而更有組織。同樣地,說明文字也會出現在錯誤報告中:

(deftest arithemetic-test
  (testing "Arithmetic"
    (testing "with positive integers"
      (is (= 4 (+ 2 2)))
      (is (= 7 (+ 3 4))))
    (testing "with negative integers"
      (is (= -4 (+ -2 -2)))
      (is (= -1 (+ 3 -4))))))

要注意的是,testing 巨集只能在 deftest 中使用。

執行

寫好測試案例之後,可以透過 clojure.test 提供的 run-tests 來執行寫好的測試範例:

(run-tests 'your.namespace 'some.other.namespace)

如果在 run-tests 中沒有寫下命名空間,將會執行目前命名空間中的測試案例。

(run-tests)
;; => 
;; => Testing user
;; =>
;; => Ran 2 tests containing 6 assertions.
;; => 0 failures, 0 errors.
;; => {:test 2, :pass 6, :fail 0, :error 0, :type :summary}

或在命令列下使用 leiningen 執行測試:

$ lein test

lein test clj-test.core-test

lein test :only clj-test.core-test/a-test

FAIL in (a-test) (core_test.clj:7)
FIXME, I fail.
expected: (= 0 1)
  actual: (not (= 0 1))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
Tests failed.

治具

有時候一些相關的測試案例執行之前,需要先啓動某些資源。比如資料庫的測試案例,就必須在所有測試開始之前,先與資料庫做好連線。在測試完畢之後,妥善地恢復成之前的樣貌。這種在測試案例之中的環境準備,就稱爲治具 (Fixture)。

clojure.test 中,Fixture 只是一個簡單的函式,它只接受一個參數。這個參數就是待執行的測試案例,如果想要在執行測試案例前後做一些準備或善後作業,只要在測試案例前後執行即可,範例如下:

(defn my-fixture [test-fn]
  ;; 在這裡設定或啓動必須事先準備好的事物
  (test-fn) ;; 呼叫測試案例
  ;; 在這裡做善後工作
)

Fixture 分爲兩種,一種是只需要執行一次,另一種是針對每個測試案例都會執行一次。以下是使用範例:

(use-fixtures :once load-data-fixture)   ;; 只執行一次
(use-fixtures :each add-test-id-fixture) ;; 每個測試案例都會執行一次

回顧

經由本篇文章,你知道了如何引入 clojure.test 命名空間開始進行測試,也知道了幾種斷言可以用來檢驗運算式是否正確。知道了撰寫測試案例的方式,還有用說明文字輔助解釋測試案例。還知道了如何執行測試案例以及治具的使用方法。

還不賴吧?今天就先到這裡,下一篇文章再見囉!

(本篇文章同步刊登於 GitHub,歡迎在文章下方留言或發送 PR 給予建議與指教)


上一篇
[第 28 天] 擁抱 Clojure:巨集
下一篇
[第 30 天] 擁抱 Clojure:下一步
系列文
擁抱 Clojure30

尚未有邦友留言

立即登入留言