As developers, how can we improve the feedback loop?
"Those who fail to learn from history are doomed to repeat it." ~ Winston Churchill
Extreme Programming
Extreme Programming is a set of best practices formulated by Kent Beck in 1996, while working on a large payroll redesign at Chrysler Automotive.
Agile
Extreme Programming closely align with the goals of agile. In fact XP could be thought of as the
developer centric parts of the greater agile methodology.
Key Principles of XP/Agile
Code must be written to agreed upon standards (DDD)
Welcome changing requirements
Technical delivery (velocity) should remain consistent and predicable
Deliver working software frequently
Working software is the primary measure of success and working software is defined as that which is tested
Working Software
Let's talk about tests
Testing Pyramid
What are Unit Tests?
A unit test is a piece of code that drives a unit of work and then checks a single assumption about the behavior of that work
Must run in isolation from other units
Must be fast < 5ms
Must produce consistent results regardless of the order the tests were run or the number of times run
photo credit: Sam Hatoum
Isolating a Unit: Test Doubles
Test doubles come in various forms typically at minimium a Stub and a Mock
Stubs wrap existing functions or objects with canned behavior that doesn't call the actual underlying method or object
Mocks are stubs with with pre-programmed expectations
```
describe("getClosestSupportedWidth()", function() {
it("should throw on invalid parameters", function() {
expect(function() {
Responsify.getClosestSupportedWidth('a');
}).to.throw;
expect(function() {
Responsify.getClosestSupportedWidth();
}).to.throw;
});
it("should calculate closest supported width if supportedWidths is set", function() {
Responsify.options.supportedWidths = [100, 500, 1000];
var width = Responsify.getClosestSupportedWidth(777);
expect(width).to.equal(1000);
});
it("should return provided width if no explicit supportedWidths are set", function() {
Responsify.options.supportedWidths = [];
var width = Responsify.getClosestSupportedWidth(777);
expect(width).to.equal(777);
});
});
```
Writing Testable JavaScript
Your primary benefit in writing tests is not the tests themselves, but in the act of writing code that can be tested
We want to validate our initial design decisions
We want to prove that our code delivers the desired functionality
We want to assess that our code is malleable to change
We want to confirm that our code is isolatable
The Single Responsibility Principle is a key tenant to writing testable code
The SRP states that a given context (class, function, variable) should have a single responsbility; that responsibility is defined
as a single reason to change
Common Opportunites for Responsibility Change
Calculation
Mutation
Configuration
Communication
Presentation
Exercize: FizzBuzz
Write a program that prints the numbers from 1 to 100. But for multiples of three print FizzBuzz instead
of the number and for the multiples of five print Buzz. For numbers that are multiples of both three and five print FizzBuzz.
FizzBuzz: Simple Solution
```
var FizzBuzz = (function() {
return {
run: function() {
for (var i = 1; i <= 100; i++) {
if (i % 3 === 0 && i % 5 === 0) {
console.log("FizzBuzz");
} else if (i % 3 === 0) {
console.log("Fizz");
} else if (i % 5 === 0) {
console.log("Buzz");
} else {
console.log(i);
}
}
}
};
})();
```
Testing FizzBuzz: First Try
```
describe("FizzBuzz", function() {
it("should return FizzBuzz for numbers that are multiples of 3 and 5", function() {
FizzBuzz.run(); // presentation and calculation are combined
// ?? impossible to test
});
it("should return Fizz for numbers that are multiples of 3", function() {
// ??
});
it("should return Buzz for numbers that are multiples of 5", function() {
// ??
});
});
```
FizzBuzz: A Better Solution
```
var FizzBuzz = (function() {
return {
// presentation
run: function() {
for (var i = 1; i <= 100; i++) {
console.log(this.calculate(i));
}
},
// calculation
calculate: function(num) {
if (num % 3 === 0 && num % 5 === 0) {
return "FizzBuzz";
} else if (num % 3 === 0) {
return "Fizz";
} else if (num % 5 === 0) {
return "Buzz";
} else {
return num.toString();
}
}
};
})();
```
Testing FizzBuzz: Second Try
```
describe("FizzBuzz", function() {
it("should return FizzBuzz for numbers that are multiples of 3 and 5", function() {
// presentation and calculation are seperated
var value1 = FizzBuzz.calculate(15);
var value2 = FizzBuzz.calculate(30);
expect(value1).to.equal("FizzBuzz");
expect(value2).to.equal("FizzBuzz");
});
it("should return Fizz for numbers that are multiples of 3", function() {
var value1 = FizzBuzz.calculate(3);
var value2 = FizzBuzz.calculate(9);
expect(value1).to.equal("Fizz");
expect(value2).to.equal("Fizz");
});
it("should return Buzz for numbers that are multiples of 5", function() {
var value1 = FizzBuzz.calculate(5);
var value2 = FizzBuzz.calculate(10);
expect(value1).to.equal("Buzz");
expect(value2).to.equal("Buzz");
});
});
```