clojure.specを開発やテストで活用する
clojure.specは大きく3つの使い方がある
ドキュメントの内容を補完する
開発やテストのときにspecによるチェックを行う
ひとつ目は特に意識しなくてもいいんだけど、ふたつ目とみっつ目は意識して行わない限り使うことができない。ひとつずつ簡単に説明していくことにします。
ドキュメントの内容を補完する
これはとても分かりやすい。例えば、以下のようなwrap-keyword-paramsというRingミドルウェアがあったとする。 code:clojure
(ns demo.core
(defn wrap-keyword-params
(wrap-keyword-params handler nil))
(let [k (if (and (regex? exclude-regex)
(re-matches exclude-regex k))
k
(keyword k))]
(assoc m k v)))
{}
params))]
(fn -wrap-keyword-params req (handler (update req :params f))))))
code:clojure
dev> (require 'demo.core)
;;=> nil
dev> (doc demo.core/wrap-keyword-params)
-------------------------
demo.core/wrap-keyword-params
;;=> nil
これに以下のようなspecを書いてあげる。
code:clojure
(instance? java.util.regex.Pattern x))
;; sはclojure.spec.alphaのエイリアス
(s/fdef wrap-keyword-params
:args (s/cat :handler fn? :regex (s/? regex?))
:ret fn?)
こうするとドキュメントの表示が以下のようにspecを考慮したものとなる。
code:clojure
dev> (doc demo.core/wrap-keyword-params)
-------------------------
demo.core/wrap-keyword-params
Spec
args: (cat :handler fn? :regex (? regex?))
ret: fn?
;;=> nil
開発やテストのときにspecによるチェックを行う
clojure.specはclojure.spec.test.alpha/instrumentという関数を実行することで、その後にspecを定義してある関数を実行するたびにspecの通りの引数や戻り値になっているかをチェックしてくれる。 code:clojure
;; instrument実行前
dev> (demo.core/wrap-keyword-params identity "")
;; instrument実行後
dev> (require 'clojure.spec.test.alpha)
;;=> nil
dev> (clojure.spec.test.alpha/instrument)
dev> (demo.core/wrap-keyword-params identity "")
clojure.core/ex-info (core.clj:4739)
で、これをどうやって実行すると良いのかという話。いくつかの選択肢があるので、パッと書き出してみる。
REPL起動時に必ず読み込まれるネームスペースでinstrumentを実行する テスト実行スクリプトでテスト実行前にinstrumentを実行する
たぶんこんなもんかな。REPL起動後に手動でinstrumentを実行するのは特に説明しなくても大丈夫だと思います。REPL起動時に必ず読み込まれるネームスペースというのは、userネームスペース、あるいはDuctやStuart Sierraのreloadedに影響を受けていればdevネームスペースだったりすると思うんですが、このネームスペースでinstrumentを実行するというもの。例えば以下のように。 code:clojure
(ns dev
(stest/instrument)
これは例なので実際にはもっといろいろなネームスペースがrequireされているでしょうし、何よりspecを書いてあるネームスペースをある程度読み込んでいることが前提です。そうでないとinstrumentはspecを認識できないので。
テスト実行スクリプトはLeiningenを利用している場合はあまり意識することがないですが、Clojure CLIを利用する場合などは分かりやすいと思う。テスト対象のネームスペースをすべて読み込んだ後(手動/自動問わず)に、instrumentを実行するとテスト実行時にspec違反しているものがあればエラーになってコケるようになる。そのため、テスト実行スクリプトでなくても、必ずテストの前に読み込まれるネームスペースであれば同様のことを行うことが可能です。 プロパティベーステスト
clojure.spec.test.alpha/checkを使って自動プロパティベーステストを実行するか、clojure.test.check.generatorsネームスペースあたりを使ってプロパティベーステストを書くことができます。 このあたりは他の資料に詳しいので、参考にしてみてください。