TDD: Develop, Refactor and Release With Confidence

Preview:

Citation preview

Mehdi Valikhani Software Engineer at AutopilotHQ

06 / 02 / 2017 | React Sydney Meetup

TDD: Develop, refactor and release with

confidence

“Worried that TDD will slow down your

programmers? Don't. They

probably need slowing down.”

— J. B. Rainsberger

Writing Testable Code

I. Avoiding big and multi-purpose

units of code (Functions, Classes, Components)

II. Developing small and focused units of code

III. Separation of concerns

IV. Using global objects

CAUTION: OVER-ENGINEERING TRAP AHEAD

Be careful when breaking down big components to multiple small ones.

8Writing Testable Code: Caution

TDD Principles

I. Don’t test behaviour

of dependencies (Both internal and external)

II. Don’t integrate with internals

of a unit of code (Components, Functions, Classes)

III. Tests should be

execution-order agnostic

IV. Tests should be

independent

V. Tests should not have

side effects

VI. Feedback

VII. Focused Tests

VIII. Consistency

IX. Don’t be DRY

X. Iterate, Review,

Improve

XI. Test Code is Untested

Unit Tests

I.  Component Internals

Don’t test internals of a component

— Example <UserProfile /> component

class UserProfile extends React.Component {

    _pathToAvatar(userId) {

        return `my-cdn.com/users/${userId}/avatar-320.png`;

    }

    render() {

        return (

            <div className=“user”>

                <h3 className=“user__name”>{this.props.name}</h3>

                <img 

                    className=“user__avatar” 

                    src=`${this._pathToAvatar(this.props.id)}` 

                />

           </div>

        );

    }

}

23Unit Tests: Component Internals

— Bad Test

it(‘_pathToAvatar() returns correct path for given user id’, () => {

    const userProfile = shallow(<UserProfile name=“Taco” id=“123” />);

    const subject = userProfile.instance()._pathToAvatar;

    const result = subject(123);

    expect(result).to.equal('my-cdn.com/users/123/avatar-320.png');

});

24Unit Tests: Component Internals

— Bad Test

it(‘_pathToAvatar() returns correct path for given user id’, () => {

    const userProfile = shallow(<UserProfile name=“Taco” id=“123” />);

    const subject = userProfile.instance()._pathToAvatar;

    const result = subject(123);

    expect(result).to.equal('my-cdn.com/users/123/avatar-320.png');

});

— Good Test

it(‘rendered image points to correct asset’, () => {

    const userProfile = shallow(<UserProfile name=“Taco” id=“123” />);

    const subject = userProfile.find(‘.user__avatar’);

    const result = subject.prop(’src');

    expect(result).to.equal('my-cdn.com/users/123/avatar-320.png');

});

25Unit Tests: Component Internals

II.  Limit Your Implementation

Knowledge

— Example <Avatar /> component

const Avatar = ({ className, path }) => {

    const classes = classNames(‘avatar’, className);

    return (

        <img src={path} className={classes} />

    );

}

27Unit Tests:  Limit Your Implementation Knowledge

— Example <UserProfile /> component

class UserProfile extends React.Component {

    _pathToAvatar(userId) {

        return `my-cdn.com/users/${userId}/avatar-320.png`;

    }

    render() {

        return (

            <div className=“user”>

                <h3 className=“user__name”>{this.props.name}</h3>

                <Avatar 

                    className=“user__avatar” 

                    path=`${this._pathToAvatar(this.props.id)}` 

                />

           </div>

        );

    }

}

28Unit Tests:  Limit Your Implementation Knowledge

— Bad Test

it(‘rendered image points to correct asset’, () => {

    const userProfile = shallow(<UserProfile name=“Taco” id=“123” />);

    const subject = userProfile.find(‘.icon’);

    const result = subject.prop(’src');

    expect(result).to.equal('my-cdn.com/users/123/avatar-320.png');

});

29Unit Tests:  Limit Your Implementation Knowledge

— Bad Test

it(‘rendered image points to correct asset’, () => {

    const userProfile = shallow(<UserProfile name=“Taco” id=“123” />);

    const subject = userProfile.find(‘.icon’);

    const result = subject.prop(’src');

    expect(result).to.equal('my-cdn.com/users/123/avatar-320.png');

});

— Good Test

it(‘rendered image points to correct asset’, () => {

    const userProfile = shallow(<UserProfile name=“Taco” id=“123” />);

    const subject = userProfile.find(Avatar);

    const result = subject.prop(’path');

    expect(result).to.equal('my-cdn.com/users/123/avatar-320.png');

});

30Unit Tests:  Limit Your Implementation Knowledge

III. Test Structure

IV. Host-agnostic Tests

TOOLS

enzyme chai

mocha

33Unit Tests: Tools

Snapshot Tests

Problems with snapshot testing

35Snapshot Tests

1. Validation of the “gold master” snapshot is manual and should be done by developer

2. Unlike unit tests, snapshot tests don’t provide specific guidance for what the original developer expected beyond the “actual behaviour”

3. They’re often fragile and generate lots of false negatives

Best Practices

36Snapshot Tests

1. Well-formatted snapshots

2. Proper diff logs

3. Skip composed components

— Example

import React from 'react';

import { shallow } from 'enzyme';

import { expect } from 'chai';

import generateSubtree from 'enzyme-to-json';

import Button from './button';

describe(‘<Button />’, () => {

    it(‘Generated markup matches golden master’, function test() {

        const subject = shallow(

            <Button size=“small” color=“blue” icon=“save” text=“Save" />

        );

        const result = generateSubtree(subject);

        expect(result).to.matchSnapshot(

`${this.test.file}.snap`, 

this.test.title);

    });

});

37Snapshot Tests

TOOLS

enzyme-to-json chai-jest-snapshot

38Snapshot Tests: Tools

Visual Regression

Tests

I. Test “widgets”, not

individual components

II. Do not test multiple “widgets” together

III. Mock Data

IV. Comparison Tool

TOOLS

webdriver.io wdio-visual-regression-service

44Visual Regression Tests: Comparison Tool: Tests

Tips

I. Well-structured Bug Backlog

Bug Backlog Insights

47Tips: Well-structured Bug Backlog

1. Identify missing tests

2. Improve code-review processes

3. Improve task spec

4. Training sessions

5. Pair programming

II. Opportunities from

Fixing Bugs

Opportunities from Fixing Bugs

49Tips: Opportunities from Fixing Bugs

1. Re-evaluate code readability

2. Investigate source of bug

3. Re-evaluate test readability

4. Re-evaluate existing test’s behaviour

Questions?

Thank You!

You can reach me at:

✉ hi@mv.id.au

" @mehdivk