Unit Testing for Great Justice
Upcoming SlideShare
Loading in...5
×
 

Unit Testing for Great Justice

on

  • 2,374 views

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 ...

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.

Statistics

Views

Total Views
2,374
Views on SlideShare
2,365
Embed Views
9

Actions

Likes
0
Downloads
26
Comments
1

3 Embeds 9

http://www.linkedin.com 4
https://si0.twimg.com 3
https://twimg0-a.akamaihd.net 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

11 of 1

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Unit Testing for Great Justice Unit Testing for Great Justice Presentation Transcript

    • Unit Testing for Great Justiceby Domenic Denicola @domenic
    • Domenic Denicola@domenichttps://npmjs.org/profile/domenicdenicolahttps://github.com/domenichttps://github.com/NobleJS
    • q: how do you know your code works?a: it doesn’t. @domenic
    • to make sure something works, you need to test it. @domenic
    • but not manually @domenic
    • two levels of automated testingintegration testingunit testing @domenic
    • @domenic
    • today we’re talking about unit testing:whatwhyhowwhen @domenic
    • what is a unit test? @domenic
    • q: what is a unit?a: a single function or method @domenic
    • A unit test is an automated piece of codethat invokes a function and then checksassumptions about its logical behavior. @domenic
    • // Arrangevar excerpt = "A unit test is an automated piece of code.";var highlights = [{ start: 2, length: 4, color: "yellow" }];// Actvar result = highlight(excerpt, highlights);// Assertexpect(result).to.equal(A <span class="highlight yellow"> + unit</span> test is an automated + piece of code.); @domenic
    • q: how big should a unit be?a: about ten lines @domenic
    • unit tested functions will:do one thingdo it correctly @domenic
    • q: what code should you unit test?a: all the code (that has logic) @domenic
    • @domenic
    • why unit test all the things? @domenic
    • the most compelling reasoning i’veseen comes from this guyhttp://butunclebob.com/ArticleS.UncleBob.TheSensitivityProblem @domenic
    • “Software is a very sensitive domain. If a single bit of a100MB executable is wrong, the entire application canbe brought to its knees. Very few other domains suffersuch extreme sensitivity to error. But one very importantdomain does: accounting. A single digit error in amassive pile of spreadsheets and financial statementscan cost millions and bankrupt an organization.” @domenic
    • “Accountants solved this problem long ago. They use aset of practices and disciplines that reduce theprobability that errors can go undetected. One of thesepractices is Dual Entry Bookkeeping. Every transaction isentered twice; once in the credit books, and once in thedebit books. The two entries participate in very differentcalculations but eventually result in a final result of zero.That zero means that the all the entries balance. Thestrong implication is that there are no single digit errors.” @domenic
    • “We in software have a similar mechanism that providesa first line of defense: Test Driven Development (TDD).Every intention is entered in two places: once in a unittest, and once in the production code. These two entriesfollow very different pathways, but eventually sum to agreen bar. That green bar means that the two intentsbalance, i.e. the production code agrees with the tests.” @domenic
    • ok, but why unit test all the things? @domenic
    • 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}
    • more generally: A C EInput Output B D F http://stackoverflow.com/a/11917341/3191 @domenic
    • you also getconfidencethe ability to refactor without fearcredibilityfree documentation @domenic
    • https://gist.github.com/305ad492c2fd20c466be @domenichttps://github.com/senchalabs/connect/blob/gh-pages/tests.md
    • and most importantly, you get testable code. @domenic
    • how do i write testable code? @domenic
    • the most important thing to remember: your tests should only test your code. @domenic
    • corollary: in the end, it’s all about managing dependencies @domenic
    • 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
    • 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
    • testing the domain model is easy// Arrangevar shelf = new Shelf();var book = { id: "123" };shelf.addBook(book);// Actvar hasBook = shelf.hasBook("123");// Assertexpect(hasBook).to.be.true; @domenic
    • spies: a gentle introduction// Arrangevar shelf = new Shelf();var book = { id: "123" };var spy = sinon.spy();shelf.on("bookAdded", spy);// Actshelf.addBook(book);// Assert @domenicexpect(spy).to.have.been.calledWith(book);
    • bdd: an even gentler introductionhttps://gist.github.com/3399842 @domenic
    • testing services is harderdownloader.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
    • when the app is offline, it shouldcallback with a “no internet” error// Arrangevar downloader = new Downloader();var book = { id: "123" };// ??? how to set up "app is offline"?// Actdownloader.download(book, function (err) { // Assert expect(err).to.exist.and.have.property("message", "No internet!"); done();}); @domenic
    • untestable Downloaderfunction Downloader() { this.download = function (book, cb) { if (!navigator.onLine) { cb(new Error("No internet!")); return; } // ... };} @domenic
    • dependency injection to the rescue!function Downloader(isOnline) { this.download = function (book, cb) { if (!isOnline()) { cb(new Error("No internet!")); return; } // ... };} @domenic
    • app code becomes:var downloader = new Downloader(function () { return navigator.onLine; }); @domenic
    • test code becomes:// Arrangefunction isOnline() { return false; }var downloader = new Downloader(isOnline);var book = { id: "123" };// … @domenic
    • similarly:function Downloader(isOnline, drmService, doDownloadAjax) { this.download = function (book, cb) { // https://gist.github.com/3400303 };} @domenic
    • testing ui is much like testing services, but now you depend on the dom @domenic
    • testing ui codevar TodoView = Backbone.View.extend({ // ... lots of stuff omitted ... events: { "dblclick label": "edit" }, edit: function () { this.$el.addClass("editing"); this.input.focus(); }}); @domenic https://github.com/addyosmani/todomvc/blob/master/architecture-examples/backbone/js/views/todos.js
    • testing ui code: bad testsetupEntireApplication();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
    • testing ui code: good testvar 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
    • when should i write my tests? @domenic
    • let’s talk about code coverage @domenic
    • 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}
    •  when given a highlight and an excerpt  it should return the excerpt with highlighting tags inserted @domenic
    • 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
    • ✓ 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✓ }
    • q: how can we achieve 100% coverage?a: use test-driven development @domenic
    • 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
    • the three steps of tdd red green refactor @domenic
    •  when there are no highlights  the excerpt should pass through unchanged 0/1 tests passed @domenic
    • function highlight(excerpt, highlights) { if (highlights.length === 0) { return excerpt; }} @domenic
    • ✓ function highlight(excerpt, highlights) {◌ if (highlights.length === 0) {✓ return excerpt;✓ }✓ } @domenic
    • ✓ function highlight(excerpt, highlights) {✓ return excerpt;✓ } 1/1 tests passed @domenic
    •  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
    • ✓ 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
    •  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
    • ✓ 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✓ }
    • @domenic
    • ✓ 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
    • 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
    • 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