TDD: Develop, Refactor and Release With Confidence
-
Upload
mehdi-valikhani -
Category
Engineering
-
view
148 -
download
5
Transcript of TDD: Develop, Refactor and Release With Confidence
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?