keitaoouchi / gulp-tdd-sample

RequireJSのモジュールをTDDで開発するための環境構築とGulpfile.coffee

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RequireJSのモジュールをTDDで開発するための環境構築とGulpfile.coffee

付帯目標

  • CoffeeScriptで書きたいので*.cofeeをウォッチして自動でコンパイルさせたい
  • Sassとかも使いたい
  • mochaをブラウザで走らせたいのでテスト用のサーバーをexpressで構築する
  • LiveReloadでテスト用のページを更新させる
  • 以上のタスクをgruntコマンド一発で立ち上げたい
  • その環境構築をnpm installを含む最低限の準備で済ませたい

構成方針

.
├── devel
│   ├── coffee
│   ├── node_modules
│   ├── sass
│   └── test
│       ├── coffee
│       └── js
└── static
    ├── css
    ├── img
    ├── js
    └── vendors

コンパイルすると devel/cofee -> static/js devel/sass -> static/css test/coffee -> test/js に自動的に配置されるようにする。

devel/node_modulsはgulpで使うモジュール、static/vendorsにはbowerでインストールする3rdパーティーのファイル。

サンプルソース

https://github.com/keitaoouchi/gulp-tdd-sample

試す

事前にbunlder、bowerの導入が必要。

npm install -g bower gulp
gem install bundler
git clone https://github.com/keitaoouchi/gulp-tdd-sample
cd gulp-tdd-sample/devel
npm install
gulp setup
gulp

そして http://localhost:8080/hoge/fuga へ。

gulpの環境を作るためのpackage.json

devel以下に配置してnpm install

{
  "name": "static",
  "version": "0.0.1",
  "description": "static files",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {
    "gulp": "3.8.8",
    "gulp-coffee": "2.2.0",
    "gulp-watch": "1.1.0",
    "gulp-compass": "2.0.0",
    "gulp-nodemon": "1.0.4",
    "gulp-notify": "2.0.0",
    "gulp-exec": "2.1.1",
    "gulp-livereload": "2.1.1",
    "gulp-plumber": "0.6.6",
    "express": "4.9.8",
    "jade": "1.7.0",
    "coffee-script": "1.8.0"
  }
}

compass/sassを導入するためのGemfile

導入はgulpにやらせるのでGemfileをdevel直下に貼るだけ

source 'http://rubygems.org'

gem 'sass'
gem 'compass'

bowerで導入する3rdパーテーのライブラリをstatic/vendorsに配置する.bowerrc

devel直下に配置

{
  "directory": "./../static/vendors"
}

bowerで導入するためのbower.jsonの一例

devel直下に配置する

{
  "name": "static",
  "version": "0.0.1",
  "ignore": [
    "**/.*",
    "node_modules",
    "components"
  ],
  "dependencies": {
    "jquery": "2.1.1",
    "backbone": "1.1.2",
    "requirejs-domready": "2.0.1",
    "requirejs": "2.1.14",
    "underscore": "1.6.0"
  },
  "devDependencies": {
    "mocha": "2.0.0",
    "chai": "1.9.2",
    "sinon": "http://sinonjs.org/releases/sinon-1.10.3.js"
  }
}

gulpfile.coffee

やはりdevel直下に配置

gulp = require 'gulp'
coffee = require 'gulp-coffee'
compass = require 'gulp-compass'
nodemon = require 'gulp-nodemon'
notify = require 'gulp-notify'
exec = require('child_process').exec
livereload = require 'gulp-livereload'
plumber = require 'gulp-plumber'
watch = require 'gulp-watch'

printout = (callback) ->
  (err, stdout, stderr) ->
    console.log stdout
    console.log stderr
    callback err

gulp.task 'setupGems', (cb) ->
  exec 'bundle install', printout(cb)

gulp.task 'setupBower', (cb) ->
  exec 'rm -rf ./../static/vendors && bower install', printout(cb)

gulp.task 'express', ->
  nodemon
    script: './test/server.js'

gulp.task 'coffee', ->
  src = './coffee/**/*.coffee'
  testSrc = './test/coffee/**/*.coffee'
  gulp.src(src)
      .pipe watch(src)
      .pipe plumber
        errorHandler: notify.onError("Error: <%= error.message %>")
      .pipe coffee
        bare: true
      .pipe gulp.dest('./../static/js')
      .pipe livereload()
  gulp.src(testSrc)
      .pipe watch(testSrc)
      .pipe plumber
        errorHandler: notify.onError("Error: <%= error.message %>")
      .pipe coffee
        bare:true
      .pipe gulp.dest('./test/js')
      .pipe livereload()

gulp.task 'compass', ->
  src = './sass/**/*.sass'
  testSrc = './test/sass/**/*.sass'
  gulp.src(src)
      .pipe watch(src)
      .pipe plumber
        errorHandler: notify.onError("Error: <%= error.message %>")
      .pipe compass
        bundle_exec:true,
        css: './../static/css'
        sass: './sass'
      .pipe livereload()
  gulp.src(testSrc)
      .pipe watch(testSrc)
      .pipe plumber
        errorHandler: notify.onError("Error: <%= error.message %>")
      .pipe compass
        bundle_exec:true
        css: './test/css'
        sass: './test/sass'
      .pipe livereload()

gulp.task 'default', ['compass', 'coffee', 'express']
gulp.task 'setup', ['setupGems', 'setupBower']

devel/test/server.js

mochaをブラウザで走らせるためのサーバーをexpressで簡単に。

var express = require('express');
var http = require('http');
var app = express();

app.set('port', process.env.PORT || 8080);
app.set('view engine', 'jade');
app.use(express.static('./../'));

app.get(/(.+)(\/.+)?/, function(req, res){
  var path = req.params[0].replace('/', '');
  res.render(__dirname + '/template', {target: path});
});

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

このサーバーでdevel/test/coffee/hoge/fuga-spec.coffeeのテスト結果をlocalhost:8080/hoge/fugaで閲覧してTDDする。livereloadしてるのでブラウザに適当な拡張入れるとリアルタイムに反映されるので嬉しい。

require.jsの設定ファイルをcoffeeで書く

require =
  baseUrl: '/static'
  paths:
    jquery: 'vendors/jquery/dist/jquery.min'
    underscore: 'vendors/underscore/underscore'
    backbone: 'vendors/backbone/backbone'
    domReady: 'vendors/requirejs-domready/domReady'
  shim:
    underscore:
      exports: '_'
    backbone:
      deps: ["jquery", "underscore"]
      exports: "Backbone"
require_test =
  paths:
    mocha: 'vendors/mocha/mocha'
    chai: 'vendors/chai/chai'
    sinon: 'vendors/sinon/index'
  shim:
    mocha:
      exports: 'mocha'
    chai:
      exports: 'chai'
    sinon:
      exports: 'sinon'

require = window.require || {}

for key, val of require_test.paths
  require.paths[key] = val
for key, val of require_test.shim
  require.shim[key] = val

テスト対象となるモジュール例

ものすごくどうでもいい感じのモジュールをRequirejsで作ります。

define(
  'js/hoge/fuga',
  ['jquery', 'underscore', 'backbone'],
  ($, _, Backbone) ->
    'use strict'

    App =
      Models: {}
      Collections: {}
      Views: {}

    class App.Models.Fuga extends Backbone.Model

      initialize: (obj) ->
        @name = obj.name

    class App.Collections.Fugas extends Backbone.Collection

      model: App.Models.Fuga

    class App.Views.FugaView extends Backbone.View

      initialize: ->
        @listenTo(@collection, 'sync', @render)
        @collection.fetch()

      render: ->

    App.initialize = ->
    if Object.freeze? then Object.freeze App else App
)

モジュールをテストするspec例

require(['js/hoge/fuga', 'backbone', 'mocha', 'chai', 'sinon'], (App, Backbone, mocha, chai, sinon) ->
  'use strict'

  mocha.ui 'bdd'

  chai.should()

  describe 'App', ->
    it 'App.Models.FugaはBackbone.Modelを継承', ->
      App.Models.Fuga.prototype.should.be.an.instanceof Backbone.Model
    it 'App.Collections.FugasはBackbone.Collectionを継承', ->
      App.Collections.Fugas.prototype.should.be.an.instanceof Backbone.Collection
    it 'App.Views.FugaViewはBackbone.Viewを継承', ->
      App.Views.FugaView.prototype.should.be.an.instanceof Backbone.View

  describe 'App.Models.Fuga', ->

    fuga = null
    before ->
      fuga = new App.Models.Fuga({name: 'fuga'})

    it 'fugaのnameがfuga', ->
      fuga.name.should.be.equals 'fuga'

  describe 'App.View.FugaView', ->

    it '初期化時にcollectionをfetchする', ->
      iamspy = new App.Collections.Fugas()
      iamspy.fetch = sinon.spy()
      new App.Views.FugaView collection: iamspy
      iamspy.fetch.called.should.be.true

  mocha.run()

)

mochaで結果を表示するためのjadeテンプレ

doctype html
html
  head
    title (^q^) < #{target} のてすと
    link(rel='stylesheet', href='/static/vendors/mocha/mocha.css')
    link(rel='stylesheet', href='/devel/test/css/style.css')

  body
    div(class='title')
      h1 (^q^) < #{target} のてすと
    div(id='mocha')
    script(src='/static/js/config.js')
    script(src='/devel/test/js/spec-config.js')
    script(src='/static/vendors/requirejs/require.js')
    script(src='/devel/test/js/#{target}-spec.js')

About

RequireJSのモジュールをTDDで開発するための環境構築とGulpfile.coffee


Languages

Language:JavaScript 47.4%Language:CoffeeScript 45.9%Language:CSS 3.5%Language:Ruby 3.2%