Fast & Maintainable Javascript TDD: A Working Approach

a Stu Penrose presentation

The Plan

  1. Define the goals of TDD
  2. Show my approach
  3. Evaluate it

Test Driven Development Is...

more than just writing tests

test first development

never writing a line of code that isn't directly satisfying a currently-failing test

The TDD Flow

Red

(failing test)

Green

(passing test, dirty implementation)

Refactor!

(clean implementation)

"Good TDD"

Focuses you on understanding & satisfying customer needs

Results in good designs

Produces easy to understand code & tests

Results in a complete, reliable and non-brittle test suite

Is faster than writing test coverage after the fact

Encourages refactoring

"Bad TDD"

Produces hard to understand code & tests

Results in an in-complete, unreliable and brittle test suite

Is slower than writing test coverage after the fact

Discourages refactoring

Can it be achieved...

In javascript?

In client-side, GUI code?

Demonstration

the approach: component oriented design

  1. focus on encapsulation of visual components
    • that's how the customer/user sees & thinks of the app
    • domain-driven design: the structure of your code should reflect the domain of your customer
    • model/view/controller responsibilities are implementation details
  2. very simple interaction between components
    • if the interaction becomes complex, it's a sign that your code is poorly factored
    • refactor to either break-apart or combine components

the approach: handling state

  1. components encapsulate state
    • each section of the dom is only touched by one component
    • javascript variables are scoped to the component
    • little to no mutable state is shared between components
    • components tend to have very little mutable state
  2. no globals (jshint)

the approach: stable design

  1. few dependencies needed
    • a way to access the dom & generate events (e.g. jquery)
    • a way to run tests
  2. favors basic language features & well written code over <insert mvc framework du jour>
  3. results in code that should be maintainable by any javascript developer, even many years down the road

the approach: smooth workflow

  1. little to no friction with tools
  2. tests are easy to start & refactor
  3. "instant" feedback loop (watchrun)

the approach: focused tests

  • each test is focused on a single function
    • given (input)
    • when (function invocation)
    • then (output)
  • with visual components, the input/invocation/output is often indirect
    • via input events
    • via side-effects in the DOM

the approach: blackbox, BDD

behavior-driven, blackbox testing

  • tests specify behavior, so they tend not to break unless the requirements change
  • test setup & expectations are abstract (clicks, text on screen, etc), so they tend to survive style/appearance changes
    • inputs: user actions, initial state
    • outputs: changes to screen, event callbacks, accessors
  • components can be completely redesigned without breaking tests
    • changing 'MVC' libraries
    • changing DOM tools (dollardom,qwery)
    • changing http tools

the approach: deterministic tests

  • async calls are faked out
    • http
    • third-party
  • tests tend to focus on smaller parts of larger workflows
    • avoids race conditions prevalent in end-to-end testing
    • makes it easier to stub-out the outside world

but can it work on REAL apps?

https://www.appleeducationaffiliate.com

multi-tab, single-page webapp

complete UI for managing affiliate relationships

advanced topics

testing inter-component contracts

functional programming

testing inter-component contracts

  • the problem: test stubs can get out of sync with the real component contract
    • less of an issue than you might think, given how simple the contracts tend to be
    • still an issue
  • multiple ways to solve this:
    • "happy path" integration/end-to-end smoke tests
      • manual
      • automated
    • implicit contract validation (ala squire)
    • explicit contract specification (ala cj's protocop, google closure compiler, etc)

functional programming

  • the problem: there is mutable state
    • mutable state is bad
    • this approach mitigates it well, but ...
    • ... it's still there
  • solution: adopt a pure-functional approach ala react
    • the DOM is now immutable
    • little-to-no test changes needed to move existing code to this approach

THE END

by Stu Penrose / engineering.cj.com