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.
8
Writing 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>
        );
    }
}
23
Unit 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');
});
24
Unit 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');
});
25
Unit 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} />
    );
}
27
Unit 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>
        );
    }
}
28
Unit 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');
});
29
Unit 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');
});
30
Unit Tests:  Limit Your Implementation Knowledge
III.
Test Structure
IV.
Host-agnostic Tests
TOOLS
enzyme
chai
mocha
33
Unit Tests: Tools
Snapshot
Tests
Problems with snapshot testing
35
Snapshot 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
36
Snapshot 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);
    });
});
37
Snapshot Tests
TOOLS
enzyme-to-json
chai-jest-snapshot
38
Snapshot 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
44
Visual Regression Tests: Comparison Tool: Tests
Tips
I.
Well-structured Bug Backlog
Bug Backlog Insights
47
Tips: 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
49
Tips: 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

TDD: Develop, Refactor and Release With Confidence

  • 1.
    Mehdi Valikhani Software Engineerat AutopilotHQ 06 / 02 / 2017 | React Sydney Meetup TDD: Develop, refactor and release with confidence
  • 2.
    “Worried that TDD willslow down your programmers? Don't. They probably need slowing down.” — J. B. Rainsberger
  • 3.
  • 4.
    I. Avoiding big and multi-purpose unitsof code (Functions, Classes, Components)
  • 5.
  • 6.
  • 7.
  • 8.
    CAUTION: OVER-ENGINEERING TRAPAHEAD Be careful when breaking down big components to multiple small ones. 8 Writing Testable Code: Caution
  • 9.
  • 10.
    I. Don’t test behaviour ofdependencies (Both internal and external)
  • 11.
    II. Don’t integrate withinternals of a unit of code (Components, Functions, Classes)
  • 12.
  • 13.
  • 14.
    V. Tests should nothave side effects
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    I.  Component Internals Don’t testinternals of a component
  • 23.
    — 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>         );     } } 23 Unit Tests: Component Internals
  • 24.
    — 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'); }); 24 Unit Tests: Component Internals
  • 25.
    — 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'); }); 25 Unit Tests: Component Internals
  • 26.
  • 27.
    — Example <Avatar />component const Avatar = ({ className, path }) => {     const classes = classNames(‘avatar’, className);     return (         <img src={path} className={classes} />     ); } 27 Unit Tests:  Limit Your Implementation Knowledge
  • 28.
    — 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>         );     } } 28 Unit Tests:  Limit Your Implementation Knowledge
  • 29.
    — Bad Test it(‘renderedimage 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'); }); 29 Unit Tests:  Limit Your Implementation Knowledge
  • 30.
    — Bad Test it(‘renderedimage 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'); }); 30 Unit Tests:  Limit Your Implementation Knowledge
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
    Problems with snapshottesting 35 Snapshot 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
  • 36.
    Best Practices 36 Snapshot Tests 1.Well-formatted snapshots 2. Proper diff logs 3. Skip composed components
  • 37.
    — Example import Reactfrom '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);     }); }); 37 Snapshot Tests
  • 38.
  • 39.
  • 40.
  • 41.
    II. Do not testmultiple “widgets” together
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
    Bug Backlog Insights 47 Tips:Well-structured Bug Backlog 1. Identify missing tests 2. Improve code-review processes 3. Improve task spec 4. Training sessions 5. Pair programming
  • 48.
  • 49.
    Opportunities from FixingBugs 49 Tips: 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
  • 50.
  • 51.
    Thank You! You canreach me at: ✉ hi@mv.id.au @mehdivk