2. Why should you care?
• you want to learn Ruby
• you want to improve your Ruby skills
• you have a friend or colleague who wants to
learn Ruby
• you want to help us improve our materials
• by teaching, you learn...
3. No, seriously:
by teaching, you learn!
• the best engineers are good teachers
• we live and work in collaborative
environments
• it is not enough to know any thing well
• we must teach in order to effectively produce
software
4. What is
Test-First Teaching?
• teacher provides microtests
• student makes them pass
• one test at a time
• can be used guided (in classroom) or solo
• or with a pair
6. Pairing in the classroom
• students learn together and teach each other
• each pair can proceed through exercises at
their own pace
• teacher is freed to wander the room
7. How do we know it's a
good idea?
2002 Alex Chaffee
jGuru Java curriculum
2005 Mike Clark
many Ruby Learning Tests
independent 2006 ara.t.howard
inventors Ruby Quiz #67
"Metakoans"
2008 Yehuda Katz
& Matt Aimonetti
Ruby on Rails training
Who else?
http://www.flickr.com/photos/annais/9335897/sizes/z/
9. Learning Ruby via Tests
• [Test-First Teaching](http://testfirst.org)
by Sarah Allen and Alex Chaffee
• [Ruby Koans](http://rubykoans.com)
by Jim Weirich and Joe O’Brien
• [Metakoans](http://rubyquiz.com/quiz67.html)
by ara.t.howard
10. Other Guided Learning
• [ruby-warrior](http://github.com/ryanb/ruby-warrior) by Ryan
Bates - a game written in Ruby for learning Ruby
• [Try Ruby](http://tryruby.org) runs a Ruby interpreter in your
browser, with hints and advice
• [Growing OO Software In Ruby](http://www.exampler.com/
blog/2009/12/17/growing-object-oriented-software-in-ruby/) by
Brian Marick
• Ruby version of [Growing Object-Oriented Software
Guided by Tests](http://www.growing-object-oriented-
software.com/)
11. Created by:
Sarah Allen
Alex Chaffee
Liah Hansen
and friends
Test-First Teaching....
http://testfirst.org
Maybe we should call it Test-First Learning
http://github.com/ultrasaurus/test-first-teaching
12. Traditional Professional
Programming Classes
Big, Boring Lecture
Followed by Exercises
• multiple choice
• fill in the blanks with words
or pseudocode
• skeleton code - big program
with chunks excised and
replaced with comments
• large task - soup to nuts
without feedback
.flickr.com/photos/chasephotography/3890300709/
14. Methodology
• Run the test
• Watch it fail
• Write code to fix the first failure
• See it pass
• Refactor
Sound familiar?
16. Why TFT?
• makes the assignment very clear
• student gets immediate feedback on progress
(or lack thereof)
• removes the magic
• leads the student through all the steps to
writing the code
• teaches student to read errors
18. Embrace Failure
• start from a point of failure
• it feels like it's not your fault
• people learn better when they're not stressed
• playfulness enhances learning
19. In a classroom setting,
do more...
• Conceptual Overview
• Experimentation (Play, irb)
• Live Coding a Real-World Example
• Simple hands-on exercise
• Name what they learned
21. Arithmetic
require "calculator"
describe Calculator do
before do
@calculator = Calculator.new
end
it "adds 0 and 0" do
@calculator.add(0,0).should == 0
end
it "adds 2 and 2" do
@calculator.add(2,2).should == 4
end
it "adds positive numbers" do
@calculator.add(2,6).should == 8
end
it "subtracts numbers" do
@calculator.subtract(10,4).should == 6
end
end
23. require "pig_latin"
describe "#translate" do
Strings
include PigLatinTranslator
it "should translate a simple word" do
s = translate("nix")
s.should == "ixnay"
end
it "should translate a word beginning with a vowel" do
s = translate("apple")
s.should == "appleay"
end
it "should translate a word with two consonants" do
s = translate("stupid")
s.should == "upidstay"
end
it "should translate two words" do
s = translate("eat pie")
s.should == "eatay iepay"
end
it "should translate many words" do
s = translate("the quick brown fox")
s.should == "ethay ickquay ownbray oxfay"
end
end
24. Pig Latin Solution
module PigLatinTranslator
def translate(s)
s.split.map do |word|
v = first_vowel(word)
word.slice(v..-1) + word[0,v] + "ay"
end.join(" ")
end
def first_vowel(word)
if word =~ /^qu/
2
else
word.gsub(/[aeiou].*$/, '').size
end
end
end
25. Another
Pig Latin Solution
module PigLatinTranslator
def translate(s)
words = s.split
s = words.map do |s|
l = s.length
if /^[aeiou]/ .match(s)
s + "ay"
elsif /^qu/ .match(s[0..1])
s[2..(l+1)] + s[0..1] + "ay"
elsif /[aeiou]/ .match(s[1..1])
s[1..(l+1)] + s[0..0] + "ay"
else
s[2..(l+1)] + s[0..1] + "ay"
end
end
s = s.join(" ")
end
end
26. And Another
Pig Latin Solution
module PigLatinTranslator
def translate(word)
words = word.split(" ")
arrResult = []
words.each do |word|
m = word.match(/^(qu)*[^aeiou]*/)
if(m.nil?)
arrResult << add_ay(word)
else
arrResult << add_ay(m.post_match + m.to_s)
end
end
arrResult.join(" ")
end
def add_ay(word)
word + "ay"
end
end
27. Iterators
describe Calculator do
before do
@calculator = Calculator.new
end
describe "#sum" do
it "computes the sum of an empty array" do
@calculator.sum([]).should == 0
end
it "computes the sum of an array of one number" do
@calculator.sum([7]).should == 7
end
it "computes the sum of an array of two numbers" do
@calculator.sum([7,11]).should == 18
end
it "computes the sum of an array of many numbers" do
@calculator.sum([1,3,5,7,9]).should == 25
end
end
28. Iterators
require "array_extension"
describe Array do
describe "#sum" do
it "should be 0 for an empty array" do
[].sum.should == 0
end
it "should add all of the elements" do
[1,2,4].sum.should == 7
end
end
end
(and open classes)
29. TDD Extra Credit!
# Test-Driving Bonus: once the above tests pass,
# write tests and code for the following:
it "multiplies two numbers"
it "multiplies an array of numbers"
it "raises one number to the power of another number"
# http://en.wikipedia.org/wiki/Factorial
describe "#factorial" do
it "computes the factorial of 0"
it "computes the factorial of 1"
it "computes the factorial of 2"
it "computes the factorial of 5"
it "computes the factorial of 10"
end
end
32. Blocks
require "performance_monitor"
(and mocks) it "takes exactly 1 second to run a block that
describe PerformanceMonitor do sleeps for 1 second (with stubs)" do
before do fake_time = 100
@monitor = PerformanceMonitor.new Time.stub!(:now).and_return {fake_time}
end @monitor.run do
fake_time += 1
it "takes about 0 seconds to run an empty block" do end.should == 1
@monitor.run do end
end.should be_close(0, 0.1)
end it "runs a block N times" do
n = 0
it "takes exactly 0 seconds to run an empty block @monitor.run(4) do
(with stubs)" do n += 1
Time.stub!(:now).and_return(100) end
@monitor.run do n.should == 4
end.should == 0 end
end
it "returns the average time, not the total time,
it "takes about 1 second to run a block that sleeps when running multiple times" do
for 1 second" do run_times = [8,6,5,7]
@monitor.run do run_index = 0
sleep 1 fake_time = 100
end.should be_close(1, 0.1) Time.stub(:now).and_return { fake_time }
end @monitor.run(4) do
fake_time += run_times[run_index]
run_index += 1
end.should == 6
end
end
33. method_missing, nested
closures, and the builder pattern
require "xml_document"
it "nests several levels" do
describe XmlDocument do @xml.hello do
before do @xml.goodbye do
@xml = XmlDocument.new @xml.come_back do
end @xml.ok_fine(:be => "that_way")
end
it "renders an empty tag" do end
@xml.hello.should == "<hello/>" end.should ==
end "<hello><goodbye><come_back><ok_fine be='that_way'/
></come_back></goodbye></hello>"
it "renders a tag with attributes" do end
@xml.hello(:name => 'dolly').should == "<hello
name='dolly'/>" it "indents" do
end @xml = XmlDocument.new(true)
@xml.hello do
it "renders a randomly named tag" do @xml.goodbye do
tag_name = (1..8).map{|i| @xml.come_back do
('a'..'z').to_a[rand(26)]}.join @xml.ok_fine(:be => "that_way")
@xml.send(tag_name).should == "<#{tag_name}/>" end
end end
end.should ==
it "renders block with text inside" do "<hello>n" +
@xml.hello do " <goodbye>n" +
"dolly" " <come_back>n" +
end.should == "<hello>dolly</hello>" " <ok_fine be='that_way'/>n" +
end " </come_back>n" +
" </goodbye>n" +
it "nests one level" do "</hello>n"
@xml.hello do end
@xml.goodbye end
end.should == "<hello><goodbye/></hello>"
end
34. threads
(sorry for the Java)
public void testThreadSafe() throws InterruptedException
{
int DEPOSITORS = 50;
int AMOUNT = 2;
// note: increase this value until it *fails* on your CPU.
// Then fix it.
int REPS = 25000;
Account account = new Account("Joe", 0);
Thread[] depositors = new Thread[DEPOSITORS];
for (int i=0; i< DEPOSITORS; ++i) {
depositors[i] = new Depositor(account, AMOUNT, REPS);
depositors[i].start();
}
for (int i=0; i< DEPOSITORS; ++i) {
depositors[i].join();
}
assertEquals(REPS * DEPOSITORS * AMOUNT, account.getBalance());
}
35. ruby koans
• self-guided, test-driven
• Ruby language basics
• very fun, whimsical and elegant
36. ruby koans example
require File.expand_path(File.dirname(__FILE__) + '/edgecase')
class AboutStrings < EdgeCase::Koan
def test_double_quoted_strings_are_strings
string = "Hello, World"
usually self-
assert_equal __, string.is_a?(String)
end contained
def test_single_quoted_strings_are_also_strings
just tests and fixtures,
string = 'Goodbye, World' with no class declaration
assert_equal __, string.is_a?(String)
end
def test_use_single_quotes_to_create_string_with_double_quotes
“fill in the
string = 'He said, "Go Away."'
assert_equal __, string blanks”
end
technique
def test_use_double_quotes_to_create_strings_with_single_quotes
string = "Don't"
assert_equal __, string
end
teaching through
def test_use_backslash_for_those_hard_cases practice and
a = "He said, "Don't""
b = 'He said, "Don't"' challenge
assert_equal __, a == b
end
37. TFT != TDD
• Mechanics of testing are hard to learn
• TFT teaches programming; TDD is design
• At the end of some modules,
students write their own tests for “extra
credit”
• doesn’t really flex the creative muscles
required for software design
38. What about TDD?
• easier to learn TDD, post-TFT
• know the language
• know the test framework
• used to the rhythm of test-first
• study design patterns, or check out [GOOS]
(http://www.exampler.com/blog/2009/12/17/
growing-object-oriented-software-in-ruby).
39. Credits
• Mr. Clean® is a registered trademark of Procter &
Gamble, used without permission
• Parody is fair use!
• Fail Whale illustrated by Yiying Lu (http://
www.yiyinglu.com/)
• Pair Programming photos by Lee Lundrigan
• Thank you Flickr and Creative Commons
(see slides for attribution)