Hubot スクリプトのテストやその環境を知ろう

これは [Hubot Advent Calendar 2014][hubot-adventar-2014] の 22 日目の記事です。

また、今回は @bouzuya の Hubot 連載の第 14 回です。[目次は、第 1 回の記事にある][hubot-adventar-2014-bouzuya-index]ので、そちらをどうぞ。

# 前回まで、そして今回は

前回は [いまさらだけど Hubot のドキュメントを読んでみよう][hubot-adventar-2014-21] ということで、Hubot のドキュメントについて紹介しました。

今回は Hubot スクリプトのテストについて紹介します。とても軽く触れるだけですが。

変にバズってぼくのリポジトリで最も多くスターがついてしまった [bouzuya/hubot-twada](https://github.com/bouzuya/hubot-twada) ライクな Hubot スクリプトをつくりましょう。

ちなみに今回から `generator-hubot` は `0.2.0` にバージョンアップしています。連載中にどんどんバージョンアップされますね。

# とりあえず `grunt test`

いつものように `yo hubot:script` しましょう。今回は `hubot-t_wada` をつくります。

```bash
$ mkdir hubot-t_wada
$ cd hubot-t_wada
$ yo hubot:script
...
```
とりあえず `grunt test` してみてください。たぶん動きません。

## grunt が見つからない

```bash
$ grunt
zsh: command not found: grunt
```

もし `grunt` が見つからないなら `grunt-cli` を global にインストールしてください。

```bash
$ # 必要なら `sudo` をつけてください
$ npm install -g grunt-cli
```

## coffe-script が見つからない

```bash
$ grunt test
Running "mochaTest:test" (mochaTest) task
Warning: Cannot find module 'coffee-script' Use --force to continue.

Aborted due to warnings.
```

もし `coffee-script` が見つからないなら `coffee-script` をインストールしてください。

`generator-hubot` は、`coffee-script` が global にインストールされている前提になっています。個人的には `coffee-script` を global にインストールはしないので、[ PR を投げたのですが、無視されています](https://github.com/github/generator-hubot/pull/11) ので、テストコマンドは動きません。

おとなしく `devDependencies` に `coffee-script` をインストールしましょう。global にインストールしたいならお好きにどうぞ。

また、余計なトラブルを防ぎたいなら `hubot` が依存している `coffee-script` にバージョンを合わせる (`1.6.3`) のが吉です。

```bash
$ npm install --save-dev coffee-script@1.6.3
```

## 今度こそ `grunt test`

これでようやくテストが動くはずです。

```bash
$ grunt test
Running "mochaTest:test" (mochaTest) task


  t-wada
    ✓ registers a respond listener 
    ✓ registers a hear listener 


  2 passing (9ms)


Done, without errors.
```

# `grunt test:watch`

じゃあ、書いていきましょう。

書き換えごとに `grunt test` するのも面倒なので、`grunt test:watch` してファイルの変更を監視しましょう。

```bash
$ grunt test:watch
```

ファイルを変更(保存)するごとに `grunt test` が動きます。

サンプルで書いてあるテストは「これインタフェースじゃなく実装をテストしてねえか？」って感じで、ぼくにコードレビューを投げてきたらリジェクトしたいんですけど、せっかく generator-hubot が提示してくれているのでその流儀に従います。

```coffeescript:test/t-wada-test.coffee
chai = require 'chai'
sinon = require 'sinon'
chai.use require 'sinon-chai'

expect = chai.expect

describe 't-wada', ->
  beforeEach ->
    @robot =
      respond: sinon.spy()
    @msg =
      send: sinon.spy()

    require('../src/t-wada')(@robot)
    @robot.respond.firstCall.args[1](@msg)

  it 'registers a respond listener', ->
    expect(@robot.respond).to.have.been.calledWith(/テスト書いてない/)

  it 'sends a message', ->
    expect(@msg.send).to.have.been.calledWith(
      'テスト書いてないとかお前それ @t_wada の前でも同じこと言えんの？')
```

こんな感じになりました。正直、解説が面倒です。

Node.js におけるテストでおなじみの `mocha` と `chai` と `sinon.js` (+`sinon-chai`) の API を理解していればなんてことはないですね。mocha はテストフレームワーク、chai はアサーションライブラリ、 sinon.js はテストダブルライブラリです。

- mocha http://mochajs.org/
- chai http://chaijs.com/
- sinon.js http://sinonjs.org/

どれも Node.js 的にはかなりメジャーなライブラリなので情報そのへんにあるでしょう (適当) 。

# `generator-hubot` が生成する環境

まとめると `generator-hubot` が生成する環境は

- grunt
- mocha
- chai
- sinon

な環境です。

# 実は Hubot のテストに標準的な方法はない

以下、私見です。

まず Hubot のテストに標準的な方法はないと言って良いと思います。

ぼく自身もいろいろ模索していて、過去にいろいろ書いています。軽く経緯に触れます。

## hubot-mock-adapter

まずは [blalor/hubot-mock-adapter](https://github.com/blalor/hubot-mock-adapter) です。

これは mock 用のアダプターです。e2e テストに近いので、良いのですが準備が相当面倒です。

## kakashi

個人的に [bouzuya/kakashi](https://github.com/bouzuya/kakashi) という hubot-mock-adapter のラッパーを書いたこともありました。仕様が複雑なので、使わない方が良いです。

ちなみに [bouzuya/hubot-url](https://github.com/bouzuya/hubot-url) などで使っているようですね。

## regex / callback

上記のような e2e テストは実際はやりづらいです。例えば Hubot のメッセージが処理されるタイミングは非同期な上に完了したかどうかも取れません。テストが失敗するときはテストフレームワークのタイムアウトまで待たされます。タイムアウトを短くするなどしてもいいでしょう。ただ、もうぼくは諦めています。

ぼくはテストを書くときは、登録済みの listener をバラして、`regex` と `callback` をテストするスタイルを取っています。

ちなみに [bouzuya/hubot-twada](https://github.com/bouzuya/hubot-twada/) などで使っているようですね。

## 現状の @bouzuya 環境

現状の @bouzuya 環境を確認したいなら [bouzuya/hubot-script-boilerplate](https://github.com/bouzuya/hubot-script-boilerplate) を見ると良いです。

一覧にするとこんな感じ。

- gulp
- mocha
- power-assert
- sinon
- istanbul
- coffee-script

すこしテスト時のビルドが複雑です。また本家の CoffeeScript のバージョンがいつまでも 1.6.3 なのが気に入らないので、もうコンパイル済みの JavaScript をコミットしています。

## 理想の環境をつくるなら

上記のものに近い、理想的な環境を手っ取り早く作りたいなら、[sanemat/generator-hubot-script-gulp](https://github.com/sanemat/generator-hubot-script-gulp) は素晴らしい選択肢です。ぼくの知る限り、Hubot スクリプト向けの Yeoman ジェネレーターではもっとも優れています。

# まとめ

ざっと Hubot スクリプトのテストやその環境について触れてみました。

今回のサンプルは [bouzuya/hubot-t_wada](https://github.com/bouzuya/hubot-t_wada) にあります。参考にどうぞ。

# 最後に

正直に言うなら Hubot のテストなんて適当で良いと思います。3 行のスクリプトに山ほどテストを書いて何が楽しいんだと。何のためにテストするのか、本当に必要なのかを考えるべきだと思います。きちんと、コストやリターンなどを考えてテストすべきです。 Hubot スクリプトに限らず。


[getting-started-with-hubot]: https://github.com/github/hubot/tree/v2.9.3/docs#getting-started-with-hubot
[bouzuya/hubot-hello-world]: https://github.com/bouzuya/hubot-hello-world/
[bouzuya/hubot-choice]: https://github.com/bouzuya/hubot-choice/
[bouzuya/hubot-adventar]: https://github.com/bouzuya/hubot-adventar/
[bouzuya/hubot-homepage]: https://github.com/bouzuya/hubot-homepage/
[bouzuya/hubot-script-gist]: https://github.com/bouzuya/hubot-script-gist/
[bouzuya/hubot-brainfxxk]: https://github.com/bouzuya/hubot-brainfxxk/
[bouzuya/hubot-7days-later]: https://github.com/bouzuya/hubot-7days-later/
[bouzuya/hubot-file-brain]: https://github.com/bouzuya/hubot-file-brain/
[hubot-scripts/hubot-redis-brain]: https://github.com/hubot-scripts/hubot-redis-brain
[hubot-script-per-day-61]: http://bouzuya.hatenablog.com/entry/2014/09/12/224215
[github/hubot]: https://github.com/github/hubot/
[github/generator-hubot]: https://github.com/github/generator-hubot/
[ops-for-everyone]: https://speakerdeck.com/johndbritton/ops-for-everyone
[adventar]: http://www.adventar.org/
[express-reference]: http://expressjs.com/3x/api.html
[hubot.github.com]: https://hubot.github.com/
[hubot-adventar-2014]: http://www.adventar.org/calendars/384
[hubot-adventar-2014-bouzuya-index]: http://qiita.com/bouzuya/items/c7d0ad80c357aab6b696
[hubot-adventar-2014-1]: http://qiita.com/bouzuya/items/c7d0ad80c357aab6b696
[hubot-adventar-2014-2]: http://qiita.com/bouzuya/items/11c0c6da2b3ad54b827f
[hubot-adventar-2014-3]: http://qiita.com/bouzuya/items/2a200c9e8a45e2478bc2
[hubot-adventar-2014-4]: http://qiita.com/bouzuya/items/4c0206d72ff22ade9339
[hubot-adventar-2014-5]: http://pasero.net/~mako/blog/s/685
[hubot-adventar-2014-6]: http://qiita.com/bouzuya/items/4e051f68f8d68a2944b0
[hubot-adventar-2014-7]: http://qiita.com/bouzuya/items/2e935c99647b93d8ee1d
[hubot-adventar-2014-8]: http://udzura.hatenablog.jp/entry/2014/12/08/182744
[hubot-adventar-2014-9]: http://qiita.com/bouzuya/items/587c4e920151342e6f1d
[hubot-adventar-2014-10]: http://qiita.com/bouzuya/items/8e8ee8e2e8f83513ad35
[hubot-adventar-2014-11]: http://qiita.com/bouzuya/items/3e239bc530e7c2c3a26c
[hubot-adventar-2014-12]: http://qiita.com/hiconyan/items/3e5481a61657890624dc
[hubot-adventar-2014-13]: http://pasero.net/~mako/blog/s/689
[hubot-adventar-2014-16]: http://qiita.com/bouzuya/items/d65a394cac9e76b56d3d
[hubot-adventar-2014-17]: http://qiita.com/hiconyan/items/baf6ac56129a26d8ac0c
[hubot-adventar-2014-18]: http://qiita.com/bouzuya/items/d5fd0ef9a8cb100933f4
[hubot-adventar-2014-19]: http://qiita.com/bouzuya/items/ae1cb5610a64794bc522
[hubot-adventar-2014-20]: http://bochibochibocchi0095.hatenablog.com/entry/2014/12/21/000710
[hubot-adventar-2014-21]: http://qiita.com/bouzuya/items/ca69548c813e87c2845a