unit testing for great justice
DESCRIPTION
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.TRANSCRIPT
![Page 1: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/1.jpg)
Unit Testing for Great Justiceby Domenic Denicola
@domenic
![Page 2: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/2.jpg)
Domenic Denicola@domenic
https://npmjs.org/profile/domenicdenicola
https://github.com/domenic
https://github.com/NobleJS
![Page 3: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/3.jpg)
q: how do you know your code works?
a: it doesn’t.
@domenic
![Page 4: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/4.jpg)
to make sure something works,
you need to test it.
@domenic
![Page 5: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/5.jpg)
but not manually
@domenic
![Page 6: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/6.jpg)
two levels of automated testing
integration testing
unit testing
@domenic
![Page 7: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/7.jpg)
@domenic
![Page 8: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/8.jpg)
today we’re talking about unit testing:
what
why
how
when
@domenic
![Page 9: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/9.jpg)
what is a unit test?
@domenic
![Page 10: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/10.jpg)
q: what is a unit?
a: a single function or method
@domenic
![Page 11: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/11.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/12.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/13.jpg)
q: how big should a unit be?
a: about ten lines
@domenic
![Page 14: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/14.jpg)
unit tested functions will:
do one thing
do it correctly
@domenic
![Page 15: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/15.jpg)
q: what code should you unit test?
a: all the code (that has logic)
@domenic
![Page 16: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/16.jpg)
@domenic
![Page 17: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/17.jpg)
why unit test all the things?
@domenic
![Page 18: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/18.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/19.jpg)
“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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/20.jpg)
“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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/21.jpg)
“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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/22.jpg)
ok, but why unit test all the things?
@domenic
![Page 23: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/23.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/24.jpg)
more generally:
Input
E
F
Output
C
D
A
B
http://stackoverflow.com/a/11917341/3191@domenic
![Page 25: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/25.jpg)
you also get
confidence
the ability to refactor without fear
credibility
free documentation
@domenic
![Page 26: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/26.jpg)
https://gist.github.com/305ad492c2fd20c466be
https://github.com/senchalabs/connect/blob/gh-pages/tests.md
@domenic
![Page 27: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/27.jpg)
and most importantly, you get
testable code.
@domenic
![Page 28: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/28.jpg)
how do i write testable code?
@domenic
![Page 29: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/29.jpg)
the most important thing to remember:
your tests should only test your code.
@domenic
![Page 30: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/30.jpg)
corollary: in the end, it’s all about
managing dependencies
@domenic
![Page 31: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/31.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/32.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/33.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/34.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/35.jpg)
bdd: an even gentler introduction
https://gist.github.com/3399842
@domenic
![Page 36: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/36.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/37.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/38.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/39.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/40.jpg)
app code becomes:
var downloader = new Downloader(function () { return navigator.onLine; });
@domenic
![Page 41: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/41.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/42.jpg)
similarly:
function Downloader(isOnline, drmService, doDownloadAjax) {
this.download = function (book, cb) {
// https://gist.github.com/3400303
};
}
@domenic
![Page 43: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/43.jpg)
testing ui is much like testing services, but
now you depend on the dom
@domenic
![Page 44: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/44.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/45.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/46.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/47.jpg)
when should i write my tests?
@domenic
![Page 48: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/48.jpg)
let’s talk about code coverage
@domenic
![Page 49: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/49.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/50.jpg)
when given a highlight and an excerpt
it should return the excerpt with highlighting tags inserted
@domenic
![Page 51: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/51.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/52.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/53.jpg)
q: how can we achieve 100% coverage?
a: use test-driven development
@domenic
![Page 54: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/54.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/55.jpg)
the three steps of tdd
red
green
refactor
@domenic
![Page 56: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/56.jpg)
when there are no highlights
the excerpt should pass through unchanged
0/1 tests passed @domenic
![Page 57: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/57.jpg)
function highlight(excerpt, highlights) {
if (highlights.length === 0) {
return excerpt;
}
}
@domenic
![Page 58: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/58.jpg)
function highlight(excerpt, highlights) {
if (highlights.length === 0) {
return excerpt;
}
}
✓
◌
✓
✓
✓
@domenic
![Page 59: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/59.jpg)
function highlight(excerpt, highlights) {
return excerpt;
}
✓
✓
✓
1/1 tests passed @domenic
![Page 60: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/60.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/61.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/62.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/63.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/64.jpg)
@domenic
![Page 65: Unit Testing for Great Justice](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/65.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/66.jpg)
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](https://reader034.vdocuments.net/reader034/viewer/2022051412/54b7971b4a79591d4a8b472c/html5/thumbnails/67.jpg)
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