Javscript Test Driven Development
Travelers • Scott Sauyet • June 7, 2011
Javscript Test Driven Development
=================================
### Scott Sauyet ###
How to effectively test Javascript applications. Introducing tools and
techniques to make test-driven development easier in Javascript.
What we'll Discuss
==================
* Limitations of YUI
* Other Test Frameworks, especially JSTestDriver
* Writing tests for existing code
* Writing tests as part of developing code
* Using test suites to comfortably refactor more complex code
* Useful Javascript techniques that differ from other common languages
Not Discussed:
* Functional / Integration Testing
Continuing Example
==================
We're going to investigate one piece of code in depth, looking at issues
with the current implementation, and, noting that it has no unit test,
first writing a test suite. Then we'll do some refactoring and/or
rewriting of the code
`Validators.PolicyEffectiveDate` has a number of problematic features.
We're not picking on the developer in question (who isn't even here today.)
We're just looking at some patterns in development, and how testing may help
us find and clean up code smells.
(iteration 090)
Validators in our Framework
===========================
The Validation framework uses objects with the following method as validators:
> {Object} validate(val)
Such validator objects might also have several other properties explaining when
they are to run or are not to run. The return value should be an object that
has a Boolean `status` property. If that value is false, the object should also
contain a `msg` property for the error message. It can also contain several
other properties not at issue here.
The `Validators` object contains a number of static factory functions to create
such validators. `PolicyEffectiveDate` is one of these. Note that those
validators with zero or one parameter are cached.
Problems with the Existing Code - Question
==========================================
What problems do we see with the existing code?
Some Problems with the Existing Code
====================================
* Far too many Date objects constructed on each call
* Many Validator objects are created on each call
* Strings are passed into those Validators only to be discarded
* Validation status objects are created only to be discarded
* Many function calls:
* Four Date constructor calls
* Three calls to Date.setDate
* One call to ui.get
* Four calls to different Validator constructors
* Between one and four calls to various `validate` functions, each
triggering
* One call to compareDate, which triggers
* Another Date constructor call.
The Biggest Problem with the Existing Code
==========================================
**It's confusing!**
* It relies on a number of other Validator objects whose function might or might
not be clear from their names
* It calls these validator objects, but then reverses the sense of their
response without even using a `not` operator.
* It tests for Leap Day on an existing date by calling another of these
Validators rather than just checking month and day.
* The only easy way to understand its behavior is by reading the error messages
it generates.
* There are no unit tests to help us refactor it or to serve as an API guide.
The Plan of Attack
==================
1. Write some unit tests to capture existing functionality
2. Build whatever helper functions are needed to make it easier to rewrite
3. Refactor the code
Along the way, we should learn a fair bit about test-driven development with
Javascript.
A secondary goal of this is to introduce other Javsascript testing tools and
demonstrate how they can be used in place of, or in conjunction with, our
YUI Test code. We're going to start with this.
JsTestDriver
============
* A test harness that makes it easy to test in multiple browsers.
* Works as a little web server serving up the pages and listening for results
from the browsers attached to it. Can be used for many browsers simultaneously.
* Easy to operate in batch, so can quickly be linked in to our build process.
* Can run as a plug-in to WebStorm.
* Has its own assertion framework, but can easily be adapted to QUnit, YUI, or
others.
(iteration 100)
Using JsTestDriver
==================
* Batch mode -- demo
* Plug-in to WebStorm -- demo
Differences between YUI Test and JsTestDriver
=============================================
* Different syntax for assertions: `assertTrue` versus `Y.Assert.isTrue`
* Differences in setup: HTML files for Test Suites or even Test Cases in YUI
versus one config file per high-level setup in JsTestDriver
* Run environments: YUI is manual, but can be automated. JsTestDriver is
automated (in many environments), and can't easily be run manually, although
the plug-in is straightforward and fast enough to run many tests quickly.
**Note**: these can be used interchangeably, running YUI-style tests inside
JSTD, and running JSTD-style tests inside a YUI file. We'll discuss this at
the end.
Test-Driven Development
=======================
* Write the test first, before any code needed to pass the test
* Run the test -- make this a habit, even though you know it'll fail.
Sometimes a test will fail for a different reason than you expect.
* Once the test actually fails as expected, write the minimal amount of
code necessary to pass it.
* Write another test
(iterations 110 - 120)
What To test
============
* The public API of your code. If there are complexities hidden behind
the public API, those should be factored out into their own subsystems
and tested separately.
* Fundamentals:
* Does this object exist?
* Is this object of the correct type?
* Does this object have the correct properties?
* Simple behavior
* Does this object behave as expected under normal circumstances?
* Does this object properly handle error conditions?
* Edge cases.
What Not To Test
================
* Implementation details: one big reason for a good test suite is to
allow yourself the flexibility to change implmentation without breaking
your codebase.
* Code too simple to fail, or for which failure is as likely to affect
your tests themselves as production code.
> // Don't bother!
> var a = 10;
> assertEquals(10, a);
* Interrelated components. As best you can, test each component in
isolation, stubbing out as much of the rest of the system as is practical.
How to add Tests to Existing Code
=================================
This is trickier than writing tests first. It involves one of two things:
* Go back to the original specs and write tests to match them. The problem
with this is that often the specs have changed, but have not been kept
up-to-date.
* Examine the code and capture its behavior in tests. The problem with this
is that any code of even moderate complexity is hard to analyze.
We'll try the second approach with Validators.MaxLength, to demonstrate how
to do this with JsTestDriver. (iteration 130)
Starting to Write Tests for our Target
======================================
The first test (iteration 140) only checks that PolicyEffectiveDate has the
proper interface. Not everyone does this. Some find it overkill, and they
have persuasive arguments. But I find it a nice way to ease into testing
my code.
That one succeeds because the code is already written. Now we try our first
test of the actual API (iteration 150). The test is easy to write. It fails,
though because one of our dependencies is not available.
Stubbing
========
Here is the first thing we try that is significantly different than much of
how we've done JavaScript testing with YUI. Rather than include our
depencencies in the test harness HTML file, we simply stub them out.
(iteration 160)
Stubs are a key feature of robust unit testing. You need to test your components
in isolation, which isn't so easy if you need a large collection of other
components for every set-up.
Features of Stubs
=================
* Quickly written and should be written in a foolproof manner. You don't want
to get into "testing the test"
* Can be easily introduced and easily removed, either in the test functions or
in the test case set-up and tear-down.