こんにちは。kintone開発チームの天野 (@ama_ch) です。
最近はJavaScriptのテストツールが著しく進歩し、日々新しいツールが登場しています。kintoneの開発もこれらのツールによって支えられています。
kintone開発チームでは、昨年末頃からテスト環境の改善に取り組み、モダンなツールセットに乗り換えました。今回は、現在のkintoneのJSユニットテスト環境について紹介します。
kintoneとJSユニットテスト
数年前からユニットテストと自動化の仕組みはあったのですが、ごく一部のユーティリティ関数に書かれているのみで、普段の開発には活用されていませんでした。
ここ1,2年ほどで
- テストスケルトンを生成するジェネレータスクリプトを作る
- テストの書き方をまとめたドキュメントを用意する
- MTGで「ユニットテストを書かなくていいのは小学生まで」などと煽る
- コードレビュー時にテストをお願いする
といった活動をおこない、かなりテストが浸透しました。 ですが、テストの仕組みがあまり使い勝手の良いものではなく、なかなか気軽にテストが書けないという問題がありました。
これまでの仕組み
kintone開発チームで利用しているClosure Libraryに含まれるテストスイート (JsUnitがベースになっています) を使ってテストを書き、PhantomJSでテストを走らせていました。 以下のような感じです。
- テストを記述するためのhtmlファイルを作成
- JsUnitスタイルでテストコードを記述
- CIジョブでPhantomJSからhtmlファイルを開いて実行
実際のテストコード例は、Closure Libraryのリポジトリで確認することができます。
当時はこの仕組みで満足していたのですが、MochaやJasmine, Testemといったモダンなテストツールが普及するにつれて、徐々に不満が溜まっていきました。
- テストを書き始めるのに必要なスケルトンコードが多い
- JsUnitスタイルではなくBDDスタイルでテストを書きたい
- レポーターが選択できない
- PhantomJS以外のブラウザでテストできない
- 自動監視によるTDDができない
これらの不満点をまとめて解消するため、新しいツールセットに乗り換えることを決めました。
現在の仕組み
現在はMocha + expect.js + Sinon.JSでテストを書き、Karmaで実行・レポーティングという構成になっています。また、テスト実行タスクはGruntでまとめています。
基本的なテスティングフレームワークは、メンバーの好みとブラウザサポート状況等から選択しました。
アサーションライブラリとモックライブラリはClosure Libraryにもあったのですが、モダンなツールを使ったほうが簡潔に書けるため、まとめて乗り換えることにしました。
Karmaを選択した理由
一方で、テストランナーの選択はなかなかうまくいきませんでした。
kintoneはテスト時に大量のJSファイル (現在は全部で約2000ファイル) をロードするという事情があり、最初はTestemでテストを動かそうとしましたが、
- テストが実行されない
- watchでCPU使用率が100%になる
などの問題が発生したため断念しました。 その後Karmaを試してみたところ、安定して動作したため、めでたく乗り換えの目処が立ちました。
乗り換えの効果
ユニットテストのツールセットを乗り換えた結果、以下のような効果が得られました。
- BDDスタイルの柔軟な記述
- 可読性の高いアサーション, モッキング
- 高速なテストの実行
約1分かかっていたのが約10秒に。 - 複数ブラウザでのテスト実行
- 柔軟なレポーター
TDD時はspec形式、CI時はJUnit形式など。 - 自動監視 + Growl通知のTDD
- カバレッジ測定
- CIとの連携による各指標の可視化 (後述)
以前の不満点はほぼすべて解消され、高速化やカバレッジ測定などもできるようになりました!
CIとの連携
KarmaはCIジョブ向けのレポートファイルを出力することができます。たとえば、karma-junit-reporterを使うとJenkinsのジョブにテスト結果のグラフを表示することができます。
karma-coverageプラグインを使うと、さまざまな形式でカバレッジレポートを出力できます。cobertura形式で出力すると、Jenkins上でカバレッジが確認できます。
また、これはKarmaとは関係ありませんが、grunt-complexityとJenkinsのCheckstyle Pluginを組み合わせることで、基準値よりも複雑なコードに警告を出すことができます。
このように、CIと連携して各指標をグラフ化することで、コードの状態を知ることができるようになりました。
発生した問題と回避策
さて、新しいツールセットに乗り換えて良いこと尽くめだったかというと決してそうではなく、実際に運用するといくつも問題が発生しました。
いくつかの実際に起きた問題とその回避策を紹介します。
DOMが汚れて他のテストが失敗する
これまではテストごとにhtmlファイルが独立していたため、ひとつのテストが他のテストに影響を与えることはありませんでした。
ですが、Karmaのようなテストランナーを使うと、複数のテストが同じページ上にロードされて実行されることになり、他のテストを失敗させる「行儀の悪い」テストが存在することが分かりました。
行儀の悪いテストを分類すると、次のようになりました。
- 生成した要素を削除しない
- 他のテストと共有するグローバル変数の書き換え
- シングルトンクラスの状態変更
個別に修正しつつ、全体としては以下のような対策をしました。
- afterEachでbody下に要素が追加されていたらエラーにする
- beforeEachでグローバル変数をstub化する
- テスト用の状態をリセットするメソッドを追加する
TDDでひとつのファイルを変更すると全部のテストが走ってしまう
TDDでコードを書いているときは特定のクラスのテストだけ走ってほしいのですが、ひとつのファイルを変更するとすべてのテストが再実行されてしまうという問題がありました。
テストに時間がかかりリズムが悪くなってしまうので、grunt-karmaの設定で読み込むファイルを指定できるようにしました。
// Gruntfile.jsの一部 karma: { options: { files: [ grunt.option('specfile') || '<%= path.js %>/**/*_spec.js' ] } }
こうすることで、
$ grunt karma:dev --specfile hoge_spec.js
のように特定のファイルだけテスト対象にしてサクサクTDDができるようになりました。
package.jsonを変更してnpm installしてもモジュールのバージョンが上がらない
一度npm installしてからpackage.jsonのdevDependenciesのバージョンを書き換え、再度npm installを実行すると、書き換え後のバージョンのモジュールがインストールされないという現象に悩まされました。
node_modulesディレクトリを削除してインストールしなおすと問題ないため、「package.jsonが変更されていたらnode_modulesを削除してnpm installする」というシェルスクリプトを作成し、Git hooksのpost-checkoutで実行するようにしました。
シェルスクリプトはこちらがベースになっています。 teppeis/post-checkout
まとめ
最近のkintoneのJSユニットテスト事情について紹介しました。フロントエンドJavaScriptのテストも、ツールの進歩によりずいぶんやりやすくなりましたね。
近ごろではさらにpower-assertのような素晴らしいツールも登場しており、まだまだ探求が必要だと感じています。
ちなみに、現在のテスト環境に移行できたのは、先日紹介したKAIZEN DAYでテストについて探求したことがきっかけになっています。
今後も継続的に改善を重ねて、効率的にリリースしていきたいと思います!