ミカヅキClojure : dooを使って、楽々ClojureScriptのUnitTest

Clojureではユニットテスト用のフレームワークとコマンドが標準で用意されているので楽チンですが、ClojureScriptはちょっと面倒です。そこで、今回は楽にするdooを紹介。今回は、ちょっと記事が長め。

Story Permalink

Clojureには、clojure.testというユニットテストフレームワークが標準で用意されています。

deftestマクロなどを使って書いたテストケースはこんな感じです。

(deftest a-test
  (testing "FIXME, I fail."
    (is (= 0 1))))

あ、これは標準のプロジェクトテンプレートで書かれているものですが、これを下記のleinコマンドで実行すると、テスト結果が出力されます。

$ lein test
 
Testing cljs-test.core-test

FAIL in (a-test) (core_test.clj:7)
FIXME, I fail.

expected: 0
  actual: 1

    diff: - 0
          + 1
 
Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
Tests failed.

この例だとテスト失敗していますが、 (is (= 0 1))のところを(is (= 1 1))にして直すところからテスト駆動な開発を進めていけるわけです。

ClojureScriptのテストは面倒?

ClojureScriptにも標準でcljs.testというテストフレームワークが用意されているのですが、ちょっと扱い方がやっかいです。

テストについては下記に書かれているのですが、

REPLでやる方法が書かれています。

ただし、lein testのようにコマンドベースで一括でテストする方法をやりたいと思い始めると、途端に藪が始まります。

ClojureScriptの場合は、lein-cljsbuildというプラグインを使って、ビルドしたりしているかと思いますが、理想的には

$ lein cljsbuild test

でテストが実行できればいいのでしょう。

これを実現可能にするには、下記のようにいくつかの準備をしないといけません。

  • PhantomJSなどの起動JavaScriptスクリプトの用意
  • テスト専用の実行HTMLの用意
  • project.cljでのビルド設定の用意
  • :test-commandsの設定

テストの方法には、ほかにもfigwheelを用いた方法などもあります。

上記の方法のサンプルは、lein-cljsbuildプラグインのサンプルとして用意されています。

僕もこの方法を使っていたのですが、もう少し楽チンなやりかたはないだろうかと探した挙句たどり着いたのが、dooというプラグインです。

dooは何がいいのか?

dooは、一言でいうと、テスト用の設定を簡略化してくれるLeiningen用のプラグインです。

dooを使うことで、下記のような面倒な設定が不要になります。

  • :test-commandsの用意が不要
  • 各種JavaScript実行用のオプションがあらかじめ用意されている
  • テスト用の専用HTMLや起動JavaScriptの用意が不要

特にJavaScript実行用のオプションの豊富さは目を見張ります。

公式にサポートされているもので下記があります。

  • Chrome
  • Firefox
  • IE
  • Safari
  • Opera
  • Slimer
  • PhantomJS
  • Node.js
  • Rhino / Nashorn

加えて、

  • V8
  • Jscore

に関して、将来的なサポートが想定されています。

さて、実際に素朴なClojureScriptプロジェクトを作成し、それにテストを組み込んで実行してみます。

今回のゴール

今回は、サンプルプロジェクトを組んだあと、dooを使ってユニットテストをコマンドラインから走らせる方法を把握します。

具体的には、ClojureScriptを書いて、テストケースを書いて、

$ lein doo phantom test

とコンソールに入力するだけで、PhantomJSを使ったユニットテストが走らせることを可能にします。しかも、ほとんど追加で設定などは不要なので、すごい楽というのを体験します。

サンプルプロジェクトを作成

やることは、

  • プロジェクトの用意
  • Node.js関係の準備
  • ClojureScript用のproject.cljの定義
  • ClojureScriptのコードを記述
  • コンパイルしたJavaScriptを開いて実行確認するためのHTML作成

まずは、素朴ClojureScriptプロジェクトを作成します。

プロジェクト名はcljs_tddとしました。

$ lein new app cljs_tdd

続いて、プロジェクトのディレクトリに移動します。

$ cd cljs_tdd

さて、まずはこのプロジェクト用のpackage.jsonを用意しましょう。

ここにテストで利用するPhantomJsの依存関係を記述します。

まず、下記を実行して、package.jsonを生成します。

$ npm init

もしnpm initをはじめて使うという方は、このコマンドを起動すると聞かれるいくつかの質問に答えるとpackage.jsonが生成できるものと思っておけばいいでしょう。

$ npm install phantomjs --save-dev

実行時に--save-devをつけることで、package.jsonに依存関係を自動で記述してもらいます。

これは別のユーザなどが使う際にnpm installと入力するだけで、必要なNode.jsパッケージがインストール可能にするために大事なことです。

ここまでで出来上がったpackage.jsonは下記のようになります。

{
  "name": "cljs_tdd",
  "version": "1.0.0",
  "description": "FIXME: description",
  "main": "index.js",
  "directories": {
    "doc": "doc",
    "test": "test"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "phantomjs": "^2.1.3"
  }
}

一回、PhantomJSがインストールされたかどうか、コンソールで実行してみましょう。

下記コマンドをコンソールで打ってみます。

$ node_modules/.bin/phantomjs -v
2.1.1

このようにバージョンが出力されれば成功です。

ちなみに、グローバルでPhantomJSがインストール済みな人は気にする必要はありません。

続いて、project.cljを下記のようにします。

project.clj

(defproject cljs_tdd "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [org.clojure/clojurescript "1.7.48" :exclusions [org.apache.ant/ant]]]
  :plugins [[lein-cljsbuild "1.1.3"]]
  :cljsbuild {:builds {:dev {:source-paths ["src_cljs"]
                             :compiler {:output-to "app/js/main.js"
                                        :optimizations :simple}}}}
  :clean-targets ["app/js/main.js"]
  :main ^:skip-aot cljs-test.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

追加したのは、

  • ClojureScriptを依存定義に追加
  • Leiningenプラグインとしてlein-cljsbuildを追加
  • cljsbuild用のビルド定義を追加
  • クリーン用のファイルを追加

といった感じです。ビルド設定は本当に必要最低限しか記述していません。

ここで設定しているのは、src_cljs以下にClojureScript用のコードを置くこと、そしてJavaScriptのコンパイル結果をapp/js/main.jsに吐き出すことです。

さて、続いてsrc_cljs以下にClojureScriptコードを書いていきます。src_cljs/cljs_tdd/core.cljsに下記のようなコードを書きます。

(ns cljs-tdd.core)

;;; 今回テストする素朴な関数
(defn my-fn
  "roleとstatusで絞り込む関数"
  [m role status]
  (if (vector? m)
    (->> m
         (filterv (fn [x]
                    (and (= role (:role x))
                         (= status (:status x))))))
    []))

;;; コンソールに出力
(.log js/console (str (my-fn [{:role 1 :status 0 :name "kara_d"}
                              {:role 2 :status 0 :name "taro"}
                              {:role 2 :status 1 :name "hanako"}]
                             2
                             1)))

忘れてはいけないポイントは、拡張子を.cljsにするということです。これをうっかり忘れて全然読み込んでもらえないとかあります。

コードの内容は、下記のような構造のデータを渡した時に、

[{:role 1 :status 0 :name "kara_d"}
 {:role 2 :status 0 :name "taro"}
 {:role 2 :status 1 :name "hanako"}]

roleとstatusで絞り込んだマップを返すという関数を書いてあります。まあ、深い意味はありません。

これで、サンプルコードとしては終わりで、あとはそれをブラウザで開いてみるHTMLを用意します。app/index.htmlを下記のコードで用意してください。

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <title>Document</title>
    <script src="js/main.js"></script>
  </head>
  <body></body>
</html>

中身はコンパイルされたJavaScriptを読み込む以外は何もしていませんし、このHTMLは、単に今回のサンプルの確認用でユニットテストに必要というわけでもありません。

まあ、ここまでで一旦休憩ということで、ClojureScriptをビルドし、ブラウザで確認してみましょう。

$ lein cljsbuild once

無事JavaScriptがapp/js/main.jsとして出力されたら、ブラウザでapp/index.htmlを開きます。

開発コンソールを表示させて、上のキャプチャのように表示されたら無事サンプルの実装は完了です。

dooでテストランナーを走らせてみる

dooを使うのは楽なのですが、それでもちょびっとだけ準備が必要です。

必要なことは下記のとおりです。

  • project.cljにテスト用のビルド設定を書く
  • テストケースを書く
  • doo用のランナー設定を書く

project.cljの修正

project.cljに下記の項目を追加します。

  • 依存関係として、[lein-doo "0.1.6"]を追加
  • テスト用のビルド設定、:testを追加
  • クリーン用のターゲットとして、:clean-targets"app_test/js/main.js"を追加
  • doo用のPhantomJSの実行パスの指定

追加したコードが以下のようになります。

(defproject cljs_tdd "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.7.0"]
                 [org.clojure/clojurescript "1.7.48" :exclusions [org.apache.ant/ant]]]
  :plugins [[lein-cljsbuild "1.1.3"]
            [lein-doo "0.1.6"]]
  :cljsbuild {:builds {:dev {:source-paths ["src_cljs"]
                             :compiler {:output-to "app/js/main.js"
                                        :optimizations :simple}}
                       :test {:source-paths ["src_cljs" "test_cljs"]
                              :compiler {:output-to "app_test/js/main.js"
                                         :optimizations :simple
                                         }}}}
  :clean-targets ["app/js/main.js"
                  "app_test/js/main.js"]
  :doo {:paths {:phantom "node_modules/.bin/phantomjs"}}
  :main ^:skip-aot cljs-test.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}})

テスト用のビルド設定では、コンパイル後の出力先をapp_test/js/main.jsにしています。これはテスト時に一時的に出力するスクリプトで、本番には使用しません。

また、クリーン時のファイルも同じファイルを削除するようにしています。

テストケースを書く

続いて、いよいよテストケースを書きます。test_cljs/cljs_tdd/test/core.cljsを用意し、下記のコードを書きます。

(ns cljs-tdd.test.core
  (:require [cljs.test :refer-macros [deftest is testing]]
            [cljs-tdd.core :as c]))

(deftest test-my-fn
  (testing "roleとstatusで絞り込む"
    (testing "hanakoの抽出"
      (is (= (c/my-fn [{:role 1 :status 0 :name "kara_d"}
                       {:role 2 :status 0 :name "taro"}
                       {:role 2 :status 1 :name "hanako"}]
                      2
                      1)
             [{:role 2 :status 1 :name "hanako"}])))
    (testing "nilの場合"
      (is (= (c/my-fn nil nil nil) [])))))

このテストコード自体は、Clojureで書いているテストコードと同様なものです。dooプラグインのメリットの一つは、テスト形式は新しい形式で書く必要がない点があります。

テストランナー向けのコードを用意する

最後に、ちょっとdooならではのコードを書きます。test_cljs/cljs_tdd/test/core_runner.cljsを用意し、下記のコードを書きましょう。やっていることは、dooにテストするコードのネームスペースを渡しているのみです。

(ns cljs-tdd.test.core-runner
  (:require ;; [cljs.test :as test]
            [doo.runner :refer-macros [doo-all-tests doo-tests]]
            [cljs-tdd.test.core]))

(doo-tests 'cljs-tdd.test.core)

以上で、テストコードの作成は完了です。

テストの実行

さて、テストを実行してみましょう。 下記のコマンドで実行が出来ます。

$ lein doo phantom test

すると、下記のような表示が出たはずです。

今回は、実装済みのサンプルを確認するところから書いたので、成功するテストケースのみ書いています。

各人のスタイルでテストケースを書き進めてみてください。

;; ======================================================================
;; Testing with Phantom:

[{:role 2, :status 1, :name "hanako"}]

Testing cljs-tdd.test.core

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

無事テストが通りました!

ちなみに、テストコードは監視されており、編集すると再度テストを走らせてくれます。

また、コマンドの第4引数に監視するかどうかを含めることができます。

$ lein doo phantom test auto

だと、監視し、

$ lein doo phantom test once

だと、一度きりのテストとなります。

Colophon

編集長
Greative GK. 原一浩 ( kara_d )
製版システム
Clojure / Compojure / Ring / Enlive / markdown-clj / Jetty / MySQL
Share this magazine!
Follow designudge

Magazine Archives

vol.122

2016-2-01

Cover

台湾に行ってきました。

今回のカバーは、台湾にて撮影したものです。

自治体Webデザイントレンドこぼれ話 : ここだよマップ

CSS Niteの年末イベントShiftでやっているセッションにデザイントレンドというのがあります。

descjop で遊ぼう day 16 : Windows版のメニューを表示する

2015年の振り返り的な一人レトロスペクティブ

そういえば、まだ2015年の振り返りをマガジンに掲載していませんでした

編集後記

vol.121

2016-1-14

Cover

今回のカバーは、、、UFO?

自治体Webデザイントレンドこぼれ話 : 予告

CSS Niteの年末イベントShiftでやっているセッションにデザイントレンドというのがあります。

descjop で遊ぼう day 11 : Omのコンポーネントで遊ぼう

descjop で遊ぼう day 12 : Omのコンポーネントで遊ぼうその2

descjop で遊ぼう day 13 : Clojure ワークショップでTAしてきた話

descjop で遊ぼう day 14 : アプリケーションメニューを作ろう

descjop で遊ぼう day 15 : descjop 0.5.3をリリースしました

編集後記

vol.120

2016-1-14

Cover

今回のカバーも、初詣に行ったときのショットです。

descjop で遊ぼう day 6 : Electronがうまくダウンロードできないとき

descjop で遊ぼう day 7 : Hello Worldを出力してみるには

descjop で遊ぼう day 8 : Om basedテンプレートを使ってみる

descjop で遊ぼう day 9 : Om basedテンプレートにボタンをつける

descjop で遊ぼう day 10 : Om basedテンプレートにボタンをつけるその2

編集後記

vol.119

2016-1-10

Cover

今回のカバーは、初詣に行ったときのショット。

「descjop で遊ぼう」について

「descjop で遊ぼう」というのは年末にやったアドベントカレンダーのシリーズなのですが、前段としてまずはその解説をば。

descjop で遊ぼう day 1 : はじめに

descjop で遊ぼう day 2 : 3つのテンプレート

descjop で遊ぼう day 3 : 環境構築をしよう

descjop で遊ぼう day 4 : Helpを見てみよう

descjop で遊ぼう day 5 : デフォルトアプリケーションのビルドと起動

編集後記

vol.118

2015-10-22

Cover

今回のカバーは、Stack Overflow DevDaysのイベントをお手伝いしたときの窓からのショット。

Vaqum Web Design Review

創刊以来延々と続くWebサイトレビュー

ミカヅキClojure : ゼロからはじめるClojure入門第2回

Clojureっていうじつに面白いプログラミング言語をとりあげていきます

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記

vol.117

2015-10-07

Cover

今回は散歩道からのショット

Editor’s Picks

designudgeで扱っている内容に近いジャンルのリソースのうち気になったものなど

U.S. Web Design Standardsを見てみる

今話題となっているU.S. Web Design Standardsの周辺情報など見てみました

Bootstrap 4徹底攻略 (3)

Bootstrap 4のアルファ版がでているので使おう

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記

vol.116

2015-9-28

Cover

今回は帰省した際に通った三島からのショットです

Vaqum Web Design Review

創刊以来延々と続くWebサイトレビュー

みそじ過ぎからの英語再学習

40を目前に今までサボっていた英語学習をはじめました

Bootstrap 4徹底攻略 (2)

Bootstrap 4のアルファ版がでているので使おう

ミカヅキClojure

Clojureっていうじつに面白いプログラミング言語をとりあげていきます

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記

vol.115

2015-9-13

Cover

前回と同じ開発合宿のショットから一つ

Vaqum Web Design Review

創刊以来延々と続くWebサイトレビュー

開発合宿へ行こう! : Python mini hack-a-thon 夏山合宿 2015

先週末に行ってきた開発合宿について書きました。

ミカヅキClojure

Clojureっていうじつに面白いプログラミング言語をとりあげていきます

インフォメーション

おすすめのイベント・勉強会情報を紹介

編集後記