Testable JavaScript
Architecting Your Application for
Testability
Mark Ethan Trostler
Google Inc.
twitter: @zzoass
mark@zz...
InfoQ.com: News & Community Site
• 750,000 unique visitors/month
• Published in 4 languages (English, Chinese, Japanese an...
Presented at QCon New York
www.qconnewyork.com
Purpose of QCon
- to empower software development by facilitating the sprea...
Loose
Coupling
Tight
Focus
No
Surprises
What Is Testability?
Minimal
Tedium
Swap
Implementations
Work/Test in
Parallel
Interfaces not Implementation
Write Tests
Once
Interfaces not Implementation
var UserRepository = {
get: function(id) {}
, save: function(user) {}
, getAll: function() {...
Interfaces not Implementation
function test(repo) {
var id = 99, user = { id: id, ... };
repo.save(user);
expect(repo.get(...
Interfaces not Implementation
var UserRepoRedis = function(host, port, opt) {
this.redis = redis.createClient({ ... });
};...
Interfaces not Implementation
● Object is interface - no initialization
● Implementation has constructor with
injected dep...
Interfaces not Implementation
function test(repo) {
var user = { ... };
repo.save(user);
expect(repo.get(id)).toEqual(user...
Interfaces not Implementation
var UserRepoS3 = function(key, secret, bucket) {
this.s3 = knox.createClient({...});
};
User...
Interfaces not Implementation
function test(repo) {
var id = 99, user = { id: id, ... };
repo.save(user);
expect(repo.get(...
Single Responsibility Principle
Every interface should have a single
responsibility, and that responsibility
should be ent...
Interface Segregation Principle
No object should be forced
to depend on methods it
does not use.
Interface Pattern
● Single Responsibility Principle
● Match Sets with Gets
● More Smaller / Fewer Bigger
● Interface Segre...
Using Interfaces
You've created nice interfaces - use them
wisely!
// DO NOT DO THIS!
var UserController = function() {
th...
Using Interfaces
You've created nice interfaces - use them
wisely!
// DO THIS - Inject the dependencies!
var UserControlle...
Liskov Substitution Principle
Any objects that implement
an interface can be used
interchangeably
Constructor Injection
All dependencies should be injected into your
object's constructor*.
● Make dependencies explicit
● ...
Instantiating Implementations
So do all dependees need to provide fully
initialized objects to their dependents when
insta...
Object Creation vs. Object Use
creation
Happens one time at the Composition Root.
All Objects* are created/wired together ...
Object Creation
What creates the objects?
Forces specification of dependencies and
their lifespan
● DIY DI
● wire.js / cuj...
Cross-Cutting Concerns
What about the crap I might need?
● Logging
● Auditing
● Profiling
● Security
● Caching
● ....
"Cro...
Cross-Cutting Concerns
// DO NOT DO THIS:
UserRepoRedis = function(...) {
this.redis = ...
this.logger = new Logger(); // ...
Cross-Cutting Concerns
// DO NOT DO THIS:
UserRepoRedis = function(..., logger, profiler) {
this.redis = ...
this.logger =...
Cross-Cutting Concerns
// DO NOT DO THIS:
UserRepoRedis.prototype.save =
function(user) {
logger.log('Saving user: ' + use...
Cross-Cutting Concerns
● Keep different functionality separate
● Single Responsibility Principle
● Interface Segregation P...
var LoggerFile = function(file) { // Implementation
this.file = file;
}
LoggerFile.prototype = Object.create(Logger);
Logg...
Object Implements UserRepo
MIXIN
Object Implements
UserRepo
METHOD 1
METHOD 3
METHOD 2
METHOD 1
METHOD 4
METHOD 3
Mixin method
// Composition not inheritance
var MIXIN = function(base, extendme) {
var prop;
for (prop in base) {
if (type...
Decorator
var UserRepoLogger = function(repo, logger) {
this.innerRepo = repo;
this.logger = logger;
MIXIN(repo, this); //...
Decorator
// UserRepoLogger will Intercept save
// all other methods will fall thru to
// UserRepoRedis
// This userRepo i...
var ProfilerTime = function() { this.profiles = {}; };
ProfilerTime.prototype = Object.create(Profiler);
ProfilerTime.prot...
Decorator
var UserRepoProfiler = function(repo, profiler) {
this.innerRepo = repo;
this.profiler = profiler;
MIXIN(repo, t...
Intercept
var redisRepo = new UserRepoRedis();
var profiler = new ProfilerTime();
var logger = new LoggerFile();
// Profil...
Testing Decorators
Create the Decorator and Test the Interface:
function testUserRepoLogger(repo) {
var id = 99, user = { ...
Testing Decorators
var repo = new UserRepoMock();
var logger = new LoggerMock();
var profiler = new ProfileMock();
var use...
Decorator Pattern
● Constructor accepts 'inner' Object of the
same type and any other necessary
dependencies.
● Mixin with...
var FindMatchesDistance = f(userRepo) { ... };
FindMatchesDistance.prototype =
Object.create(FindMatches);
var FindMatches...
Runtime/Short-lived Dependencies
// User Controller needs a findMatches
// implementation - but which one???
var UserContr...
var FindMatchFactoryImp = function(repo) {
this.repo = repo;
};
FindMatchFactoryImpl.prototype =
Object.create(FindMatchFa...
Runtime/Short-lived Dependencies
getMatchImplementation = function(type) {
switch(type) {
case 'likes':
return new FindMat...
Runtime/Short-lived Dependencies
var UserController = function(findMatchFactory) {
this.findMatchFactory = findMatchFactor...
Testing Abstract Factories
var matchTypes = [
{ name: 'likes', type: FindMatchesLikes }
, ....
];
test(findMatchFactory) {...
Mocking Abstract Factories
var TestFactoryImpl = function(expectedType, mockMatch) {
this.expectedType = expectedType;
thi...
Abstract Factory Pattern
● Abstract factory translates runtime
parameter -> Dependency
● Factory implementation created at...
Testable JavaScript
● Composition not Inheritance (impl. lock-in)
● Program and Test to Interfaces / Interfaces are
the AP...
Testable JavaScript
Architecting Your Application for
Testability
Mark Ethan Trostler
Google Inc.
twitter: @zzoass
mark@zz...
Ensuring Testability
Watch the video with slide synchronization on
InfoQ.com!
http://www.infoq.com/presentations/javascript
-testing
Testable JavaScriptUntitled
Upcoming SlideShare
Loading in...5
×

Testable JavaScriptUntitled

196

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

Transcript of "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

×