by Alex Chaffee
alexch @ gmail.com
An assertion is a claim about the code
Example:
Set set = new MySet();
set.add("ice cream");
assertTrue(set.contains("ice cream"));
In RSpec, "assert" is called "should" or "expect"
In Jasmine/Jest, "assert" is called "expect...to"
Start with the assert
assertTrue(set.contains("cake"));
Then add the code above it
Set set = new MySet();
set.add("cake");
Helps focus on goal
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." – Martin Fowler
Some tricks to get started:
"If you can't write a test, then you don't know what the code should do. And what business do you have writing code in the first place when you can't say what it's supposed to do?" - Rob Mee
If you don't know what test to write, then start with a spike.
A "spike" is an experiment, a proof of concept, a playground.
Code until you feel confident that you understand the general shape of the solution.
Then put your spike aside and write it again test-first.
Quite a lot of overlap, but worth keeping difference in mind
Test everything that could possibly break
Step one:
function testSum() {
assertEquals(4, plus(3,1));
}
plus(x, y) {
return 4;
}
Step two:
function testSum() {
assertEquals(4, plus(3,1));
assertEquals(5, plus(3,2));
}
function plus(x, y) {
return x + y;
}
SetTest.testEmpty
SetTest.testShouldHaveSizeZeroWhenEmpty
EmptySetTest.testHasSizeZero
nested "describe" blocks can help too...(see later slide)
Assertion messages can be confusing
Example: assertTrue("set is empty", set.isEmpty());
FAILURE: set is empty
mean Solution: should statements
assertTrue("set should be empty", set.isEmpty())
or even better:
assertTrue("a newly-created set should be empty", set.isEmpty())
describe('Set', ()=> {
let set;
describe('when first created', ()=> {
beforeEach(()=> {
set = new Set();
});
it('should exist', ()=>{
expect(set).toBeTruthy();
})
it('should be empty', ()=> {
expect(set.size()).toBe(0);
});
});
});
Output:
Set
when first created
✓ should exist
✓ should be empty
let
and subject
and context
Increase test readability by clarifying your input and output values
assertEquals(86400, new Day().getSeconds())
vs.
assertEquals(60 * 60 * 24, new Day().getSeconds())
vs.
secondsPerMinute = 60
minutesPerHour = 60
hoursPerDay = 24
assertEquals(secondsPerMinute * minutesPerHour * hoursPerDay,
new Day().getSeconds())
%w(a e i o u).each do |letter|
it "#{letter} is a vowel" do
assertTrue(letter.vowel?)
end
end
public void testUnknownCountry() {
try {
currencyConverter.getRate("Snozistan");
fail("Should have thrown an exception for unknown country");
} catch (UnknownCountryException e) {
// ok
}
}
The empty catch
block is fine here, since here an exception is a success, not a failure to be handled.
Jasmine has a built-in way to test exceptions:
expect( function(){ parser.parse(bogus); } )
.toThrow(new Error("Parsing is not possible"));
"Regression tests are tests that you would have written originally." - Kent Beck
Difficult quest, but worth it
Don't get stuck in molasses!
Corey Haines has some great tips for keeping Rails tests fast
Q: What to do when you have an existing untested codebase?
A: Start small!
Write tests before attempting refactoring
Usually easier to write characterization tests (UI/integration/Selenium)
A Test Double replaces the "real" instance of an object used by the production code with something suitable for the currently running test, but with the same interface.
Test Doubles in general are often called Mock Objects, but there's also a specific technical type of double called a Mock.
A very useful test double
In ruby:
@fake_time = Time.now
Time.stub(:now) { @fake_time }
In Jasmine (built in, see the docs for more details):
it("causes a timeout to be called synchronously", function() {
let timerCallback = jasmine.createSpy("timerCallback");
setTimeout(function() {
timerCallback();
}, 100);
expect(timerCallback).not.toHaveBeenCalled();
jasmine.clock().tick(101);
expect(timerCallback).toHaveBeenCalled();
});
a way of designing your objects to be more isolated and more testable
Pass in dependencies to the constructor
An object under test will receive references to all external services
Allows tests to inject Test Doubles at will
Forces objects to be isolated
Thanks!
1/73