How to Test Asynchronous Code (v2)


Published on

Slides from my unit testing talk at JSBerlin on May 19.

Published in: Technology
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

How to Test Asynchronous Code (v2)

  1. 1. How to TestAsynchronous Code by Felix Geisendörfer 19.05.2011 (v2)
  2. 2. @felixgeTwitter / GitHub / IRC
  3. 3. Core Contributor & Module Author node-mysql node-formidable- Joined the mailing list in June 26, 2009- When I joined there where #24 people- Now mailing list has close to 4000 members members- First patch in September 2009
  4. 4. - Disclaimer: Don’t listen to me : )
  5. 5. The current approachtest(something, function(done) { doSomethingAsync(function() { assert.equals(...); done(); });});
  6. 6. The current approachtest(something, function(done) { doSomethingAsync(function() { assert.equals(...); done(); });}); If you have multiple tests, how will exceptions be linked?
  7. 7. db.query(SELECT A, function() { db.query(SELECT B, function() { db.query(SELECT C, function() { db.query(SELECT D, function() { // WTF }); }); }); });- Who here is running node in production?- Raise hands if you have a test suite for your node.js projects- Raise hands if you are happy with it- But why is it so difficult?
  8. 8. test(something, function(done) { doSomethingAsync(function() { assert.equals(...); done(); }); }); F%$! that shitForget that shit
  9. 9. - We looked at asynchronous testing frameworks- We really wanted to do TDD- But everything we tried was hard
  10. 10. Take out the I/O
  11. 11. Stub / Mock Dependencies
  12. 12. Synchronous Tests
  13. 13. Unit TestsIf you have nested callbacks (or any other I/O) in your tests, you are not unit testing
  14. 14. Microtests• It is short, typically under a dozen lines of code.• It is always automated.• It does not test the object inside the running app, but instead in a purpose-built testing application.• It invokes only a tiny portion of the code, most usually a single branch of a single function.• It is written gray-box, i.e. it reads as if it were black-box, but sometimes takes advantage of white-box knowledge. (Typically a critical factor in avoiding combinatoric issues.)• It is coded to the same standard as shipping code, i.e. the team’s best current understanding of coding excellence.• It is vault-committed source, with a lifetime co-terminous with the functionality it tests.• In combination with all other microtests of an app, it serves as a ‘gateway-to-commit’. That is, a developer is encouraged to commit anytime all microtests run green, and discouraged (strongly, even nastily) to commit otherwise.• It takes complete control of the object-under-test and is therefore self-contained, i.e. running with no dependencies on anything other than the testing code and its dependency graph.• It runs in an extremely short time, milliseconds per test.• It provides precise feedback on any errors that it encounters.• It usually (not always) runs entirely inside a single computer.• It usually (not always) runs entirely inside a single process, i.e. with few extra-process runtime dependencies.• It is part of a collection all or any subset of which is invokable with a single programmer gesture.• It is written before the code-change it is meant to test.• It avoids most or all usage of ‘awkward’ collaborators via a variety of slip-and-fake techniques.• It rarely involves construction of more than a few classes of object, often one or two, usually under five.
  15. 15. Approach • Load SUT in a Sandbox using v8 / node.js • Inject dependencies using the global object of the sandbox • Don’t use test doubles for internals, only for peersInternal: An object that has the same life span as its hostPeers: Something that is being passed in / out of the SUT
  16. 16. Test Doubles • Dummy Object • Test Stub • Test Spy • Mock Object • Fake Object • Temporary Test Stub,%20Fakes,%20Stubs%20and%20Dummies.html
  17. 17. node-fakevar fake = require(fake)();var object = {};fake.expect(object, method);object.method();
  18. 18. node-fakevar fake = require(fake)();var object = {};var objectMethodCall = fake.stub(object, method);object.method();assert.equals(objectMethodCall.calls.length, 1);
  19. 19. node-fakevar fake = require(fake)();var MyClass = fake.class(MyClass);fake .expect(new, MyClass) .withArgs(1, 2);var myClass = new MyClass(1, 2);
  20. 20. node-microtestvar fs = require(fs);module.exports = function(path) { var file = fs.createReadStream(path); file.pipe(process.stdout);}; cat.js
  21. 21. node-microtestvar test = require(microtest).module(cat.js);test.requires(fs);test.context.process = { stdout: test.object(stdout),};var cat = test.compile(); test-cat.js
  22. 22. node-microtesttest.describe(cat, function() { var PATH = test.value(path); var file = test.object(file); test .expect(test.required.fs, createReadStream) .withArgs(PATH) .andReturn(file); test .expect(file, pipe) .withArgs(test.context.process.stdout); cat(PATH);}); test-cat.js
  23. 23. BenefitsWe take the position that the real benefit of extensivemicrotest-driven development isnt higher quality atall. Higher quality is a side effect of TDD. Rather, thebenefit and real purpose of TDD as we teach it issheer productivity: more function faster. Mike Hill
  24. 24. Benefits• Simpler implementations• Short change / verification cycles• Tests for edge cases (error handling, race conditions, etc.)
  25. 25. There really are only two acceptablemodels of development: "think andanalyze" or "years and years oftesting on thousands of machines". Linus Torvalds
  26. 26. Disadvantages • Requires gray / white box knowledge of implementation • Lots of test code needs to be written • Sometimes feels awkward
  27. 27. node-mysql Lines of code 1.700 1.275 850 425 0 library tests library vs test code: 1x : 1.35xLibrary: 1240 LoC (+600 LoC MySql constants)Tests:Tests: 1673 LoC
  28. 28. node-mysql Lines of code 1.300 975 650 325 0 integration micro integration vs micro tests: 1x : 2.89xMicro Tests: 1243 LoCIntegration tests: 430 LoC
  29. 29. node-mysql Assertions 400 300 200 100 0 integration micro integration vs micro tests: 1x : 8.15xMicro Tests: 375 assertsIntegration tests: 46 asserts
  30. 30. Transloadit Lines of code 13.000 9.750 6.500 3.250 0 library tests library vs test code: 1x : 2.04xLibrary: 6184 LoCTests: 12622 LoCNot included: Fixture data, server configuration, customer website, dependencies wedeveloped
  31. 31. Transloadit Lines of code 10.000 7.500 5.000 2.500 0 integration micro integration vs. micro tests: 1x : 4.30xIntegration tests: 2254 LoCMicro Tests: 9695 LoC
  32. 32. Transloadit Assertions 3.000 2.250 1.500 750 0 integration micro integration vs. micro tests: 1x : 12.30xIntegration tests: 189 assertsMicro Tests: 2324 asserts
  33. 33. tl;dr• Don’t use integration tests to show the basic correctness of your software.• Write more microtests.
  34. 34. Questions?
  35. 35. Node.js Workshop Use discount code ‘berlinjs’ for 10% off