Subsecciones

Putting Things Together


Unit Testing: Mocha

Introducción

Mocha is a feature-rich JavaScript test framework running on node.js and the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases.

mocha init

[~/srcPLgrado/mocha-chai-browser-demo(master)]$ mocha --help

  Usage: _mocha [debug] [options] [files]

  Commands:

    init <path>            initialize a client-side mocha setup at <path>

  Options:

    -h, --help                      output usage information
    -V, --version                   output the version number
    -r, --require <name>            require the given module
    -R, --reporter <name>           specify the reporter to use
    -u, --ui <name>                 specify user-interface (bdd|tdd|exports)
    -g, --grep <pattern>            only run tests matching <pattern>
    -i, --invert                    inverts --grep matches
    -t, --timeout <ms>              set test-case timeout in milliseconds [2000]
    -s, --slow <ms>                 "slow" test threshold in milliseconds [75]
    -w, --watch                     watch files for changes
    -c, --colors                    force enabling of colors
    -C, --no-colors                 force disabling of colors
    -G, --growl                     enable growl notification support
    -d, --debug                     enable node's debugger, synonym for node --debug
    -b, --bail                      bail after first test failure
    -A, --async-only                force all tests to take a callback (async)
    -S, --sort                      sort test files
    --recursive                     include sub directories
    --debug-brk                     enable node's debugger breaking on the first line
    --globals <names>               allow the given comma-delimited global [names]
    --check-leaks                   check for global variable leaks
    --interfaces                    display available interfaces
    --reporters                     display available reporters
    --compilers <ext>:<module>,...  use the given module(s) to compile files
    --inline-diffs                  display actual/expected differences inline within each string
    --no-exit                       require a clean shutdown of the event loop: mocha will not call process.exit

[~/srcPLgrado]$ mocha init chuchu
[~/srcPLgrado]$ ls -ltr
total 16
....
drwxr-xr-x   6 casiano  staff  204 20 ene 11:16 chuchu
[~/srcPLgrado]$ tree chuchu/
chuchu/
|-- index.html
|-- mocha.css
|-- mocha.js
`-- tests.js

[~/srcPLgrado/mocha-tutorial]$ cat test/test.js 
var assert = require("assert")
describe('Array', function(){
  describe('#indexOf()', function(){
    it('should return -1 when the value is not present', function(){
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
      assert.equal( 0, [1,2,3].indexOf(99));
    })
  })
})

[~/srcPLgrado/mocha-tutorial]$ mocha
  .
  0 passing (5ms)
  1 failing

  1) Array #indexOf() should return -1 when the value is not present:
     AssertionError: 0 == -1
      at Context.<anonymous> (/Users/casiano/local/src/javascript/PLgrado/mocha-tutorial/test/test.js:7:14)

Mocha allows you to use any assertion library you want, if it throws an error, it will work! This means you can utilize libraries such as should.js, node's regular assert module, or others.

Browser support

Mocha runs in the browser.

A typical setup might look something like the following, where we call mocha.setup('bdd') to use the BDD interface before loading the test scripts, running them onload with mocha.run().

<html>
<head>
  <meta charset="utf-8">
  <title>Mocha Tests</title>
  <link rel="stylesheet" href="mocha.css" />
</head>
<body>
  <div id="mocha"></div>
  <script src="jquery.js"></script>
  <script src="expect.js"></script>
  <script src="mocha.js"></script>

  <script>mocha.setup('bdd')</script>

  <script src="test.array.js"></script>
  <script src="test.object.js"></script>
  <script src="test.xhr.js"></script>

  <script>
    mocha.checkLeaks();
    mocha.globals(['jQuery']);
    mocha.run();
  </script>

</body>
</html>

TDD

The Mocha TDD interface provides suite(), test(), setup(), and teardown().

suite('Array', function(){
  setup(function(){
    // ...
  });

  suite('#indexOf()', function(){
    test('should return -1 when not present', function(){
      assert.equal(-1, [1,2,3].indexOf(4));
    });
  });
});

Véase

Grunt

http://gruntjs.com/getting-started

npm install -g grunt-cli

A typical setup will involve adding two files to your project: package.json and the Gruntfile.

package.json

Gruntfile

The Gruntfile.js or Gruntfile.coffee file is a valid JavaScript or CoffeeScript file that belongs in the root directory of your project, next to the package.json file, and should be committed with your project source.

A Gruntfile is comprised of the following parts:

An example Gruntfile

In the following Gruntfile, project metadata is imported into the Grunt config from the project's package.json file and the

grunt-contrib-uglify

plugin's uglify task is configured to minify a source file and generate a banner comment dynamically using that metadata.

When grunt is run on the command line, the uglify task will be run by default.

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'src/<%= pkg.name %>.js',
        dest: 'build/<%= pkg.name %>.min.js'
      }
    }
  });

  // Load the plugin that provides the "uglify" task.
  grunt.loadNpmTasks('grunt-contrib-uglify');

  // Default task(s).
  grunt.registerTask('default', ['uglify']);

};
Now that you've seen the whole Gruntfile, let's look at its component parts.

The "wrapper" function

Every Gruntfile (and gruntplugin) uses this basic format, and all of your Grunt code must be specified inside this function:

module.exports = function(grunt) {
  // Do grunt-related things in here
};

Project and task configuration

Most Grunt tasks rely on configuration data defined in an object passed to the grunt.initConfig method.

In this example, grunt.file.readJSON('package.json') imports the JSON metadata stored in package.json into the grunt config. Because <% %> template strings may reference any config properties, configuration data like filepaths and file lists may be specified this way to reduce repetition.

You may store any arbitrary data inside of the configuration object, and as long as it doesn't conflict with properties your tasks require, it will be otherwise ignored. Also, because this is JavaScript, you're not limited to JSON; you may use any valid JS here. You can even programmatically generate the configuration if necessary.

Like most tasks, the grunt-contrib-uglify plugin's uglify task expects its configuration to be specified in a property of the same name. Here, the banner option is specified, along with a single uglify target named build that minifies a single source file to a single destination file.

// Project configuration.
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  uglify: {
    options: {
      banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    build: {
      src: 'src/<%= pkg.name %>.js',
      dest: 'build/<%= pkg.name %>.min.js'
    }
  }
});

A simple Grunt.js example

https://github.com/UWMadisonUcomm/grunt-simple-example

[~/srcPLgrado/grunt-simple-example(master)]$ pwd
/Users/casiano/srcPLgrado/grunt-simple-example
[~/srcPLgrado/grunt-simple-example(master)]$ git remote -v
origin  git@github.com:UWMadisonUcomm/grunt-simple-example.git (fetch)
origin  git@github.com:UWMadisonUcomm/grunt-simple-example.git (push)
[~/srcPLgrado/grunt-simple-example(master)]$ ls
Gruntfile.js Readme.md    assets       index.html   node_modules package.json src

[~/srcPLgrado/grunt-simple-example(master)]$ cat Gruntfile.js 
module.exports = function(grunt){
  grunt.initConfig({
    uglify: {
      main: {
        files: {
          'assets/app.min.js': [
            'src/javascripts/jquery-1.10.2.min.js',
            'src/javascripts/bootstrap.js',
            'src/javascripts/application.js'
          ]
        }
      }
    },
    less: {
      application: {
        options: {
          yuicompress: true
        },
        files: {
          "assets/app.min.css": "src/stylesheets/application.less"
        }
      }
    },
    watch: {
      javascripts: {
        files: ['src/javascripts/**/*'],
        tasks: ['uglify']
      },
      stylesheets: {
        files: ['src/stylesheets/**/*'],
        tasks: ['less']
      }
    }
  });

  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Register tasks
  grunt.registerTask('default', ['uglify', 'less']);
}

[~/srcPLgrado/grunt-simple-example(master)]$ cat package.json 
{
  "name": "grunt-simple-example",
  "version": "0.0.1",
  "main": "index.js",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-cssmin": "~0.6.2",
    "grunt-contrib-less": "~0.7.0",
    "grunt-contrib-uglify": "~0.2.4",
    "grunt-contrib-watch": "~0.5.3"
  },
  "author": "Bryan Shelton",
  "license": "BSD-2-Clause"
}

[~/srcPLgrado/grunt-simple-example(master)]$ npm install
npm WARN package.json grunt-simple-example@0.0.1 No repository field.
[~/srcPLgrado/grunt-simple-example(master)]$

[~/srcPLgrado/grunt-simple-example(master)]$ grunt watch
Running "watch" task
Waiting...OK
>> File "src/javascripts/application.js" changed.

Running "uglify:main" (uglify) task
File "assets/app.min.js" created.

Done, without errors.
Completed in 3.897s at Mon Jan 20 2014 19:02:03 GMT+0000 (WET) - Waiting...


GitHub Project Pages

Project Pages are kept in the same repository as the project they are for.

These pages are similar to User and Org Pages, with a few slight differences:

  1. Setting up Pages on a project requires a new "orphan" branch in your repository. The safest way to do this is to start with a fresh clone.

    git clone https://github.com/user/repository.git
    # Clone our repository
    # Cloning into 'repository'...
    remote: Counting objects: 2791, done.
    remote: Compressing objects: 100% (1225/1225), done.
    remote: Total 2791 (delta 1722), reused 2513 (delta 1493)
    Receiving objects: 100% (2791/2791), 3.77 MiB | 969 KiB/s, done.
    Resolving deltas: 100% (1722/1722), done.
    
  2. Now that we have a clean repository, we need to create the new branch and remove all content from the working directory and index.

    cd repository
    
    git checkout --orphan gh-pages
    # Creates our branch, without any parents (it's an orphan!)
    # Switched to a new branch 'gh-pages'
    
    git rm -rf .
    # Remove all files from the old working tree
    # rm '.gitignore'
    
  3. Now we have an empty working directory. We can create some content in this branch and push it to GitHub. For example:
    echo "My GitHub Page" > index.html
    git add index.html
    git commit -a -m "First pages commit"
    git push origin gh-pages
    

Casiano Rodríguez León
2016-03-27