share responsibility. It's easy to say, “It's not my child, not my community, not my world, not my problem.” Then there are those who see the need and respond. I consider those people my heroes. Fred Rogers
are the characters? (Subject & Collaborators) ▸ What is the setting? (Environment) ▸ What is the plot? (Goal of our system) ▸ What is the conflict? (Task at hand) ▸ What is the resolution? (Solution)
Skill level ▸ Ambiguous intent ▸ Cultural differences ▸ Language differences ▸ Contextual differences ▸ Mood at the time of reading ▸ Knowledge of area/domain of code
named read in a CSV parsing library ▸ Load the entire file into memory into an array ▸ Stream the file, each line as an array ▸ Load the file into a lazily evaluated collection - each row lazily loaded in an array
PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
UserRepositoryTest extends PHPUnit_Framework_TestCase { public function setup() { $this->subject = new UserRepository(); $this->name = ‘Machuga’; $this->subject->save(new User($this->name)); } public function teardown() { $this->subject->clear(); } public function testCanFindAUserByName() { $user = $this->subject->findByName($this->name); $this->assertEquals($this->name, $user->name); } }
OVER XUNIT ▸ Generally written out in sentences ▸ Easily mapped to user stories ▸ Easily mapped to acceptance criteria ▸ Easily readable by stakeholders ▸ Allow for contexts to be described easily ▸ Allow for nested contexts ▸ Handles multiple cases well ▸ I work with them day-to-day
{ const subject = new UserRepository(); const name = ‘Machuga’; beforeEach(function() { subject.save(new User(name)); }); afterEach(function() { subject.clear(); }); it(‘can find a user by name’, function() { const user = subject.findByName(name); expect(user.name).toEqual(name); }); }); const defines a variable that cannot be redefined
{ const subject = new UserRepository(); const name = ‘Machuga’; beforeEach(function() { subject.save(new User(name)); }); afterEach(function() { subject.clear(); }); it(‘can find a user by name’, function() { const user = subject.findByName(name); expect(user.name).toEqual(name); }); }); dot syntax rather than ->
do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end
do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end blocks denoted by { } or do..end
do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end optional syntax
do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end new is a method on the class
do subject { UserRepository.new } let(:name) { ‘Machuga’ } before do subject.save(User.new(name)) end after do subject.clear! end it ‘can find a user by name’ do user = subject.find_by_name(name) expect(user.name).to eq(name) end end can use ! and ? in method names
do it 'should be able to be dragged into categories' do drag_item_to_category_with_text 'apple', 'a-f' expect('apple').to be_in_category_with_text(‘a-f') end end
drag and drop on item by name to category by name def drag_item_to_category_with_text(item_text, category_text) item(item_text).drag_to category_with_text(category_text) end
drag and drop on item by name to category by name def drag_item_to_category_with_text(item_text, category_text) item(item_text).drag_to category_with_text(category_text) end def item(text) sel = “#{item_prefix}_categorizable_selector” page.find(send(sel), text: text) end def category_with_text(text) sel = “#{category_prefix}_category_selector” page.find(send(sel), text: text) end
'items' do it 'should be able to be dragged into categories' do drag_item_to_category_with_text 'apple', 'a-f' expect('apple').to be_in_category_with_text(‘a-f') end end
'items' do it 'should be able to be dragged into categories' do item = page.find( send(#{item_prefix}_categorizable_selector”), text: ‘apple’) category = page.find( send(“#{category_prefix}_category_selector”), text: ‘a-f’) item.drag_to category expect('apple').to be_in_category_with_text(‘a-f') end end
true expected true got false (compared using ==) # ./testing_spec.rb:3:in `block (2 levels) in <top (required)>' Finished in 0.02861 seconds (files took 0.16021 seconds to load) 1 example, 1 failure Failed examples: rspec ./testing_spec.rb:2 # My awesome example group works
4 got: "4" (compared using ==) # ./testing_spec.rb:3:in `block (2 levels) in <top (required)>' Finished in 0.01195 seconds (files took 0.13384 seconds to load) 1 example, 1 failure Failed examples: rspec ./testing_spec.rb:2 # My awesome example group works
Asserts that the response contains the given header * and equals the optional value. * * @param string $headerName * @param mixed $value * @return $this */ protected function seeHeader($headerName, $value = null) { $headers = $this->response->headers; $this->assertTrue($headers->has($headerName), "Header [{$headerName}] not present on response."); if (! is_null($value)) { $this->assertEquals( $headers->get($headerName), $value, "Header [{$headerName}] was found, but value [{$headers->get($headerName)}] does not match [{$value}]." ); } return $this; }
<?php use PHPUnit\Framework\TestCase; abstract class PHPUnit_Framework_Assert { public static function assertTrue($condition, $message = ‘') { self::assertThat($condition, self::isTrue(), $message); } public static function isTrue() { return new PHPUnit_Framework_Constraint_IsTrue; } } class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint { public function matches($other) { return $other === true; } public function toString() { return 'is true'; } }
RSpec::Matchers.define :be_a_multiple_of do |expected| match do |actual| actual % expected == 0 end failure_message do |actual| "expected that #{actual} would be a multiple of #{expected}" end end RSpec.describe 9 do it ‘should blow up and be helpful’ do expect(9).to be_a_multiple_of(4) end end Failures: 1) 9 should blow up and be helpful Failure/Error: expect(9).to be_a_multiple_of(4) expected that 9 would be a multiple of 4
Feature: Getting Laracon attendees test their code In order to convince Laracon attendees to test their code As a speaker I want to provide helpful information and entertainment Scenario: Adam Wathan gives a TDD talk prior to my talk Given that attendees recently saw Adam’s talk And I was in the audience When I go to give my talk Then I can make some assumptions on what attendees know And I can weep as a rearrange my slide deck
describe ‘Convincing Laracon attendees to test their code’ do describe ‘Giving a talk as a speaker’ do context ‘Adam Wathan gives a TDD talk the day before’ do it ‘can make some assumptions on what attendees know’ it ‘can weep while rearranging slide deck’ end end end
application as how it appears to the stakeholder ‣ Feature-driven workflow ‣ Embraces concepts of the Agile Manifesto ‣ Individuals and interactions over processes and tools ‣ Working software over comprehensive documentation ‣ Customer collaboration over contract negotiation ‣ Responding to change over following a plan
your client needs at that moment ▸ Supports slice plans ▸ Waste few resources to test an idea ▸ Provides readable, agreed upon specs for stakeholders ▸ Encourages collaboration
▸ Write failing test first ▸ Evidence feature is incomplete ▸ Evidence feature is not coincidently passing ▸ Write smallest amount of code ▸ Evidence necessary code to solve that use-case is implemented
▸ Write failing test first ▸ Evidence feature is incomplete ▸ Evidence feature is not coincidently passing ▸ Write smallest amount of code ▸ Evidence necessary code to solve that use-case is implemented ▸ Listen to feedback & refactor code & test in pieces ▸ Confidence nothing is broken ▸ Aids in better design
▸ Write failing test first ▸ Evidence feature is incomplete ▸ Evidence feature is not coincidently passing ▸ Write smallest amount of code ▸ Evidence necessary code to solve that use-case is implemented ▸ Listen to feedback & refactor code & test in pieces ▸ Confidence nothing is broken ▸ Aids in better design ▸ Taking small steps ▸ Ensures frequent save points
▸ ~ 3 Million Students ▸ ~ 2 Million Live Help Sessions ▸ ~ 1 Billion Math Problems ▸ Math Content Quantity Comparable to Khan Academy ▸ A gigantic Rails monolith + two SPA’s
Flash ▸ Reimagined & Rewritten in 2013 ▸ Goals of rewrite ▸ Decrease wait time of over 2 minutes ▸ Work on iPad ▸ Improve performance and maintainability ▸ Better pairing of teachers to students
CoffeeScript with Node & Angular ▸ Great Success ▸ Throughput bumped to the current level ▸ Wait times decreased to typically < 15 seconds ▸ Compatible with iPad ▸ Improved pairing students to teachers
Time-based weighting queue ▸ Grows linearly over time ▸ Those who are waiting longer get selected first ▸ Bonus weighting for teachers/students speaking same language ▸ Adds large offset that tapers over time ▸ Offset becomes 0 at threshold ▸ Fastpass match weight - Guaranteed win
WITHIN THRESHOLD Given a language boost threshold of 15000 And EnglishStudent1 has waited 11s And SpanishStudent1 has waited 6s (13.5s) When a Spanish Teacher pulls in another student Then that student will be SpanishStudent1 Weight Points 0 15000 30000 Seconds 0 5 10 15 20 25 30 English Student Spanish Student
WITHIN THRESHOLD Given a language boost threshold of 15000 And EnglishStudent1 has waited 11s And SpanishStudent1 has waited 6s (13.5s) When a Spanish Teacher pulls in another student Then that student will be SpanishStudent1 Weight Points 0 15000 30000 Seconds 0 5 10 15 20 25 30 English Student Spanish Student
PAST THRESHOLD Given a language boost threshold of 15000 And EnglishStudent1 has waited 20s And SpanishStudent1 has waited 15s (15s) When a Spanish Teacher pulls in another student Then that student will be EnglishStudent1 Weight Points 0 15000 30000 Seconds 0 5 10 15 20 25 30 English Student Spanish Student
pair order for Spanish speaking teachers Given this list of four students [es, en, es, en] And all have wait times within the language threshold And each is waiting longer than the previous When the teacher pulls two new students Then the teacher should receive the Spanish speaking students
scoring order for Spanish speaking teachers", function() { beforeEach(function() { this.students = [ this.helper.createSpanishStudent(), this.helper.createEnglishStudent(), this.helper.createSpanishStudent(), this.helper.createStudent() ]; this.matcher = new PairMatcher(this.students); }); it("will prefer new Spanish speaking students", function() { this.offsetStudentTimes(); var match1 = this.matcher.getNextStudentFor(this.spanishTeacher); this.removeStudent(match1); var match2 = this.matcher.getNextStudentFor(this.spanishTeacher); expect(match1.getLanguage()).toBe(‘es’); expect(match2.getLanguage()).toBe('es'); }); }); M. NIGHT SHYMALAN TWIST & NO CHARACTER DEVELOPMENT
▸ Helpers cause side-effects (offsetTimes, removeStudent) ▸ Object under test has dependency mutated after use ▸ Character development happens off-screen
▸ Helpers cause side-effects (offsetTimes, removeStudent) ▸ Object under test has dependency mutated after use ▸ Character development happens off-screen ▸ Expectations are dependent on result of side-effects
▸ Helpers cause side-effects (offsetTimes, removeStudent) ▸ Object under test has dependency mutated after use ▸ Character development happens off-screen ▸ Expectations are dependent on result of side-effects ▸ Three methods of creating a student are used
Spanish Speaking teacher pulling students with the same language Given all wait times are within the threshold And englishStudent1 has been waiting 3s And spanishStudent2 has been waiting 2s And spanishStudent1 has been waiting 1s When the teacher pulls the next student Then the teacher should receive spanishStudent2
the student waiting shortestlongest', function() { const chosenStudent = scorer.findBestMatchFor(sessions, spanishTeacher); expect(chosenStudent).toEqual(spanishStudent2); }); // TODO: This behavior is probably unintended, but the algorithm has not been changed. // It appears as though students who have waited *less* than the ELL threshold are actually // processed in the opposite order than we intend. Once they get beyond the ELL threshold // then wait time correctly influences the student scoring.
Had an English Speaking teacher accepting fast pass ▸ Enqueued a English speaking student ▸ Waited a while ▸ Enqueued another English speaking student w/ fast pass
Had an English Speaking teacher accepting fast pass ▸ Enqueued a English speaking student ▸ Waited a while ▸ Enqueued another English speaking student w/ fast pass ▸ Fast pass ensures second student gets max score, no way to lose
hid this ▸ Forced shorter times to be favored on language match ▸ Was documented as working the other way ▸ Our acceptance test worked because of order dependency ▸ Only two students, last student was under test and won favorability ▸ Putting another student closer could have prevented failure
Favor explicit assertions over potential coincidence ▸ Favor specs that tell the story you need to know ▸ Beware of coincidental truths ▸ Don’t be overly clever with fixtures/fake data
Favor explicit assertions over potential coincidence ▸ Favor specs that tell the story you need to know ▸ Beware of coincidental truths ▸ Don’t be overly clever with fixtures/fake data ▸ Our careers are founded on math - learn more math