Since we launched the private beta of Tenon.io, the feedback has been really positive and, frankly, energizing. But we have more work to do before we’re ready to open the whole thing up for the public. Much of that work centers around tests. We need more tests. Right now, we have a backlog of about 65 tests to write. Some of those tests require additional utility methods so we can keep things DRY. As I was writing one such method, I thought it might be a good topic for an intro to what I call modern web development techniques. I covered this in my recent presentation at Open Webcamp, titled The new hotness: How we use Node, Phantom, Grunt, Bower, Chai, Wercker, and more to build and deploy the next generation of accessibility testing tools. (an obnoxiously long title, I know).
In this tutorial I’m going to go over the basics of starting a project, scaffolding out a project, and give a very quick intro to Test Driven Development. There are a ton of details, nuances, and considerations that I’m going to mostly gloss over, because this tutorial touches on a lot of things and each of them are worthy of multiple blog posts in and of themselves. There are a ton of links throughout the content of this post where you can find a lot more info on these various topics and I really encourage you to explore them.
The general principle of Test Driven Development is this: If you know your requirements, you know the acceptance criteria. Write tests (first) which test whether you’ve met the acceptance criteria. Write the code that passes the test. This approach has multiple benefits, especially when it comes to quality. If you write good tests and you’re passing those tests, then you’re avoiding bugs. Also, as new code is added, if the new code passes its own tests but causes prior tests to fail, then you avoid the new bugs as well. This assumes, of course, that you’re writing good tests. At Tenon, we’ve seen our own bugs arise from tests that didn’t take into consideration some edge case scenarios. In my opinion, this demonstrates the best part about TDD, because all we needed to do was add a new test fixture that matched the failed case, modify the code, checked to make sure we passed the test, and the bug was squashed.
Some background preparation
In this tutorial I’m only making a really tiny jQuery plugin, but we’re going to pretend it is an actual project.
Every single project I embark on has a local development environment and its own repository for version control. Over many years, I’ve learned the hard way that I can’t have a single dev server for everything I do and version control is critical. This is because chances are pretty high that I’ll eventually need to re-use, refactor, or expand on something, even if I consider it purely experimental at the time.
So, the first step for me is always to create the project and set up the version control. I use Git for version control and I use Bitbucket to host the repositories. I type these items in Terminal to get everything started:
mkdir /path/to/your/project cd /path/to/your/project git init git remote add origin email@example.com:karlgroves/jquery-area.git
So, for the newbs: I’ve made the folder to hold the project using
mkdir, I went to it using
cd, I initialized the repository using
git init and then I added the remote location using
git remote add origin. The next step I often take is to set up the new host in MAMP but in this case I don’t need to since it is just a small jQuery plugin being written.
Every bit of code discussed in this tutorial can be found on Bitbucket at https://bitbucket.org/karlgroves/jquery-area. To download & use that code to follow along, do this:
mkdir /path/to/your/project cd /path/to/your/project git clone firstname.lastname@example.org:karlgroves/jquery-area.git
Every feature must be driven by a need
I’m a very strong proponent of Agile software development processes and a very strong believer in requirements driven by a user-oriented need, often referred to as a User Story. Good user stories follow the INVEST pattern. Once a User Story has been defined, it is broken down into the distinct tasks that need to be performed to complete the story. For most user stories, there are likely to be multiple tasks. For this tutorial our user story is simple:
As a test developer, I want to be able to create tests which check for an actionable object’s dimensions.
Given the above, we then need to determine what tasks must be performed in order to complete the story. Since we’re testing for an actionable object’s final dimensions – and because we use jQuery – we want to test the values returned for
.innerWidth(). This is because border and margin aren’t part of the “hit area” for actionable items. We also want to determine the overall area of the object. So our task in this case is pretty simple:
Create a jQuery plugin that will calculate an object’s dimensions
We determined this to be a story with a single task because it only requires that. But we also determined that, down the road, we may need more than just actionable objects, so we’ll let it be used for any object. In reality this plugin will only work for HTML elements that can take up space. Some elements like
<br> don’t take up any space, but we won’t be using this for them.
Set up the project
In reality, this sort of simple plugin doesn’t require its own project, but go with me here.
The first step, after creating a local folder and setting up the Git repository, is to “scaffold” out the project or, get the basic structure in place. One of the best ways out there for this is to use Yeoman. Depending on the nature of your project, there may already be an official Yeoman Generator for your type of project. In fact, Sindre Sohrus has already created one for jQuery plugins. No matter your approach, it makes sense to start out with a basic structure for your project.
I didn’t use the Yeoman generator for this project, mostly because I have my own set of preferences. The best approach, if I planned on making a habit of making jQuery plugins, would be to fork the Yeoman Generator and use it as a basis for my own. Either way, here’s how my structure winds up:
- src – this is where the source file(s) go. For instance, in a big project involving multiple files, there may be several files which get concatenated or compiled (or both) later
- dist – this is the final location of the files to be used later. For instance, a project like Bootstrap may have several files in ‘src’ which get concatenated and minified for distribution here in the ‘dist’ folder
- test – this is where the unit test files go
- Files in the project root. This holds many of the project related files such as configuration files, etc. Many of these files allow other developers involved in the project to work efficiently by setting up shared settings at the project level.
- .bowerrc – this is a JSON formatted configuration file. There are a lot more interesting things you can do with this file, but all we’re going to do is tell it where our bower components are located.
- .editorconfig – this is a file to be shared with other developers to share configuration settings for IDEs for things like linefeed styles, indentation styles, etc. This (helps) avoid silly arguments over things like tabs vs. spaces, character encodings, etc.
- .gitattributes – this is another file allowing you to do some project-level configuration
- .gitignore – this lets you establish some files to be ignored by git. You can even find tons of example .gitignore files
- jscs.json – this is a configuration for a coding style enforcement tool called JSCS.
- CONTRIBUTING.md – common convention for open source projects is to add this file to inform possible contributors how they can help and what they need to know.
- LICENSE – another convention is to provide a file as part of the repository which explains the appropriate license type for the project.
- README.md – finally, in terms of convention, is the README file which provides an overview of the project. The README file often includes a description of what the project is all about and how to use it.
- jquery manifest file (area.jquery.json) – If you plan on publishing a jQuery plugin, you need to create a JSON-formatted package manifest file to describe your plugin
- package.json – This JSON-formatted file allows you to describe your project according to the CommonJS package format and describes things like your dependencies and other descriptive information about the project
- Gruntfile.js – This file allows you to define and configure the specific tasks you’ll be running via Grunt.
Task Automation via Grunt
As described above, we’re going to be using Grunt to automate tasks. To use Grunt you first need to install Node. Once you have node installed, all you need to do is install Grunt via the Node Package Manager (npm) like so:
npm install -g grunt-cli
If you were starting your project from scratch, you’d want to find the plugins you want and follow each one’s instructions to install. Usually the install requires little more than running:
npm install PLUGIN_NAME_HERE --save-dev
So, installing the Grunt JShint plugin would be:
npm install grunt-contrib-jshint --save-dev
For this tutorial, if you’ve cloned the repo for the jquery area plugin, run this instead:
npm install && bower install
This will install all of the dev dependencies for the Grunt tasks as well as installing the jQuery and QUnit files needed for testing.
Let’s back up a second: what is Grunt?
As tools and technologies continue to evolve, mankind’s goals remain the same: make things easier, faster, better and make the previously impossible become possible.
There are some tasks that developers do over and over during their regular day-to-day work which are made far easier through automation. There are even some automation-related tasks developers do which can be further automated. In this regard, Grunt can be seen as a way to apply DRY even to human effort. I’m a huge fan of that idea.
The specific Grunt plugins we’ll be using are:
- Load Grunt Tasks (load-grunt-tasks) – this lets us do some lazy loading of all of the Grunt tasks.
- Time Grunt (time-grunt) – this will show how long each task takes. This can be pretty important when running a lot of tasks or a single task (like a bunch of unit tests) that takes a long time.
- Clean (grunt-contrib-clean) – we’ll be using this one to simply clean out the ‘dist’ folder prior to adding the final compiled plugin
- Watch (grunt-contrib-watch) – this is a hugely beneficial task for us, because it will allow us to automatically run specific tasks whenever new changes are saved. For instance, we can set it up so that whenever the plugin file is changed, it runs JSHint and JSCS on it.
- JSON Lint (grunt-jsonlint) – A bit like JSHint, this does syntax checking on JSON files. For us, this specifically saves us from problems with our configuration files which would in-turn cause issues with our tasks running properly.
- Connect (grunt-contrib-connect) – This task sets up a connect server
- Uglify (grunt-contrib-uglify) – This task will do code minification on our plugin file and place it in the ‘dist’ folder.
Our Workflow: How Grunt and Qunit come into play
In this scenario we’re going to have some ‘watch’ tasks that run while we’re developing, primarily to make sure we don’t make silly coding style mistakes. Along the way, we’ll do test-driven development: defining our acceptance criteria and coding to meet them. Grunt allows us to automate the performance of tasks that we, as developers, do repetitively. As I’ve said in other posts:
In any case where a capability exists which can replace or reduce human effort, it only makes sense to do so. In any case where we can avoid repetitious effort, we should seek to do so.
This is exactly where tools like Grunt and Gulp truly shine. Instead of repetitively saving the files, then running jshint, then jscs, then qunit, then minifying the source, then copying it over to our dist folder, we can avoid that tedium through automation. We can establish a series of tasks, configured to our preferences, to be automatically run while we work, thus increasing our efficiency and quality.
Up next in Part 2: Actual TDD
At 2200+ words already, we’ll have to reserve the discussion of the TDD process to Part 2. We’ll go through defining the tests, creating fixtures, and writing the code. Stay Tuned!