Practices based on the Grunt & amp; mocha JavaScript automated testing framework

Source: Internet
Author: User

Significance of Automated Testing

Build the CI version based on Grunt. Real-time Monitoring of Project health, the most extreme situation is to rely on CI to ensure that versions can be released at any time. Therefore, the test must be able to be integrated by Grunt and report can be output. After the build is complete, send the Group to all project members.

The test code should be layered, that is, the test cases and framework should be separated and cannot be coupled. The skill of the test case writer is not too high, and the framework architecture is not required.

Grunt Introduction

Grunt is a task-based JavaScript project command line build tool that runs on the Node. js platform. Grunt can quickly create projects from templates, merge, compress and validate CSS & JS files, run unit tests, and start static servers (these functions depend on grunt plug-ins, which are installed through npm, and managed by Github ).

The Grunt and Grunt plug-ins are installed and managed through npm. Installation command:

npm install gruntnpm install grunt-cli

Each time grunt is run, it uses the require () system provided by node to find locally installed Grunt. Thanks to this mechanism, you can run grunt in any subdirectory of the project. If you find a locally installed Grunt, CLI will load it, pass the configuration information in Gruntfile, and then execute the task you specified.

Mocha Introduction

Mocha is a node. the most commonly used test framework in js, supporting multiple node assert libs (shocould, chai, Except CT, better-assert, etc.), and exporting results in multiple ways, you can also run JavaScript code tests directly on your browser.

Mocha Installation
npm install  mocha
Introduction to Mocha and related plug-ins
TDD & BDD basic definition

TDD (Test Driven Development) focuses on whether the interface is correctly implemented. Behavior Driven Development (BDD) usually focuses on whether a class implements the Behavior defined in the document. The default Mocha execution method is BDD.

Mocha's implementation of TDD & BDD

The sample code of BDD is as follows:

// Synchronous mode 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);}); describe ('# join ()', function () {it ('could return-1 when the value is not present ', function () {assert. equal ('1, 2, 3 ', [1, 2, 3]. join (',') ;})}); // asynchronous mode fs = require ('fs'); describe ('file', function () {describe ('# readFile ()', function () {it ('could read test. ls without error ', function (done) {fs. readFile ('test. ls ', function (err) {if (err) throw err; done ();});})})})

The key API of BDD is describe and nesting is allowed. The first layer describe specifies the test class, and the second layer specifies the test Member method. Each subscribe from the second layer can be considered as a statement in the test method corresponding to the first layer of subscribe. This is also in line with the concept of BDD in class. In addition, BDD provides before, after, beforeEach, and afterEach similar to setup in TDD. teardown is not described here.

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

How to integrate Mocha to Grunt
Install grunt-mocha-test The command is as follows:
npm install grunt-mocha-test

The Gruntfile code is as follows, which is relatively simple and not described here.

For options for mochaTest, see https://github.com/pghalliday/grunt-mocha-test
    mochaTest: {      tdd_test: {        options: {          ui: 'tdd',          reporter: 'spec'                 },        src: ['<%= pkg.name%>/tdd_api_testcase/prepare_browser.js','<%= pkg.name%>/tdd_api_testcase/test_*.js']      },            bdd_test: {        options: {                    reporter: 'spec',          output: '<%= pkg.name%>/bdd_api_testcase/result.txt',          require: 'blanket'        },        src: ['<%= pkg.name%>/bdd_api_testcase/*.js']      },      coverage: {        options: {          reporter: 'html-cov',          // use the quiet flag to suppress the mocha console output          quiet: true,          // specify a destination file to capture the mocha          // output (the quiet option does not suppress this)          captureFile: '<%= pkg.name%>/coverage.html'        },        src: ['<%= pkg.name%>/tdd_api_testcase/*.js']      },      testReporter: {        options: {          output: 'frame_test/result.txt'             }      }       },

Test AMD module and browser Environment Simulation

To test the project code on node, you need to solve the following problems:

A. Node module loaders are CMD specifications, but the project module loaders are AMD specifications. You need to define a normalized require.

B. Re-specify the module loading path in copnfig of requirejs.

C. Because Javascript is executed locally, it is necessary to modify the absolute path used in the project.

D. You need to introduce jsdom to simulate the browser environment so that the browser-related operations in the Code do not go wrong.

E. Because the simulation of jsdom is asynchronous, the source code adjustment of grunt is involved.

Each time a node loads a file, it overwrites require (see node source code module. js). Therefore, you need to execute the following code before executing the test case.

var assert = require('assert');var requirejs = require('requirejs');var c_require = require;requirejs.config({    //Pass the top-level main.js/index.js require    //function to requirejs so that node modules    //are loaded relative to the top-level JS file.    baseUrl: 'webapp/1.1/web/',    nodeRequire: require});require = function (path, callback) {    if (arguments.length == 2) {        return requirejs.call(global, path, callback);    }    else {        return c_require.call(global, path);    }}

The Processing Method of the test project is to separate the above Code into an independent JS file (case_header.js), and add the following code to GruntFile. js:

// Use Case header file load var _ compile = module. _ proto __. _ compile; module. _ proto __. _ compile = function (content, filename) {if (filename. indexOf ('tdd _ api_testcase ')>-1 | filename. indexOf ('bdd _ api_testcase ')>-1) {content = grunt. file. read ('case _ header. js') + content;} _ compile. call (this, content, filename );};

The solution for D and E is to let Grunt first execute the test case to complete the window simulation of Jsdom. The test file of the corresponding TDD mode is

suite('PrepareBrowser', function () {    console.log("Prepare browser enviroment");    setup(function (done) {        var jsdom = require('jsdom');        jsdom.env({            html: "",            documentRoot: '<%= pkg.name%>/../../../',            scripts: [],            done: function (errors, window) {                global.window = window;                for (var p in window) {                    if (!global[p]) {                        global[p] = window[p];                    }                }                window.localStorage = {};                window.localStorage.get = function () {                    return this;                }                window.localStorage.getItem = function (name) {                    return this[name];                }                window.localStorage.set = function (data) {                    this = data;                }                window.localStorage.setItem = function (name, value) {                    this[name] = value;                }                done();            }        });    });    suite('PrepareBrowser', function () {        test('PrepareBrowser', function () {            if (!window)                throw new Error("failed!");        });    });});

It should be emphasized that Jsdom only ensures that the BOM and DOM objects of the browser can be accessed in the node environment. This and headless testing are two concepts. The basic principle of Jsdom is to simulate browser objects by loading a page (which can be a remote http address, a local file, or a piece of HTML) and a js file. During the life cycle of the MochaTest plug-in, the HTML environment will not change. The so-called headless testing refers to testing html and testing html can be correctly executed in the browser, but the browser does not need to be started during testing. Therefore, it is not a good solution to perform a headless test using Jsdom.

TDD or BDD

The following is the TDD and BDD code framework to complete the same test.

Suite ('testinherit', function () {var core; setup (function (done) {// load the module in Setup, one set of require (['core/c. core. inherit '], function (module) {core = module; done () ;}); suite ('test _ inherit_1', function () {test ('test _ inherit_1 ', function () {// test code}); test ('test _ inherit_2', function () {// test code });});});
Describe ('testin', function (done) {console. log ("Inherit bdd test suite fired! "); It ('test _ inherit_1 ', function (done) {require (['core/c. core. inherit '], function (core) {// test code done () ;}); it ('test _ inherit_2', function (done) {require (['core/c. core. inherit '], function (core) {// test code done ();});});});

From the above, we can see that the module loading in tdd can be completed in setup only once. In bdd, the module must be reloaded in each test case. To simplify the test code and improve the execution efficiency of test cases, I personally prefer TDD.
Introduction to Jscoverage

The principle of jscoverage is to insert a checkpoint for each Logic Path of the test code. When the code runs to that path, 1 is added to the counter, and the code that runs multiple times is accumulated. The Open Source protocol of Jscoverage is GPL, which must be evaluated before use.

Preparations:

L download the test coverage tool jscoverage.

Here, http://siliconforks.com/jscoverage/download.htmlselect A windows#下, and place it in the pathvariable of the operating system.

Install the grunt-jscoverage plugin
npm install grunt- jscoverage

Modify GruntFile configurations as follows:
    jscoverage: {      options: {        inputDirectory: 'webapp',        outputDirectory: 'webapp' + '_cov',        encoding: 'utf-8'      }    },    coverage: {        options: {          reporter: 'html-cov',          // use the quiet flag to suppress the mocha console output          quiet: true,          // specify a destination file to capture the mocha          // output (the quiet option does not suppress this)          captureFile: 'frame_test/coverage.html'        },        src: ['<%= pkg.name%>/tdd_api_testcase/*.js']      }       }
Test Case debugging

Install the plug-in node-inspector. For more information, see https://github.com/node-inspector/node-inspector.

After the installation is complete, open the Operating System Command window to execute
node-inspector &

Do not close this command window

Open http: // 127.0.0.1: 8080/debug in chrome? Port = 5858

Under the grunt Project (note that it is important to execute the test case (the red font part is the debugging parameter) because the module loads the configured relative path)

The command line for executing the API use case is as follows:

Node -- debug-brk node_modules \ grunt-cli \ bin \ grunt

The refresh address is http: // 127.0.0.1: 8080/debug? In the browser window with port = 5858, the code is loaded into the browser Debugger for debugging.

Grunt File
In addition to defining basic tasks, the GruntFile. js file also needs to add the following code to process test report generation and case file header file loading.
// Test report var hooker = require ('hooker'); if (gruntConfig. mochaTest. testReporter & gruntConfig. mochaTest. testReporter. options. output) {var output = gruntConfig. mochaTest. testReporter. options. output; var fs = require ('fs'); fs. open (output, "w"); hooker. hook (process. stdout, 'write', {pre: function (result) {fs. open (output, "a", 420, function (e, fd) {if (e) throw e; fs. write (fd, result, 0, 'utf8', function (e) {if (e) throw e; fs. closeSync (fd) ;}}) ;}}; // Use Case header file loading var _ compile = module. _ proto __. _ compile; module. _ proto __. _ compile = function (content, filename) {if (filename. indexOf ('tdd _ api_testcase ')>-1 | filename. indexOf ('bdd _ api_testcase ')>-1) {content = grunt. file. read ('case _ header. js') + content;} _ compile. call (this, content, filename );};

Environment preparation

Currently, relevant executable programs are installed. They are

Python 2.7.6 (jsdom dependency)

Jscoverage0.5.1 (grunt-jscoverage dependency)

And add their paths to system environment variables.

Maintain the following dependent plug-ins in Package. json:

"devDependencies": {    "grunt": "^0.4.4",    "grunt-cli": "^0.1.13",    "jsdom": "^0.10.3",    "requirejs": "^2.1.11",    "grunt-jscoverage": "0.0.3",    "grunt-mocha-test": "^0.10.0",        "hooker": "^0.2.3",    "blanket": "^1.1.6"  }
Compared with the previous blog, the biggest improvement is to avoid modifying grunt. At the same time, some environment preparation work is a separate step. Further clarify the code hierarchy









Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.