TDD Fundamentals with TypeScript


Introduction

“If one’s first ask yourself what the structure of convincing proof would be and, having found this, then constructs a program satisfying this proof’s requirements..”
~~The Humble Programmer, Edsger W. Dijkstra (1972)

“It is necessary to have a hand calculated check case with which to compare the answers which will later be calculated by the machine.”
~~Digital Computer Programming, D.D McCracken (1957)

“The first step is to write a test that fails.”
~~ Test Driven Development by Example, Kent Beck (2002)

Why TDD?

  1. Fast feedback
  • Reduces risk
  • More deploys
  1. Higher confidence
  • Testable code
  • Enables refactoring
  • Higher quality code
  1. Thinking tool
  • Less debugging
  • More coding time
  1. Documentation
  • Living documentation
  • Examples
  • Specifications

Setup

npm i -g jest

mkdir tdd-ts && $_
npm i -D jest typescript ts-jest @types/jest
npx ts-jest config:init
# test installation:
jest
## TypeScript setup

```sh
npx tsc --init

Basic configuration:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "strict": true
  }
}

Update: using Vitest instead of Jest: repo

Usage

jest --watchAll --verbose --coverage

The Anatomy of a Test

Naming and Arranging

import { greeter } from './greeter'

describe('greeter', () => {
  // same as production code file name
  test('helloWorld given default should return `Hello world!`', () => {
    // Arrange (harnessing)
    // State, services or SUT (in this order)
    const expected = 'Hello world!' // expected outcome
    const sut = greeter() // subject under test

    // Act (preferrably one line)
    const actual = sut.helloWorld() // actual outcome

    // Assert (one assertion per test)
    expect(actual).toBe(expected) // assertion: expect actual outcome
    // to be equal to expected outcome
  })
})
  • To skip a test, prefix it with a x (e.g. xtest('should skip this test', () => {})). Also xdescribe, xit, or xtest.
  • To run only one test, use test.only (e.g. test.only('should run only this test', () => {})).

Structure

Four-Phase Test

  1. Setup
  2. Exercise
  3. Verify
  4. Tear down

There is no repetition in the phases.

F.I.R.S.T. Principles

  • Fast

    • Tests should be fast to run
    • Tests should be fast to write
  • Independent

    • Tests should not depend on each other
    • Tests should not depend on external resources
  • Repeatable

    • Tests should be repeatable in any environment
    • Tests should be repeatable at any time
    • Deterministic
  • Self-validating

    • Tests either pass or fail
    • The test runner reports the result
  • Thorough

    • Tests should cover all scenarios
    • Raw coverage numbers are not enough

    Number of tests:

    (less) Acceptance -> Integration -> Unit (more)

    F.I.R.S.T. Principles are more important than other principles like DRY, SOLID, etc. for TEST CODE.

The 3 “laws” of TDD

  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

Green Bar Pattern: Fake It

Pattern: A known way to solve a known problem.

Fake it till you make it: do a fake implementation to get the current test to pass.

Equivalence Partitions and Boundaries

A group of values for which the behavior of the code is the same. A partition is a set of inputs that produce the same output.

Boundary: where two equivalence partitions meet.

Triangulation Green Bar Pattern

Fake it, Fake it, Make it (triangulate).

Naming tests

The stages of naming:

Nonsense —> Accurate —> Precise —> Meaningful

Meaningful: intention revealing, domain concepts

Green Bar Patterns and TDD Gears

  • One-to-Many Green Bar Pattern
  • The Obvious Green Bar Patter
  • The Backout Green Bar Pattern
  • The Learning Test Green Bar Pattern
  • The TDD Gears Model

Test Doubles

A piece of code that replaces some “real” code for purpose of testing. So that tests can be Fast, Isolated and Repeatable.

Two main styles of test doubles:

  • London: heavy use of test of test doubles
  • Chicago: minimize use of test doubles

Fakes, Stubs and Mocks:

Fake: an object with a simplified working implementation

  • Only for indirect input
  • Not used for control
  • Not used for assertions

Stub: An object that provides predefined data

  • Can be used for indirect input
  • Can be used for control
  • Not used for assertions

Mock: An object that records calls received, and verifies them against expected calls

  • Can be used for indirect input
  • Can be used for control
  • Used for assertions

Notes

Show me the code

References