Functional testing

We have reached a point in our sample_project Gruntfile setup where we are ready to begin testing the functionality of each registered task to ensure that we are meeting the project requirements. Recall that we have implemented the following plugins:

  • contrib-jshint
  • contrib-uglify
  • contrib-less
  • contrib-imagemin
  • notify
  • open
  • contrib-watch

Starting at the top, we will test each requirement individually, then we will begin adding in the entire stack to create our automated build process. We do have a banner option that is not a task; it is simply a property with a value that we can use elsewhere. In the case of our configuration, the banner is used by contrib-uglify. We will start with contrib-jshint testing.

The process for testing individual tasks is simple: we will only register the task we wish to test and then inspect the result of the task to ensure that what we expected to happen has indeed happened. Our custom task with a callback function that takes arguments is a handy way to test each task; we will set that up now so that we can call each task independently.

grunt.registerTask('testing', 'testing individual tasks', function(a) {
  grunt.task.run([a]);
});

As you can see, our custom register task method will take a single argument that will be the task we wish to run, then the callback will run that task for us. The terminal syntax for testing each plugin configuration will be the same, the only difference being the name of the plugin that gets passed in to the callback function. For each plugin the syntax will be as follows:

grunt testing:plugin-name

We will then inspect the resulting output; for instance, with contrib-jshint we will ensure that it runs and detects errors. For contrib-uglify we will inspect the output file contents to ensure that the banner was created correctly and that our JavaScript code was minified. We will continue through all of the plugins in this fashion.

Testing the contrib-jshint configuration

First, let's make a small change to the path of the file parameter of the jshint configuration. We want to check that our JavaScript files are lint-free and we will start by adding app.js to the files to lint. The path change will point to our app directory where app.js resides:

jshint: {
  files: ['app/app.js'],
  gruntfile: {
    options: {
       jshintrc: '.jshintrc'
    },
    src: 'Gruntfile.js'
  }
},

Let's do a preliminary run of jshint. There may be some errors and warnings that we will need to debug until jshint is lint free. Ensure you are in the correct directory location in Terminal and change directories if needed to the root of sample_project:

cd path/to/sample_project/

Then, using the syntax illustrated earlier, run the jshint task:

grunt testing:jshint

Immediately, there are errors and warnings that will need to be addressed. The following output is telling us that use strict should be wrapped inside the function instead of being used outside the function scope. In this case it is pointing to the use strict that is found at the head of our app.js file.

Here is the output:

Testing the contrib-jshint configuration

Since we know that we will not have any issues with use strict in Gruntfile.js or app.js, we can safely suppress the error by adding the .jshintrc option to the main body of the jshint configuration like this:

jshint: {
      files: ['app/app.js'],
      options: {
          jshintrc: '.jshintrc'
        },
      gruntfile: {
        options: {
          jshintrc: '.jshintrc'
        },
        src: 'Gruntfile.js'
      }
    },

The reason this will work is because, if you notice, we already provide a jshintrc option to the gruntfile property, which also has use strict at the head of the file. Looking at the .jshintrc file, which if you recall is used to define jshint options, there is a line which suppresses strict error messages:

"strict": false,

Now, run the jshint task again and check the output. Notice that the use strict error is no longer present in the lint message. We can clean up the jshint a bit now because we no longer need the gruntfile option for jshintrc, given that we added the jshintrc option to the main body of the jshint configuration.

Remove the gruntfile jshintrc option:

jshint: {
      files: ['app/app.js'],
      options: {
          jshintrc: '.jshintrc'
        },
      gruntfile: {
        src: 'Gruntfile.js'
      }
    },

The jshint class runs without errors since they are suppressed by the configuration in jshintrc.

Testing the contrib-jshint configuration

The final step is to add to the path array, any additional files that should be linted. In the case of sample_app, these are located in the app/view1 and app/view2 directories:

jshint: {
  files: ['app/app.js', 'app/view1/*.js', 'app/view2/*.js'],
  options: {
     jshintrc: '.jshintrc'
  },
  gruntfile: {
     src: 'Gruntfile.js'
  }
},

As is illustrated by the files configuration, the use of wildcards is a perfectly acceptable and efficient way to include files in the configuration.

In this case, there will be a total of five files plus the gruntfile linted that completes our functional testing of jshint:

Testing the contrib-jshint configuration

Testing the contrib-uglify configuration

For the purposes of illustrating the configuration of contrib-uglify, let's say that we need our JavaScript files in a scripts directory that the HTML files will use on the Test and Production servers. The contrib-uglify plugin will minify our development JavaScript and put it in our distribution location: dist/scripts. Then for production, the related HTML files would be modified to use the relative links to the minified JavaScript files in the dist/scripts directory. Don't worry about creating any directories; when we configure uglify to use the dist/scripts path, it will be created for us automatically if it does not already exist.

The first thing to do is modify the uglify configuration with the new directory paths, and set up dynamic naming for our generated, minified, JavaScript file:

uglify: {
   options: {
      banner: '<%= banner %>'
   },
   dist: {
      src: ['app/app.js', 'app/view1/view1.js', 'app/view2/view2.js'],
      dest: 'dist/scripts/<%= pkg.name %>.<%= pkg.version %>.min.js'
   },
},

In the dist configuration the src property is changed to an array of source files to be minified. The three sample_project JavaScript files of interest are app.js, view1.js, and view2.js. These three files will be minified and concatenated together into a file created and named dynamically using properties from package.json, via our pkg variable. Additionally, the banner we created previously will be prepended to the minified file.

Run the uglify task:

grunt testing:uglify
Testing the contrib-uglify configuration

Now have a look at the file tree to see the new destination directory and minified file that were created:

Testing the contrib-uglify configuration

Modify sample_project's index.html to point to the new minified JavaScript file and comment out the imports for the three source files:

Testing the contrib-uglify configuration

Navigate to http://localhost:8000/app/#/view1 and test the application.

Have a look at the minified JavaScript file, sample_project.0.0.1.min.js, located in the dist/scripts directory. Notice that the banner has been prepended and the three files have been minified and concatenated together:

/*! *** DO NOT EDIT THIS FILE ***
It is automatically generated in the build 
sample_project - v0.0.1 - 2015-12-29
https://myrepository.dr-int/project/sample_project
* Copyright (c) 2015 Douglas Reynolds;*/
"use strict";angular.module("myApp",["ngRoute","myApp.view1","myApp.view2","myApp.version"]).config(["$routeProvider",function(a){a.otherwise({redirectTo:"/view1"})}]),angular.module("myApp.view1",["ngRoute"]).config(["$routeProvider",function(a){a.when("/view1",{templateUrl:"view1/view1.HTML",controller:"View1Ctrl"})}]).controller("View1Ctrl",[function(){}]),angular.module("myApp.view2",["ngRoute"]).config(["$routeProvider",function(a){a.when("/view2",{templateUrl:"view2/view2.HTML", controller:"View2Ctrl"})}]).controller("View2Ctrl",[function(){}]);

Testing the contrib-less Configuration

This section assumes a little knowledge about LESS; however, the example will be very simple and I will walk through each line to explain what is going on. Even if you don't use LESS, this is a good example of the power of automated tasks.

The process for configuring and testing contrib-less will be similar to uglify. It will be necessary to modify the existing app.css by adding some LESS and renaming the file to app.less so that it will be compiled by the LESS processor. A distribution directory will be created with a style directory where the compiled CSS file will be generated. The path will be dist/styles.

To begin, rename app.css to change the file extension to .less. When complete the file will be named app.less. Next, add some LESS.

Here is the example that will be used to style the menu:

@blue: #5B83AD;

.box-shadow(@style, @a) {
  -webkit-box-shadow: @style @a;
  box-shadow:         @style @a;
}
.menu {
  @light-blue: (@blue + #111);
  color: saturate(@light-blue, 5%);
  border-color: lighten(@light-blue, 30%);
  .box-shadow(0 0 5px, @blue);
}

A quick walk-through is in order. The first line creates a variable for a blue color. The @ designates that it is a variable, blue is the variable name, and its value is the hexadecimal color #5B83AD.

Next is a mixin .box-shadow that takes arguments, much like a function. Because it behaves like a function, we could have many different callers of .box-shadow that pass in different values for arguments that will be used within their particular element style.

Finally, we have the .menu element style declaration, which creates a new color of light-blue from our base blue color. Then the color style is assigned the light-blue color within the saturate method, saturating the color by 5%. The menu border color is assigned the light-blue color and lightened, using the lighten method, by 30%. The box-shadow mixin is called and arguments passed for the box-shadow style. The style definition for box-shadow is:

box-shadow: none|h-shadow v-shadow blur spread color

In the example we pass in h-shadow of 0, v-shadow of 0, blur of 5px, and the color. Spread is optional and omitted:

.box-shadow(0 0 5px, @blue);

This line is of particular interest because it is here we are calling the mixin and passing in the arguments. The result of the mixin will become the .box-shadow style defined within the .menu style declaration.

The entire contents of app.less will now look like the following:

@blue: #5B83AD;

.box-shadow(@style, @color) {
  -webkit-box-shadow: @style @color;
  box-shadow:         @style @color;
}
.menu {
  @light-blue: (@blue + #111);
  color: saturate(@light-blue, 5%);
  border-color: lighten(@light-blue, 30%);
  .box-shadow(0 0 5px, #5B83AD);
}

.menu:before {
  content: «[«;
}

.menu:after {
  content: «]»;
}

.menu > li {
  display: inline;
}

.menu > li:before {
  content: "|";
  padding-right: 0.3em;
}

.menu > li:nth-child(1):before {
  content: "";
  padding: 0;
}

At this point, it is time to go back to Gruntfile.js and make the necessary modifications to the LESS configuration. First, we will need to set the options paths property to be the directory that contains the input file; in this case, app.less exists with the app directory. We will then need to define the dist properties for the source and destination paths. The source is located in app/app.less and the destination will be dist/styles/main.css.

The LESS configuration section will be as follows:

less: {
   options: {
      paths: ["app"]
   },
   dist: {
     src: 'app/app.less',
     dest: 'dist/styles/main.css'
   }
},

Once the LESS configuration and app.less have been modified and saved, it is time to run the task to inspect the resulting behavior and generated file:

grunt testing:less
Testing the contrib-less Configuration

If all has gone well, a new directory, named styles, will be created under the dist directory. The dist directory will be created for you if it does not already exist. Then the app.less file is compiled into main.css, which is then written to dist/styles/main.css.

Now main.css may be inspected to view the compiled result:

/* app css stylesheet */
.menu {
  color: #6794c3;
  border-color: #d6e1ed;
  -webkit-box-shadow: 0 0 5px #5B83AD;
  box-shadow: 0 0 5px #5B83AD;
}
.menu:before {
  content: «[«;
}
.menu:after {
  content: «]»;
}
.menu > li {
  display: inline;
}
.menu > li:before {
  content: "|";
  padding-right: 0.3em;
}
.menu > li:nth-child(1):before {
  content: "";
  padding: 0;
}

Modify the index.html file to use the newly generated CSS created by the LESS task. Simply comment out the existing import and add the new import as follows:

Testing the contrib-less Configuration

Now navigate to http://localhost:8000/app/#/view1 and view the style change to the menu, which now has a light blue box-shadow style:

Testing the contrib-less Configuration

The new directory structure will now look as follows:

Testing the contrib-less Configuration

Testing the contrib-imagemin configuration

The process for testing the imagemin configuration will be extremely simple. We will need to create a directory for the images that will be used in sample_project and then add some test source images into the directory. We can get the images from the SpaceX public domain image site located at: https://www.flickr.com/photos/spacexphotos. Choose any photos you like and as many as you wish to use. This example will use the following two images:

https://www.flickr.com/photos/spacexphotos/23802553412/

Testing the contrib-imagemin configuration

https://www.flickr.com/photos/spacexphotos/23604164970/

Testing the contrib-imagemin configuration

First, create a directory named images located within the app directory. The path will be sample_project/app/images. Then, download the full-size Original source files from the SpaceX photos into the newly created images directory:

Testing the contrib-imagemin configuration

During the writing of this section, a bug was found in the version of contrib-imagemin being used. It was necessary to run an update of imagemin from version 0.9.4 to 1.0.1. This is accomplished by the following process:

In the terminal, issue the following command in order to get the current versions and any available updates to existing packages in the project:

npm outdated

This will provide output similar to this example:

Testing the contrib-imagemin configuration

Then, using the version found in the Latest column of the output, update the version number used in package.json. In this case the modification was to update imagemin registration in devDependencies to 1.0.0, as follows:

"grunt-contrib-imagemin": "^1.0.0",

Finally, running the npm command to update grunt-contrib-imagemin will then get the version from package.json and update the package in the node_modules directory:

npm update grunt-contrib-imagemin

This process may be used to update any specific package manually. Now we can return to testing imagemin with our images.

Next, modify the imagemin configuration in Gruntfile.js to point to the correct path for the source image files and also the path for the distribution directory, as follows:

imagemin: {
      dynamic: {
        files: [{
          expand: true,
          cwd: 'app/images/',
          src: ['**/*.{png,jpg,gif}'],
          dest: 'dist/images/'
        }]
      }
    },

Notice that the src property configuration is a glob of file extensions that may be used for matching an expression to files in the source directory. The source directory is relative to the configuration for the Current Working Directory (cwd), which is app/images/. Then the destination directory path is defined as the location where optimized images will be written.

Save your changes and then run the following command to run the imagemin task:

grunt testing:imagemin
Testing the contrib-imagemin configuration

For these two images, imagemin reports that a total of 67.95 kB was saved in optimization. The images directory was dynamically created on the first run. Now that the task has been run, the directory tree will be as follows:

Testing the contrib-imagemin configuration

Testing the notify configuration

The notify configuration is very simple; all that is needed is to declare the tasks and define the notification title and message. The title and task messages may be any text you wish to be displayed in the notification.

For the sample_project notify configuration, the following was used:

notify: {
      jshint: {
        options: {
          title: 'Linting Complete',
          message: 'jshint finished',
        }
      },
      uglify: {
        options: {
          title: 'Minification Complete',
          message: 'JavaScript is minified'
        }
      },
      less: {
        options: {
          title: 'LESS Compiled',
          message: 'CSS is generated'
        }
      },
      imagemin: {
        options: {
          title: 'Images Minified',
          message: 'Images are minified'
        }
      },
      watch: {
        options: {
          title: 'Watch Started',
          message: 'Watch is running'
        }
      }
    },

A small modification needs to be made to our registerTask callback in Gruntfile.js: an additional parameter needs to be added to the function and that argument variable needs to be added to the array of tasks in the run method. The modification will look like this when complete:

grunt.registerTask('testing', 'testing individual tasks', function(a, b) {
    grunt.task.run([a,b]);
  });

This will now allow us to run two tasks, passing both in one Terminal command like this:

grunt testing:jshint:notify

In this example, both jshint and notify were run in a single command. jshint was passed into argument a and notify was passed into argument b. Then, jshint was run first with notify being run following the successful completion of jshint. The terminal output from this test should look like the following:

Testing the notify configuration

The notify command will create a system notification using the configured title and message for each task defined within notify. For this example, growl is being used on this system, so the notification appears on screen and looks like the following:

Testing the notify configuration

This is a really convenient feature because, when the sample_project automated build is fully configured, the build process will not need user interaction for each change. The process will launch automatically and the notifications will provide information about the task process. If there is an error, an error message will be included in the notification instead of the user-configured message:

Testing the notify configuration

Upon notification, information may be obtained from the terminal about the error. Otherwise, you, the developer, don't have to monitor the terminal for each build.

Testing the open configuration

The open command is the simplest configuration of all the tasks that are being used in sample_project. All that is needed is to define the URL to open when the task is run along with the browser application name that should open the URL:

open : {
   dev : {
      path: 'http://localhost:8000/app/index.HTML',
      app: 'Google Chrome'
   }
},

In the case of sample_project, the HTTP server runs on localhost port 8000, with the homepage located in the path /app/index.HTML. The open command is configured to open Google Chrome in this example. No changes to Gruntfile.js are necessary at this time; simply run the following command for our setup used in notify, passing in two task arguments with the second one being open:

grunt testing:jshint:open

Once run, and assuming there are no errors in the jshint task for this example, the open command will launch Google Chrome and run the current version of sample_project in the running browser.

Testing the contrib-watch Configuration

The watch is the task that brings everything together for our truly automated build. Once configured, watch will continue running after the first manual build and automatically build when any of the configured files it is watching change. watch will also run any tasks configured to run when it detects changes in files. For sample_project, watch configuration should include any files that require other tasks to be run when files have been changed. The watch configuration will be configured like this:

watch: {
   gruntfile: {
      files: 'Gruntfile.js',
      tasks: ['jshint'],
   },
   scripts: {
      files:['app/app.js', 'app/view1/*.js', 'app/view2/*.js', 'app/app.less'],
      tasks: ['default']
   }
},

In this configuration, if Gruntfile.js itself changes, then it should be linted to ensure no errors are introduced. Additionally, if any defined scripts change then the default task should be run. The default task configuration needs to be changed in order to include all of the tasks needed to run by default for sample_project. The registerTask method changes will look like the following:

grunt.registerTask('default',['jshint','uglify', 'less', 'imagemin', 'notify', 'open', 'watch']);

Running the default task only takes a single command from Terminal:

grunt

Once the default command runs for the first time, watch will continue to run and monitor for changes. The output for sample_project's first run will look like this example:

Testing the contrib-watch Configuration

Next, make a small change to Gruntfile that adds an error. Remove a semi-colon, for example. Once saved, an error notification will be presented from notify. Inspect the terminal for the error:

Testing the contrib-watch Configuration

Revert the change made to Gruntfile.js so that there will be no errors and save the file:

Testing the contrib-watch Configuration

Notice that notify did not run now that there was no error. This is because watch does not specify notify to run when only Gruntfile.js is changed. Next, we will make a change to one of the files configured in the scripts property of watch. This time it is expected that the default task will run, launching all of our configured tasks. Open up app.js and place a comment at the top of the file:

'use strict';
// comment added for testing
// Declare app level module which depends on views, and components
angular.module('myApp', [
  'ngRoute',
  'myApp.view1',
  'myApp.view2',
  'myApp.version'
]).
config(['$routeProvider', function($routeProvider) {
  $routeProvider.otherwise({redirectTo: '/view1'});
}]);

Save the change and then notice that the default task runs automatically. A notification will be presented for each task and the terminal output will provide logging for the completion of each task. watch will continue to monitor for changes and you can continue working without having to manually run any of your tasks:

Testing the contrib-watch Configuration

The Complete Gruntfile.js

Lets have a look at the code for the Gruntfile.js file:

module.exports = function(grunt) {
  'use strict';

  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! *** DO NOT EDIT THIS FILE ***
' +
    'It is automatically generated in the build 
' +
    '<%= pkg.name %> - v<%= pkg.version %> - ' +
      '<%= grunt.template.today("yyyy-mm-dd") %>
' +
      '<%= pkg.repository %>
' +
      '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;*/
',
    // Task configuration.
    jshint: {
      files: ['app/app.js', 'app/view1/*.js', 'app/view2/*.js'],
      options: {
          jshintrc: '.jshintrc'
        },
      gruntfile: {
        src: 'Gruntfile.js'
      }
    },
    uglify: {
      options: {
        banner: '<%= banner %>'
      },
      dist: {
        src: ['app/app.js', 'app/view1/view1.js', 'app/view2/view2.js'],
        dest: 'dist/scripts/<%= pkg.name %>.<%= pkg.version %>.min.js'
      },
    },
    less: {
      options: {
        paths: ["app"]
      },
      dist: {
        src: 'app/app.less',
        dest: 'dist/styles/main.css'
      }
    },
    imagemin: {
      dynamic: {
        files: [{
          expand: true,
          cwd: 'app/images/',
          src: ['**/*.{png,jpg,gif}'],
          dest: 'dist/images/'
        }]
      }
    },
    notify: {
      jshint: {
        options: {
          title: 'Linting Complete',
          message: 'jshint finished',
        }
      },
      uglify: {
        options: {
          title: 'Minification Complete',
          message: 'JavaScript is minified'
        }
      },
      less: {
        options: {
          title: 'LESS Compiled',
          message: 'CSS is generated'
        }
      },
      imagemin: {
        options: {
          title: 'Images Minified',
          message: 'Images are minified'
        }
      },
      watch: {
        options: {
          title: 'Watch Started',
          message: 'Watch is running'
        }
      }
    },
    open : {
      dev : {
        path: 'http://localhost:8000/app/index.HTML',
        app: 'Google Chrome'
      }
    },
    watch: {
      gruntfile: {
        files: 'Gruntfile.js',
        tasks: ['jshint'],
      },
      scripts: {
        files:['app/app.js', 'app/view1/*.js', 'app/view2/*.js', 'app/app.less'],
        tasks: ['default']
      }
    },
  });

  grunt.loadNpmTasks('grunt-contrib-jshint'),
  grunt.loadNpmTasks('grunt-contrib-uglify'),
  grunt.loadNpmTasks('grunt-contrib-less'),
  grunt.loadNpmTasks('grunt-contrib-imagemin'),
  grunt.loadNpmTasks('grunt-contrib-watch'),
  grunt.loadNpmTasks('grunt-notify'),
  grunt.loadNpmTasks('grunt-open'),

  grunt.registerTask('default',['jshint','uglify', 'less', 'imagemin', 'notify', 'open', 'watch']);

};
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.239.105