Testable JavaScriptUntitled

241
-1

Published on

Video and slides synchronized, mp3 and slide download available at URL http://bit.ly/1dfXkDX.

Mark Ethan Trostler conducts a tutorial on getting started with JavaScript testing. Filmed at qconnewyork.com.

From humble beginnings as a Computer Science/Philosophy double major at UC San Diego, to working at both startups (IPivot) and large corporations (Qualcomm, Intel, Redback Networks, Juniper Networks, Yahoo, and currently Google), Mark Ethan Trostler has always been dedicated to code quality.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
241
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
0
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Testable JavaScriptUntitled

  1. 1. Testable JavaScript Architecting Your Application for Testability Mark Ethan Trostler Google Inc. twitter: @zzoass mark@zzo.com
  2. 2. InfoQ.com: News & Community Site • 750,000 unique visitors/month • Published in 4 languages (English, Chinese, Japanese and Brazilian Portuguese) • Post content from our QCon conferences • News 15-20 / week • Articles 3-4 / week • Presentations (videos) 12-15 / week • Interviews 2-3 / week • Books 1 / month Watch the video with slide synchronization on InfoQ.com! http://www.infoq.com/presentations /javascript-testing
  3. 3. Presented at QCon New York www.qconnewyork.com Purpose of QCon - to empower software development by facilitating the spread of knowledge and innovation Strategy - practitioner-driven conference designed for YOU: influencers of change and innovation in your teams - speakers and topics driving the evolution and innovation - connecting and catalyzing the influencers and innovators Highlights - attended by more than 12,000 delegates since 2007 - held in 9 cities worldwide
  4. 4. Loose Coupling Tight Focus No Surprises What Is Testability? Minimal Tedium
  5. 5. Swap Implementations Work/Test in Parallel Interfaces not Implementation Write Tests Once
  6. 6. Interfaces not Implementation var UserRepository = { get: function(id) {} , save: function(user) {} , getAll: function() {} , edit: function(id, user) {} , delete: function(id) {} , query: function(query) {} };
  7. 7. Interfaces not Implementation function test(repo) { var id = 99, user = { id: id, ... }; repo.save(user); expect(repo.get(id)).toEqual(user); } Test the interface
  8. 8. Interfaces not Implementation var UserRepoRedis = function(host, port, opt) { this.redis = redis.createClient({ ... }); }; UserRepoRedis.prototype = Object.create(UserRepo); UserRepoRedis.prototype.save = function(user) { ... };
  9. 9. Interfaces not Implementation ● Object is interface - no initialization ● Implementation has constructor with injected dependencies ● Prototype is Object.create(Interface) ● Override with prototype functions
  10. 10. Interfaces not Implementation function test(repo) { var user = { ... }; repo.save(user); expect(repo.get(id)).toEqual(user); } var repo = new UserRepoRedis(host, port, opt); test(repo); Test the interface
  11. 11. Interfaces not Implementation var UserRepoS3 = function(key, secret, bucket) { this.s3 = knox.createClient({...}); }; UserRepoS3.prototype = Object.create(UserRepo); UserRepoS3.prototype.save = function(user) { ... }
  12. 12. Interfaces not Implementation function test(repo) { var id = 99, user = { id: id, ... }; repo.save(user); expect(repo.get(id)).toEqual(user); } var repo = new UserRepoS3(key, secret, bucket); test(repo); Test the interface
  13. 13. Single Responsibility Principle Every interface should have a single responsibility, and that responsibility should be entirely encapsulated by the interface. Responsibility = Reason To Change
  14. 14. Interface Segregation Principle No object should be forced to depend on methods it does not use.
  15. 15. Interface Pattern ● Single Responsibility Principle ● Match Sets with Gets ● More Smaller / Fewer Bigger ● Interface Segregation Principle ● Test and Program to Interface Only
  16. 16. Using Interfaces You've created nice interfaces - use them wisely! // DO NOT DO THIS! var UserController = function() { this.userRepo = new UserRepoRedis(); };
  17. 17. Using Interfaces You've created nice interfaces - use them wisely! // DO THIS - Inject the dependencies! var UserController = function(userRepo) { this.userRepo = userRepo; };
  18. 18. Liskov Substitution Principle Any objects that implement an interface can be used interchangeably
  19. 19. Constructor Injection All dependencies should be injected into your object's constructor*. ● Make dependencies explicit ● Loose coupling ● Cannot instantiate a non-usable Object * Except: ● runtime dependencies ● objects with a shorter lifetime
  20. 20. Instantiating Implementations So do all dependees need to provide fully initialized objects to their dependents when instantiating them? NO - THEY DO NOT INSTANTIATE THEM! Object Creation vs. Object Use ● ensures a loosely coupled system ● ensures testability
  21. 21. Object Creation vs. Object Use creation Happens one time at the Composition Root. All Objects* are created/wired together at this time ● startup ● request use Happens all the time throughout the application
  22. 22. Object Creation What creates the objects? Forces specification of dependencies and their lifespan ● DIY DI ● wire.js / cujojs ● Inverted ● Intravenous ● AngularJS whole lotta others...
  23. 23. Cross-Cutting Concerns What about the crap I might need? ● Logging ● Auditing ● Profiling ● Security ● Caching ● .... "Cross-cutting concerns"
  24. 24. Cross-Cutting Concerns // DO NOT DO THIS: UserRepoRedis = function(...) { this.redis = ... this.logger = new Logger(); // or Logger.get() this.profiler = new Profiler(); // or Profiler.get() }; ● tightly coupled to Logger & Profiler implementations ● PITA to test
  25. 25. Cross-Cutting Concerns // DO NOT DO THIS: UserRepoRedis = function(..., logger, profiler) { this.redis = ... this.logger = logger; this.profiler = profiler; }; Injection - so looks good BUT violating SRP!
  26. 26. Cross-Cutting Concerns // DO NOT DO THIS: UserRepoRedis.prototype.save = function(user) { logger.log('Saving user: ' + user); profiler.startProfiling('saveUser'); ... do redis/actual save user stuff ... profiler.stopProfiling('saveUser'); logger.log('that took: ' + (start - end)); };
  27. 27. Cross-Cutting Concerns ● Keep different functionality separate ● Single Responsibility Principle ● Interface Segregation Principle
  28. 28. var LoggerFile = function(file) { // Implementation this.file = file; } LoggerFile.prototype = Object.create(Logger); LoggerFile.prototype.log = function(msg) { this.file.write(msg); }; Logging Interface and Implementation var Logger = { // Interface log: function(msg) {} , getLastLog: function() {} // get it out! };
  29. 29. Object Implements UserRepo MIXIN Object Implements UserRepo METHOD 1 METHOD 3 METHOD 2 METHOD 1 METHOD 4 METHOD 3
  30. 30. Mixin method // Composition not inheritance var MIXIN = function(base, extendme) { var prop; for (prop in base) { if (typeof base[prop] === 'function' && !extendme[prop]) { extendme[prop] = base[prop].bind(base); } } };
  31. 31. Decorator var UserRepoLogger = function(repo, logger) { this.innerRepo = repo; this.logger = logger; MIXIN(repo, this); // Mixin repo's methods }; UserRepoLogger.prototype.save = function(user) { this.logger.log('Saving user: ' + user); return this.innerRepo.save(user); };
  32. 32. Decorator // UserRepoLogger will Intercept save // all other methods will fall thru to // UserRepoRedis // This userRepo implements the UserRepo // interface therefore can be used anywhere var userRepo = new UserRepoLogger( new UserRepoRedis() , new LoggerFile() );
  33. 33. var ProfilerTime = function() { this.profiles = {}; }; ProfilerTime.prototype = Object.create(Profiler); ProfilerTime.prototype.start = function(id) { this.profiles[id] = new Date().getTimestamp(); }; ProfilerTime.prototype.stop = function(id) { ... }; .... Profile Interface and Implementation var Profiler = { // Interface start: function(id) {} , stop: function(id) {} , getProfile: function(id) {} // get it out! };
  34. 34. Decorator var UserRepoProfiler = function(repo, profiler) { this.innerRepo = repo; this.profiler = profiler; MIXIN(repo, this); // Mixin repo's methods }; UserRepoProfiler.prototype.save = f(user) { this.profiler.start('save user'); this.innerRepo.save(user); this.profiler.stop('save user'); };
  35. 35. Intercept var redisRepo = new UserRepoRedis(); var profiler = new ProfilerTime(); var logger = new LoggerFile(); // Profiling UserRepo var userRepoProf = new UserRepoProfiler(redisRepo, profiler); // Logging Profiling UserRepo var userRepo = new UserRepoLogger(userRepoProf, logger);
  36. 36. Testing Decorators Create the Decorator and Test the Interface: function testUserRepoLogger(repo) { var id = 99, user = { id: id, ... }, loggerMock = new LoggerMock(), testRepo = new UserRepoLogger(repo, loggerMock); testRepo.save(user); // verify loggerMock }
  37. 37. Testing Decorators var repo = new UserRepoMock(); var logger = new LoggerMock(); var profiler = new ProfileMock(); var userRepo = new UserRepoProfile( new UserRepoLogger(repo, logger), profiler); // test UserRepo interface testRepo(userRepo); // verify logger and profiler mocks
  38. 38. Decorator Pattern ● Constructor accepts 'inner' Object of the same type and any other necessary dependencies. ● Mixin with 'inner' Object to get default behavior for non-decorated methods. ● Decorate necessary interface methods and (optionally) delegate to 'inner' Object.
  39. 39. var FindMatchesDistance = f(userRepo) { ... }; FindMatchesDistance.prototype = Object.create(FindMatches); var FindMatchesActivites = f(userRepo) { ... }; var FindMatchesLikes = f(userRepo) { ... }; Runtime/Short-lived Dependencies var FindMatches = { matches: function(userid) {} };
  40. 40. Runtime/Short-lived Dependencies // User Controller needs a findMatches // implementation - but which one??? var UserController = function(findMatches, ...) { .... } Inject all three?? What if I make more? What if other classes need a dynamic findMatch implementation?
  41. 41. var FindMatchFactoryImp = function(repo) { this.repo = repo; }; FindMatchFactoryImpl.prototype = Object.create(FindMatchFactory); Runtime/Short-lived Dependencies var FindMatchFactory = { getMatchImplementation: function(type) {} };
  42. 42. Runtime/Short-lived Dependencies getMatchImplementation = function(type) { switch(type) { case 'likes': return new FindMatchesLikes(this.repo); break; .... default: return new FindMatchesActivites(this.repo); } };
  43. 43. Runtime/Short-lived Dependencies var UserController = function(findMatchFactory) { this.findMatchFactory = findMatchFactory; }; UserController.prototype = Object.create(Controller); UserController.prototype.findMatches = function(type, uid) { var matcher = this.findMatchFactory.getMatchImplementation(type); return matcher.matches(uid); };
  44. 44. Testing Abstract Factories var matchTypes = [ { name: 'likes', type: FindMatchesLikes } , .... ]; test(findMatchFactory) { matchTypes.forEach(function(type) { expect( findMatchFactory .getMatchImplementation(type.name) .toEqual(type.type); }); }
  45. 45. Mocking Abstract Factories var TestFactoryImpl = function(expectedType, mockMatch) { this.expectedType = expectedType; this.mockMach = mockMatch; }; TestFactoryImpl.prototype = Object.create(FindMatchFactory); TestFactoryImpl.prototype.getMatchImplementation(type) { expect(type).toEqual(this.expectedType); return this.mockMatch; };
  46. 46. Abstract Factory Pattern ● Abstract factory translates runtime parameter -> Dependency ● Factory implementation created at Composition Root along with everything else ● Inject factory implementation into the Constructor for runtime dependencies ● Object uses injected factory to get Dependency providing Runtime Value
  47. 47. Testable JavaScript ● Composition not Inheritance (impl. lock-in) ● Program and Test to Interfaces / Interfaces are the API ● Create lots of small Single Responsibility Interfaces ● Decorate and Intercept Cross-Cutting Concerns ● Constructor Inject all Dependencies ● Inject Abstract Factory for run-time Dependencies
  48. 48. Testable JavaScript Architecting Your Application for Testability Mark Ethan Trostler Google Inc. twitter: @zzoass mark@zzo.com
  49. 49. Ensuring Testability
  50. 50. Watch the video with slide synchronization on InfoQ.com! http://www.infoq.com/presentations/javascript -testing

×