TDD session #1

generic overview

purpose of this session

common ground

terminology

workflow

good / bad practices

initiate a conversation

where to start

how to adapt

recommend

dev process

resources

goals

front-end devs

understand principles

enjoy writing tests

our codebase

maintainable

performant

easy to move forward

three type of questions

WHATs

definitions

terminology

WHYs

concepts

good practices

HOWs

challenges

patterns

the WHATs

WHAT is a unit?

a module

a component

a function

WHAT is test code?

requirements / AC turned into code

also documentation of your code

But WHAT is production code then?

manifestation of requirements

your creation helping people out there

so make sure it works properly === tested :)

(some TDD obsessed just say "side effect")

WHAT is the cycle of TDD?

RED: write a new test case, see it fail

GREEN: fulfil it by implementing functionality

REFACTOR: make it more clean / efficient

1. Red

understand your requirements

write your first test case / assertion

run your tests and see the new one fail

2. Green

write the least sufficient production code

don't worry about design, just make it work

3. Refactor

make your changes, clean up the mess :)

look for code smells (duplication, etc), fix them

after each tiny bit of refactoring, run your tests

Make it work. Make it right. Make it fast.

Kent Beck (father of TDD)

Refactoring is not something you do at the end of the project

Uncle Bob Martin (cleancoder.com)

it's something you do on a minute-by-minute basis

Refactoring is a continuous in-process activity

not something that is done late (and therefore optionally)

WHAT are stubs, mocks, fakes, spys?

helpful patterns to make devs life easier

whilst we are writing tests

we call them test doubles

test double

any kind of pretend object is a test double

 in place of a real object for testing purposes

term origin: stunt double

stubs

you can stub out e.g. a method of an object

to return a certain value

your test cases can rely on

spys

keep track of method calls

what arguments they were called with

help when testing contract between components

can be overused in a wrong way though (!)

fakes

fully working test doubles

faking behaviours of a collaborator object

resolving their own dependencies (e.g. in-memory db)

to help you test dependant objects easier

mocks

a pre-set doubles of a collaborator objects

containing certain expectations / assertions

so your tests won't be bloated

and you can keep them DRY

The WHYs

WHY would you write tests?

must test our code somehow

spotting mistakes immediately

finding bugs quickly

knowledge transfer + documentation

(automated, of course)

WHY not just go "Cowboy"?

writing code that works by pure luck

test if it works manually, or just forget

ashamed when demoing, it "should" work!

unintentionally introduce regressions

MYTH: "TDD slows you down"

yes, it has a learning curve + needs practicing

yes, needs a completely different mindset

but gives you confidence + piece of mind

less pain, more gain! :)

it's all about TIME

fixing OR preventing a defect?

might take more time up-front (for a Dev)

reduces time later (for more people involved)

debugging time converges to zero

more time to create and experiment

WHY unit tests?

peace of mind: test and "forget"

unit tests document components

tests have their own place (e2e, integration, smoke etc)

helps devs how to use them

fast!

unit testing drives design

modular as opposed to monolithic

testable pieces of code

WHY test driven, test first?

helps you

understand what needs to be done

immediate feedback when not clear

write the only necessary code

requirements >>> automated tests

better code

easier to change

shortens turnaround time / release cycle

TDD improves design

automated testing !== TDD

remember

TDD === writing tests first

WHY break your test?

is potentially dangerous

breaking tests helps to spot mistakes

made in your tests

getting used to writing and relying on tests

WHY run tests in automation?

on each file save tests should run

so you can experiment quickly

we tend to forget running tests (manually)

and spot mistakes immediately (!)

it's all about TIME

unit test suite must be run in seconds
at most

remember

WHY refactor your tests?

keep them tidy and fast

you will improve how you write tests over time

tests are code, keep them DRY

refactoring vs rewriting

rewriting is changing code

refactoring is keeping behaviour + improving code

tests are your requirements

change them when requirements changed

remember

WHY test code in isolation?

all have a contract with the outside world

test their behaviours

components are LEGO(tm) blocks

treat them as blackboxes / 3rd party libs

WHY integration tests?

we create functionality by composing them

are dependencies used as expected?

components must play together nicely

still "unit level"

WHY end-to-end tests?

make sure they're complete

manual testing is tedious and error prone

user journeys defined in AC / stories

WHY team effort?

discuss test cases with your tester!

before you start ;)

figuring how to test something properly is sometimes difficult

WHY avoid spying?

do not write test against implementation

writing tests AFTER might require it

spying tends to drive design to the wrong direction

it prevents refactoring

WHY is it red sometimes?

because it is non-deterministic

unreliable dependencies

ok, but how to prevent it?

set up unique test context for each test case

run tests in isolation

The HOWs

HOW to deal with bugs?

hunting down bugs is the most time consuming

when a bug is discovered

write test(s) to reproduce the bug, see them fail

fix production code, make it green

HOW to do efficient unit testing?

automation and speed is the key

least amount of tests + make them fast

run them as often as possible

HOW to deal with duplicates?

the bigger the team, the more chance

why test something twice? code smell?

tidying up / refactoring is always encouraged!

but comprehensiveness has priority

HOW to

write human readable tests?

are easy to understand, even for non-devs

describe behaviours

give good examples on how to use a component

good test cases

Bad example

Good example

HOW to deal with dependencies?

other code / component, database, remote service etc.

avoid coupling, inject dependencies

it's all about the contract between components

fake / stub / mock dependencies of the unit-under-test

enclose 3rd parties into adapters

3rd party libs

allows you to consider replacing / updating them

good test coverage

poor coverage

base jumping naked! :P

it's all about TIME

remember

don't let dependencies slow you (and your tests) down!

HOW to write just enough tests?

imagine the test suite as a Minimum Spanning Tree

nodes === requirements

edges === tests

translated to unit testing

find the minimal amount of tests

to cover a component and fulfil all requirements

possible multiplicity

there might be several spanning trees in a graph

there might be several ways to cover all requirements

there's no such a thing as the "only" right solution

HOW to add coverage afterwards?

probably the most difficult thing in TDD

code written without tests is already LEGACY code

"code written without tests is BAD code,

doesn't matter how well-written it is"

Michael Feathers

difficult because

you have to set aside the current implementation

to focus on real requirements

understand and write tests against them

adding tests afterwards is

you will most likely

have to break dependencies

run into several difficulties

rewrite components instead of improve them

whilst refactoring existing code

HOW can I still find

the joy of crafting code?

we always start writing tests first in TDD

which is a challenge itself :)

but implementing functionality also remains a challenge

it's about HOW you approach problems

try to find joy in

writing proper test cases

which all your peers can understand easily

writing just enough tests and still cover everything

refactoring your code with confidence you've never felt

focusing on cool stuff instead of hunting bugs :)

TDD is an ART

the more you practice it

the more you enjoy it

is pretty much learning how to ride a bicycle

first you need to learn balancing

and might need a helping hand

then you will try it out yourself

over time it will become an instinct

Recommended resources

Next session?

Creating HTML5 apps / pages via TDD

automated tests for HTML / CSS / JS

An introduction to TDD

By Marton Hipszki

An introduction to TDD

  • 889