Test First Teaching - GoGaRuCo 2010


Published on

Alex Chaffee and Sarah Allen talk about testfirst.org and test first teaching in general at Golden Gate Ruby Conference 2010

1 Like
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

  • TFT is not sufficient for learning, but needs to be one component of a curriculum or course of self-study.

  • Test First Teaching - GoGaRuCo 2010

    1. Test First Teaching Alex Chaffee @alexch Sarah Allen @ultrasaurus
    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
    5. Pairing Is Teaching
    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/
    8. How do we know it's a good idea? it works
    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/
    13. writing code is engaging
    14. Methodology • Run the test • Watch it fail • Write code to fix the first failure • See it pass • Refactor Sound familiar?
    15. 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
    16. Embrace Failure
    17. 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
    18. 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
    19. TFT Examples Let's look at some code
    20. 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
    21. 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
    22. 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
    23. 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
    24. 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
    25. 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
    26. 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)
    27. 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
    28. But... that's impossible
    29. Solutions for Challenging Idioms blocks time method missing builder pattern
    30. 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
    31. 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
    32. 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()); }
    33. ruby koans • self-guided, test-driven • Ruby language basics • very fun, whimsical and elegant
    34. 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
    35. 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
    36. 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).
    37. 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)
    38. Sarah Blazing Cloud
    39. Alex alexch.github.com @alexch Erector Moodlog Cohuman Wrong
    40. Learning should be fun • Questions?