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?
- Fast feedback
- Reduces risk
- More deploys
- Higher confidence
- Testable code
- Enables refactoring
- Higher quality code
- Thinking tool
- Less debugging
- More coding time
- 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', () => {})
). Alsoxdescribe
,xit
, orxtest
. - To run only one test, use
test.only
(e.g.test.only('should run only this test', () => {})
).
Structure
- Setup
- Exercise
- Verify
- 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
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- 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
- TypeScript TDD Template
- Rock, Paper, Scissors Kata
- FizzBuzz Kata
- Age Calculator Kata
- String Calculator Kata