Top Banner
Unit Testing for Great Justice by Domenic Denicola @domenic
67

Unit Testing for Great Justice

Jan 15, 2015

Download

Technology

The discovery of unit testing and test-driven development was one of the most important parts of my growth as a developer. The ability to write simple, small pieces of code that could verify the behavior of my application was in itself quite useful. And the ability to refactor without fear, just by running the test suite, changed how I program. But the real benefits come in how unit tests shape your application code: more testable code is often more well thought-out, more decoupled, and more extensible.

In this talk, I'll give a whirlwind introduction to unit testing as a concept and as a practice. I want you fully convinced it's the best thing to happen to software development, if you aren't already. Once we're on the same page there, I'll take a deep dive into what makes a good unit test. This involves testing tools such as spies, stubs, and mocks, concepts like code coverage, and practices like dependency injection that shape your application code. The most important lesson will be on how to focus on singular, isolated units of code in your testing, as this guides you toward building modular, flexible, and comprehensible applications.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Unit Testing for Great Justice

Unit Testing for Great Justiceby Domenic Denicola

@domenic

Page 2: Unit Testing for Great Justice

Domenic Denicola@domenic

https://npmjs.org/profile/domenicdenicola

https://github.com/domenic

https://github.com/NobleJS

Page 3: Unit Testing for Great Justice

q: how do you know your code works?

a: it doesn’t.

@domenic

Page 4: Unit Testing for Great Justice

to make sure something works,

you need to test it.

@domenic

Page 5: Unit Testing for Great Justice

but not manually

@domenic

Page 6: Unit Testing for Great Justice

two levels of automated testing

integration testing

unit testing

@domenic

Page 7: Unit Testing for Great Justice

@domenic

Page 8: Unit Testing for Great Justice

today we’re talking about unit testing:

what

why

how

when

@domenic

Page 9: Unit Testing for Great Justice

what is a unit test?

@domenic

Page 10: Unit Testing for Great Justice

q: what is a unit?

a: a single function or method

@domenic

Page 11: Unit Testing for Great Justice

A unit test is an automated piece of code

that invokes a function and then checks

assumptions about its logical behavior.

@domenic

Page 12: Unit Testing for Great Justice

var excerpt = "A unit test is an automated piece of code.";

var highlights = [{ start: 2, length: 4, color: "yellow" }];

var result = highlight(excerpt, highlights);

expect(result).to.equal('A <span class="highlight yellow">' +

'unit</span> test is an automated ' +

'piece of code.');

// Arrange

// Act

// Assert

@domenic

Page 13: Unit Testing for Great Justice

q: how big should a unit be?

a: about ten lines

@domenic

Page 14: Unit Testing for Great Justice

unit tested functions will:

do one thing

do it correctly

@domenic

Page 15: Unit Testing for Great Justice

q: what code should you unit test?

a: all the code (that has logic)

@domenic

Page 16: Unit Testing for Great Justice

@domenic

Page 17: Unit Testing for Great Justice

why unit test all the things?

@domenic

Page 18: Unit Testing for Great Justice

the most compelling reasoning i’ve

seen comes from this guy

http://butunclebob.com/ArticleS.UncleBob.TheSensitivityProblem @domenic

Page 19: Unit Testing for Great Justice

“Software is a very sensitive domain. If a single bit of a

100MB executable is wrong, the entire application can

be brought to it's knees. Very few other domains suffer

such extreme sensitivity to error. But one very important

domain does: accounting. A single digit error in a

massive pile of spreadsheets and financial statements

can cost millions and bankrupt an organization.”

@domenic

Page 20: Unit Testing for Great Justice

“Accountants solved this problem long ago. They use a

set of practices and disciplines that reduce the

probability that errors can go undetected. One of these

practices is Dual Entry Bookkeeping. Every transaction is

entered twice; once in the credit books, and once in the

debit books. The two entries participate in very different

calculations but eventually result in a final result of zero.

That zero means that the all the entries balance. The

strong implication is that there are no single digit errors.”

@domenic

Page 21: Unit Testing for Great Justice

“We in software have a similar mechanism that provides

a first line of defense: Test Driven Development (TDD).

Every intention is entered in two places: once in a unit

test, and once in the production code. These two entries

follow very different pathways, but eventually sum to a

green bar. That green bar means that the two intents

balance, i.e. the production code agrees with the tests.”

@domenic

Page 22: Unit Testing for Great Justice

ok, but why unit test all the things?

@domenic

Page 23: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlights.length === 0) {

return excerpt;

}

if (highlightsOverlap(highlights)) {

highlights = subdivideHighlights(highlights);

}

var tags = makeTags(highlights);

var highlighted = insertTags(excerpt, tags);

return highlighted;

}@domenic

Page 24: Unit Testing for Great Justice

more generally:

Input

E

F

Output

C

D

A

B

http://stackoverflow.com/a/11917341/3191@domenic

Page 25: Unit Testing for Great Justice

you also get

confidence

the ability to refactor without fear

credibility

free documentation

@domenic

Page 26: Unit Testing for Great Justice

https://gist.github.com/305ad492c2fd20c466be

https://github.com/senchalabs/connect/blob/gh-pages/tests.md

@domenic

Page 27: Unit Testing for Great Justice

and most importantly, you get

testable code.

@domenic

Page 28: Unit Testing for Great Justice

how do i write testable code?

@domenic

Page 29: Unit Testing for Great Justice

the most important thing to remember:

your tests should only test your code.

@domenic

Page 30: Unit Testing for Great Justice

corollary: in the end, it’s all about

managing dependencies

@domenic

Page 31: Unit Testing for Great Justice

this is why we use mv* patterns

the model is all your code: no dependencies

the view has no logic: no need to test it

the controller (or whatever) has simple logic and is easy to test using fakes

@domenic

Page 32: Unit Testing for Great Justice

this is why we use layered architecture

the domain model only depends on itself

the domain services only depend on the models

the application services only depend on the domain

the infrastructure code is straightforward translation: easy to test

the ui code just ties together application services and views

@domenic

Page 33: Unit Testing for Great Justice

testing the domain model is easy

// Arrange

var shelf = new Shelf();

var book = { id: "123" };

shelf.addBook(book);

// Act

var hasBook = shelf.hasBook("123");

// Assert

expect(hasBook).to.be.true;

@domenic

Page 34: Unit Testing for Great Justice

spies: a gentle introduction

// Arrange

var shelf = new Shelf();

var book = { id: "123" };

var spy = sinon.spy();

shelf.on("bookAdded", spy);

// Act

shelf.addBook(book);

// Assert

expect(spy).to.have.been.calledWith(book);@domenic

Page 35: Unit Testing for Great Justice

bdd: an even gentler introduction

https://gist.github.com/3399842

@domenic

Page 36: Unit Testing for Great Justice

testing services is harder

downloader.download(book)

when the app is offline

it should callback with a “no internet” error

when the app is online

and the DRM service says the user has run out of licenses

it should callback with a “no licenses left” error

and the DRM service says the user can download on this computer

and the download succeeds

it should callback with no error, and the book text

and the download fails

it should callback with the underlying error

@domenic

Page 37: Unit Testing for Great Justice

when the app is offline, it should

callback with a “no internet” error

// Arrange

var downloader = new Downloader();

var book = { id: "123" };

// ??? how to set up "app is offline"?

// Act

downloader.download(book, function (err) {

// Assert

expect(err).to.exist.and.have.property("message", "No internet!");

done();

}); @domenic

Page 38: Unit Testing for Great Justice

untestable Downloader

function Downloader() {

this.download = function (book, cb) {

if (!navigator.onLine) {

cb(new Error("No internet!"));

return;

}

// ...

};

}

@domenic

Page 39: Unit Testing for Great Justice

dependency injection to the rescue!

function Downloader(isOnline) {

this.download = function (book, cb) {

if (!isOnline()) {

cb(new Error("No internet!"));

return;

}

// ...

};

}

@domenic

Page 40: Unit Testing for Great Justice

app code becomes:

var downloader = new Downloader(function () { return navigator.onLine; });

@domenic

Page 41: Unit Testing for Great Justice

test code becomes:

// Arrange

function isOnline() { return false; }

var downloader = new Downloader(isOnline);

var book = { id: "123" };

// …

@domenic

Page 42: Unit Testing for Great Justice

similarly:

function Downloader(isOnline, drmService, doDownloadAjax) {

this.download = function (book, cb) {

// https://gist.github.com/3400303

};

}

@domenic

Page 43: Unit Testing for Great Justice

testing ui is much like testing services, but

now you depend on the dom

@domenic

Page 44: Unit Testing for Great Justice

testing ui code

var TodoView = Backbone.View.extend({

// ... lots of stuff omitted ...

events: {

"dblclick label": "edit"

},

edit: function () {

this.$el.addClass("editing");

this.input.focus();

}

});

https://github.com/addyosmani/todomvc/blob/master/architecture-examples/backbone/js/views/todos.js

@domenic

Page 45: Unit Testing for Great Justice

testing ui code: bad test

setupEntireApplication();

addATodo();

var $todo = $("#todos > li").first();

$todo.find("label").dblclick();

expect($todo.hasClass("editing")).to.be.true;

expect(document.activeElement).to.equal($todo.find(".edit")[0]);

@domenic

Page 46: Unit Testing for Great Justice

testing ui code: good test

var todoView = new TodoView();

todoView.$el = $(document.createElement("div"));

todoView.input = { focus: sinon.spy() };

todoView.edit();

expect(todoView.$el.hasClass("editing")).to.be.true;

expect(todoView.input.focus).to.have.been.called;

@domenic

Page 47: Unit Testing for Great Justice

when should i write my tests?

@domenic

Page 48: Unit Testing for Great Justice

let’s talk about code coverage

@domenic

Page 49: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlights.length === 0) {

return excerpt;

}

if (highlightsOverlap(highlights)) {

highlights = subdivideHighlights(highlights);

}

var tags = makeTags(highlights);

var highlighted = insertTags(excerpt, tags);

return highlighted;

}@domenic

Page 50: Unit Testing for Great Justice

when given a highlight and an excerpt

it should return the excerpt with highlighting tags inserted

@domenic

Page 51: Unit Testing for Great Justice

var excerpt = "A unit test is an automated piece of code.";

var highlights = [{ start: 2, length: 4, color: "yellow" }];

var result = highlight(excerpt, highlights);

expect(result).to.equal('A <span class="highlight yellow">' +

'unit</span> test is an automated ' +

'piece of code.');@domenic

Page 52: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlights.length === 0) {

return excerpt;

}

if (highlightsOverlap(highlights)) {

highlights = subdivideHighlights(highlights);

}

var tags = makeTags(highlights);

var highlighted = insertTags(excerpt, tags);

return highlighted;

}

✓@domenic

Page 53: Unit Testing for Great Justice

q: how can we achieve 100% coverage?

a: use test-driven development

@domenic

Page 54: Unit Testing for Great Justice

the three rules 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.

You are not allowed to write any more production code

than is sufficient to pass the one failing unit test.

http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd@domenic

Page 55: Unit Testing for Great Justice

the three steps of tdd

red

green

refactor

@domenic

Page 56: Unit Testing for Great Justice

when there are no highlights

the excerpt should pass through unchanged

0/1 tests passed @domenic

Page 57: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlights.length === 0) {

return excerpt;

}

}

@domenic

Page 58: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlights.length === 0) {

return excerpt;

}

}

@domenic

Page 59: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

return excerpt;

}

1/1 tests passed @domenic

Page 60: Unit Testing for Great Justice

when there are no highlights

the excerpt should pass through unchanged

when there are simple non-overlapping highlights

it should insert tags around those areas

1/2 tests passed @domenic

Page 61: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlights.length === 0) {

return excerpt;

}

var tags = makeTags(highlights);

var highlighted = insertTags(excerpt, tags);

return highlighted;

}

2/2 tests passed @domenic

Page 62: Unit Testing for Great Justice

when there are no highlights

the excerpt should pass through unchanged

when there are simple non-overlapping highlights

it should insert tags around those substrings

when there are overlapping highlights

it should subdivide them before inserting the tags

2/3 tests passed @domenic

Page 63: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlights.length === 0) {

return excerpt;

}

if (highlightsOverlap(highlights)) {

highlights = subdivideHighlights(highlights);

}

var tags = makeTags(highlights);

var highlighted = insertTags(excerpt, tags);

return highlighted;

}

✓3/3 tests passed @domenic

Page 64: Unit Testing for Great Justice

@domenic

Page 65: Unit Testing for Great Justice

function highlight(excerpt, highlights) {

if (highlightsOverlap(highlights)) {

highlights = subdivideHighlights(highlights);

}

var tags = makeTags(highlights);

var highlighted = insertTags(excerpt, tags);

return highlighted;

}

3/3 tests still passing! @domenic

Page 66: Unit Testing for Great Justice

summary

Unit tests are automated tests that verify your application’s logic by

breaking it up into small units.

Unit testing is like double-entry bookkeeping. It gives you the ability

to refactor without fear.

Writing unit tests will lead to writing testable code, which is

decoupled via dependency injection and thus becomes more

modular, flexible, and comprehensible.

The best way to write unit tests is with test-driven development,

which has three steps: red, green, refactor. Make these steps as

small as possible.

@domenic

Page 67: Unit Testing for Great Justice

unit-testing tools i like

Mocha test runner: http://mochajs.com

Chai assertion library: http://chaijs.com

Sinon.JS spy/stub/mock library: http://sinonjs.org

Sandboxed-Module environment faker: http://npm.im/sandboxed-module

Cover code coverage tool: http://npm.im/cover

My Chai plugins:

Sinon–Chai: http://npm.im/sinon-chai

Chai as Promised: http://npm.im/chai-as-promised

@domenic