TDD 101

Workshop

Silvia Mur
Elena García

Index

  1. Why do we need tests?
  2. Types of tests
  3. What is TDD?
  4. TDD life cycle
  5. The anatomy of a test
  6. Unit testing in JavaScript
  7. Let's start coding!

Why do we need tests?

Tests help deliver code with quality

Tests ease maintenance

How do I know that with my code I didn't break anything?

Oh, there is a bug, something is not working properly

Tests ease maintenance

Tests are live documentation

Types of tests

Functional testing types

 

  • Unit testing
  • Integration testing
  • System testing
  • Sanity testing
  • Smoke testing
  • Interface testing
  • Regression testing
  • Beta/Acceptance testing

Non-functional testing types

  • Performance testing
  • Load testing
  • Stress testing
  • Security testing
  • Compatibility testing
  • Install testing
  • Recovery testing
  • Reliability testing
  • Usability testing
  • ...

There are multiple ways on how you can test software

Functional testing

scope vs price

What is TDD?

Test driven development (TDD) is a software development approach in which a test is written before writing the code. Once the new code passes the test, it is refactored to an acceptable standard.
 

TDD Benefits

  • Your code is tested
  • No time spent in debugging
  • Bugs are caught in early stages of development 
  • You need to fully understand the functional requirements. Tests are based on business logic
    • Happy path, edge cases
  • You design what you need
  • Loose coupling design. Your code is testable

TDD Downs

  • Learning TDD is a journey that involves a lot of practice
  • Hard to apply to existing legacy code

 

but... in the mid term is really beneficial

Demistifying TDD

We do not have time to do TDD

TDD does not require more time than "normal" programming

We do not have time to do TDD

I have awesome tests because I do TDD

TDD is not black magic: you have also to learn how to write good unit tests

I have awesome tests because I do TDD
My test coverage is 100% because I do TDD

Test coverage does not need to be 100% with TDD

My test coverage is 100% because I do TDD

The architecture of my application is a result of doing TDD

Doing TDD does not mean that you don't have to worry about analysis and architecture design.

The architecture of my application is a result of doing TDD

TDD life cycle

Law 1

You are not allowed to write any production code unless it is to make a failing unit test pass

 

Law 2

You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures

Law 3

You are not allowed to write any more production code than is sufficient to pass the one failing unit test

Write enough code to pass the test

describe('sum function', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2).toBe(3));
  });
});
function sum () {
  return 3;
}
describe('sum function', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2).toBe(3));
  });

  test('adds 2 + 2 to equal 4', () => {
    expect(sum(2, 2).toBe(4));
  });
});
function sum (a, b) {
  return a + b;
}

The anatomy

of a test

1. Setup

2. Execution

3. Assert

0. Description

1. Setup

2. Execution

3. Assert

0. Description

1. Assert

2. Execution

3. Setup

Description

It's used to create a sentence for the test

describe('Hermione casts the Alohomora spell', () => {
  it('opens the door in front of her', () => {
    ...
  })
})

Assert

It checks that the action was performed correctly

(check that either the return value of a function or the state of an object are as expected)

describe('Hermione casts the Alohomora spell', () => {
  it('unlocks the door in front of her', () => {
    // assert
    expect(door.status).toBe('unlocked')
  })
})

Execution

(Act)

It performs the action that we are testing on the unit

describe('Hermione casts the Alohomora spell', () => {
  it('unlocks the door in front of her', () => {
    // execute
    Hermione.cast('Alohomora', door)
    // assert
    expect(door.status).toBe('unlocks')
  })
})

Setup

(Arrange)

It's used to get everything ready to perform the test

(declaring variables, building required objects or setting the state based

on the circumstances we want to test)

describe('Hermione casts the Alohomora spell', () => {
  it('unlocks the door in front of her', () => {
    // setup
    const door = { status: 'locked' }
    // execute
    Hermione.cast('Alohomora', door)
    // assert
    expect(door.status).toBe('unlocked')
  })
})

Unit testing in JavaScript

  • Libraries
    • Mocking/stubbing
    • Assertion
  • Test runners
  • Testing frameworks

Sinon.JS

Jasmine

Libraries

Test runners

Testing frameworks

Karma

Jest

QUnit

Jest (Delightful JavaScript Testing)

  • Installation
    • yarn add jest --dev
    • npm install jest --save-dev
  • Configuration
    • jest --init
  • Run through npm scripts
    • yarn test
    • npm test

Example

function sum (a, b) {
  return a + b;
}

describe('sum function', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2).toBe(3));
  });
});

Matchers

  • toBe
  • toBeNull
  • toBeUndefined
  • toBeTruthy
  • toBeFalsy
  • toEqual
  • toBeGreaterThan
  • toBeLessThan
  • toMatch
  • toMatchObject
  • toContain
  • not
  • ...

Let's start coding!

We have two katas

  • Password validator (~1h)
  • Mars Rover (~2h)

We will do

ping-pong pair programming

Write failing test

Write code that makes it pass

Write failing test

Write code that makes it pass

REFACTOR

TDD 101 Workshop

By adaJS

TDD 101 Workshop

  • 1,307