Luminus, Reagentな環境でSpecljを導入
ClojureでRSpecのような読みやすいテストを書くためのフレームワークとしてSpecljというものがあります。
ここではprofileにcljsを選択して作成したLuminus projectでClojure, ClojureScriptのspecを実行できるようにしていきます。
前提
前述したLuminus projectは以下のように作成されたprojectを指します
lein new luminus foo +cljs cd foo tree . . ├── Procfile ├── README.md ├── env │ ├── dev │ │ ├── clj │ │ │ └── foo │ │ │ ├── dev.clj │ │ │ └── repl.clj │ │ └── cljs │ │ └── foo │ │ └── dev.cljs │ └── prod │ └── cljs │ └── foo │ └── prod.cljs ├── project.clj ├── resources │ ├── docs │ │ └── docs.md │ ├── public │ │ ├── css │ │ │ └── screen.css │ │ ├── img │ │ └── js │ └── templates │ └── home.html ├── src │ └── foo │ ├── core.clj │ ├── handler.clj │ ├── layout.clj │ ├── middleware.clj │ ├── routes │ │ └── home.clj │ └── session.clj ├── src-cljs │ └── foo │ └── core.cljs └── test └── foo └── test └── handler.clj
この方法で作成すると、React.jsのClojureScriptインターフェイスのreagentやClojureScriptの変更を自動でブラウザに送信するfigwheelなど便利なlibraryを導入してくれます。 (これらの詳しい説明についてはネット上にたくさん転がっているので、それらをご覧ください。)
また、この方法で作成したディレクトリはsrc, src-cljsの2種類のソースコードを格納するディレクトリがあって今ひとつな気がするので、 ここではspecの環境構築と併せて以下のようなディレクトリ構成にしていきます。
src ├──clj └──cljs spec ├──clj └──cljs
ClojureのSpec
まずclojure向けのspecを構成していきます 現在src直下にcljファイルの格納されているfooディレクトリがあるので、これを以下のようにclj配下に持っていきます
src └──clj └── foo
ついでにspecファイルを格納するディレクトリも追加しておきます 以下のような構成でディレクトリを作成してください。
spec ├──clj │ └── foo └──cljs └── foo
次にproject.cljを編集してSpecljの依存関係とtest pathを追加します
... :plugins [[lein-ring "0.9.1"] [lein-environ "1.0.0"] [lein-ancient "0.6.0"] [ragtime/ragtime.lein "0.3.8"] [lein-cljsbuild "1.0.4"] [speclj "3.2.0"]] ; <- これを追加 :source-paths ["src/clj"] ; <- これを追加 :test-paths ["spec/clj"] ; <- これを追加 :dev {:dependencies [[ring-mock "0.1.5"] [ring/ring-devel "1.3.2"] [pjstadig/humane-test-output "0.7.0"] [leiningen "2.5.1"] [figwheel "0.2.5"] [weasel "0.6.0"] [speclj "3.2.0"] ; <- これを追加 [com.cemerick/piggieback "0.1.6-SNAPSHOT"]] ...
一旦ライブラリをインストールしておきましょう$ lein deps
続いてspec/clj/foo/core_spec.clj
というファイルを作成して、以下のようなダミーのテストを記述します
(ns foo.core-spec (:require [speclj.core :refer :all] [foo.core :refer :all])) (describe "Truth" (it "is true" (should true)) (it "is not false" (should-not false))) (run-specs)
これで以下のコマンドを実行するとSpecljによるテストが実行されるはずです
$lein spec spec/clj
ClojureScriptのSpec
続いてClojureScriptのSpecです ClojureScriptのspecにはphantomjsが必要ですのでインストールしておいてください。
またここでは PhantomJS 1.9.8での動作を想定しています
(※ PhantomJS 2.0ではうまく動作しないようです。)
luminusによってClojureScriptはsrc-cljsに格納されているのでこれをsrc/cljsに移動します。 (src-cljsのままでいい場合はこの作業は不要です)
src ├──clj │ └── foo └──cljs └── foo
ディレクトリを移動したらproject.cljでsrc-cljsとなっている部分は全てsrc/cljsと変更しておいてください。
次にproject.cljにテスト向けのbuild情報を記述します。
devのprofile情報に対して以下のように変更してください
:cljsbuild {:test-commands {"spec" ["phantomjs" "bin/speclj" "resources/public/js/app_test.js"]} ; <- これを追加 :builds {:app {:source-paths ["env/dev/cljs"]} :test {:source-paths ["src/cljs" "spec/cljs"] ; <- これを追加 :compiler {:output-to "resources/public/js/app_test.js" ; <- これを追加 :output-dir "resources/public/js/test" ; <- これを追加 :source-map "resources/public/js/test.js.map" ; <- これを追加 :externs ["react/externs/react.js"] ; <- これを追加 :optimizations :whitespace ; <- これを追加 :pretty-print false}}}} ; <- これを追加
project rootにbin/specljというファイルを作成して以下のように記述します (※ Speclj公式のtutorialに対してReactの使用も考慮に入れた変更を加えています)
#! /usr/bin/env phantomjs var fs = require("fs"); var webpage = require('webpage').create(); var system = require('system'); var url = phantom.args[0]; webpage.onConsoleMessage = function (x) { system.stdout.write(x) }; webpage.injectJs("env/test/js/polyfill.js"); // note the polyfill file, this is added to play nice with React webpage.injectJs(url); var result = webpage.evaluate(function () { speclj.run.standard.armed = true; return speclj.run.standard.run_specs( cljs.core.keyword("color"), true ); }); phantom.exit(result);
env配下にtest/js/polyfill.jsというファイルを作成し以下のように編集します。 (phantomjsでcomponentを扱うために必要みたい(適当))
if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
spec/cljs/foo/core_spec.cljsを作成して以下のようにダミーのspecを記述します
(ns foo.core-spec (:require-macros [speclj.core :refer [describe it should should-not run-specs]]) (:require [speclj.core] [foo.core :as my-core])) (describe "Truth" (it "is true" (should true)) (it "is not false" (should-not false))) (run-specs)
以下のようにClojureScriptをcompileして、testタスクを実行したらSpecljによるClojureScript向けのspecが実行されるはずです
$ lein cljsbuild once $ lein cljsbuild spec
これでclojureでの快適なTDD/BDD環境が整いました!
こんなエラーが出たら
以下のようなエラーが出たらPhantomjsがReactのcomponent操作などに対応できていない可能性があります。
TypeError: 'undefined' is not a function (evaluating 'ReactElementValidator.createElement.bind( null, type )')
おそらくtest/js/polyfill.jsかbin/specljの設定がうまくできていないのでしょう。
polyfill.js or bin/specljを見なおして見てください。