Upload
trantram
View
217
Download
2
Embed Size (px)
Citation preview
XP Practices
!"#$%&'(
)'#$#
*+%,'
)'-&
.,-//0/1
2-&'
3&-,,
4','-#'#
!%50/1
3$-/5-(5
3"#$-0/-6,'
.-7'
8'$-9+%(
!%/$0/"%"#
:/$'1(-$0%/
!%,,'7$0;'
<=/'(#+09 )'#$>?(0;'/
?';',%9&'/$
4'@-7$%(0/130&9,'
?'#01/
.-0(
.(%1(-&&0/1
Copyright © 1999-2006, Ronald E Jeffries, xprogramming.comused with permission
!"#$%&'(
)'#$#
*+%,'
)'-&
.,-//0/1
2-&'
3&-,,
4','-#'#
!%50/1
3$-/5-(5
3"#$-0/-6,'
.-7'
8'$-9+%(
!%/$0/"%"#
:/$'1(-$0%/
!%,,'7$0;'
<=/'(#+09 )'#$>?(0;'/
?';',%9&'/$
30&9,'
?'#01/
.-0(
.(%1(-&&0/1
4'@-7$%(0/1
Our Focus
Copyright © 1999-2006, Ronald E Jeffries, xprogramming.comused with permission
A Very Quick Intro to TDD
• A Special Thanks to Steven Baker:
• He nailed it at Canada on Rails.
• Rewriting these slides felt like a violation of DRY.
Redclass AccountTest < Test::Unit::TestCase def test_new account = Account.new assert_equal 0, account.balance endend
E
1) Error:test_new:NameError: uninitialized constant Account ./test/unit/account_test.rb:6:in `test_new'
1 tests, 0 assertions, 0 failures, 1 errors
class Account < ActiveRecord::Baseend
# ...plus a migration with balance field
Green
.Finished in 0.055268 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
Redclass AccountTest < Test::Unit::TestCase def test_deposit account = Account.new account.deposit(10) assert_equal 10, account.balance endend
.E 1) Error:test_deposit(DepositTest):NameError: undefined method `deposit' for #<Account...>./test/unit/account_test.rb:12:in `test_deposit'
2 tests, 1 assertions, 0 failures, 1 errors
Redclass Account < ActiveRecord::Base def deposit(amount)
end end
.F
1) Failure:test_deposit:<10> expected but was<0>.
2 tests, 2 assertions, 1 failures, 0 errors
Greenclass Account < ActiveRecord::Base def deposit(amount) balance += amount save endend
..Finished in 0.242602 seconds.
2 tests, 2 assertions, 0 failures, 0 errors
Refactor
..Finished in 0.253956 seconds.
2 tests, 2 assertions, 0 failures, 0 errors
class AccountTest < Test::Unit::TestCase def setup @account = Account.new end def test_new assert_equal 0, @account.balance end def test_deposit @account.deposit(10) assert_equal 10, @account.balance endend
TDD as a Mind-Altering Drug
0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5
0.8
1.6
2.4
3.2
4
4.8
5.6
Mushrooms
0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5
0.8
1.6
2.4
3.2
4
4.8
5.6
TDD
Stages of TDD
Onset .5-2 hours
Coming Up 4-8 weeks
Plateau as long as used
Coming Down 2-3 months
After Effects years...
• Shedding years of bad habits.
• Simplified logic and data structures.
• Not worrying about “featuritis”.
• Taking your time, but being bolder.
• Less brain clutter.
• Less stress, more fun.
• Expanded consciousness.
Symptoms
Onset
• This is one of the hardest phases.
• TDD is hard to accept for many.
• Years of bad habits, schooling, peers, and environment make it harder.
Coming Up
• For a while, you will be less efficient.
• Your brain will hurt going through “formal design withdrawl”.
• Dreaming in UML only lasts a few days.
• Most of your bad habits are actually pretty easy to drop.
Plateau, Indications:
• Euphoria, confidence, freedom:
• Clarity of thought and purpose.
• Large goals are no longer intimidating.
• More brain is available after dropping years worth of bad habits.
• Paranoia when maintaining non-TDD code.
• Allergic reaction to “Big Design”.
Contraindications?
PHB’s Coding Cowboys
“Put Testing Where It Belongs: At the End”
Waterfall 2006 Keynote
by Brian Marick(fortunately, this example is a parody)
(unfortunately, it is too real to be funny)
Traditionalists
The “Do the Simplest Thing That Could
Possibly Work” Mindset
“If it is not useful or necessary, free yourself from imagining that you need to make it.”
Shaker Proverb
“Code simply so that others may simply code.”TDD Proverb, sorta
This One is Hard
• Nerds resist this. I suspect humans do too.
• For your task right now, only do the simplest thing that will work, e.g.:
• Use an array, or a hash, or a string. Not a red-black tree, or a database, or a file.
• Don’t do anything more. Really.
Why?
You Ain’t Gonna Need It(YAGNI)
• Only do what you need, for right now, to pass your test and move on.
• You ain’t gonna need that extra mess of complexity you are dreaming up in the back of your brain.
• When you really need it, implement it then.
• Basically: let all things flow organically.
Benefits of YAGNI
• You get done sooner.
• Your work is easier to communicate and understand.
• Refactoring is easier.
• Tests are easier to write.
• You feel less stress.
(c2.com)
My Favorite Nerd Quote
“More computing sins are committed in the name of efficiency
(without necessarily achieving it) than for any other single reason—
including blind stupidity.”W.A. Wulf
If That Wasn’t Clear:
“Rules of Optimization:Rule 1: Don't do it.
Rule 2 (for experts only): Don't do it yet.”M.A. Jackson
"We should forget about small efficiencies, say about 97% of the time:
premature optimization is the root of all evil."Donald Knuth
"Optimization hinders evolution."Anon.
def minutes self * 60end
def hours self * 60.minutesend
module ActiveSupport module CoreExtensions module Numeric module Time alias :day :days end end endend
module ActiveSupport module CoreExtensions module Time module Calculations module ClassMethods def yesterday self.ago(1.day) end end end end endend
def ago(seconds) seconds.until(self)end
alias :until :ago
def ago(time = ::Time.now) time - selfend
def days self * 24.hoursend
now = Time.nownow.yesterday=> now.ago(1.day)=> now.ago(1 * 24.hours)=> now.ago(1 * 24 * 60.minutes)=> now.ago(1 * 24 * 60 * 60)=> now.ago(86400)=> 86400.until(now)=> 86400.ago(now)=> now - 86400=> yesterday's time
The Rails Way™
now = Time.nownow.yesterday=> now - SEC_PER_DAY=> new - 86400=> yesterday's time
The TDD Way™class Time SEC_PER_DAY = 86400
def yesterday self - SEC_PER_DAY endend
Expand Your Mind...by not filling it
• TDD/YAGNI is a continuous feedback loop.
• Focusing on simplicity sheds bad habits.
• Shedding bad habits lets you focus more on simplicity.
• Clarity and simplicity increase your effectiveness.
• This goes far beyond code and tests.
• This is the mind-expansion I allude to.
Keep it Simple!
• YAGNI applies just as much (if not more) to tests as to code!
• It helps keep your head clear.
• It helps keep your goals clear.
• It helps keep your designs solid.
Tests First
• Write the simplest test that expresses your desire.
• Watch it fail. (Red!)
• Write the simplest implementation possible.
• Watch it pass. (Green!)
• Refactor!
• Repeat until out of coffee (or done).
Cover Your Bases
• Everything but stupid accessors need a test.
• Make methods public when testing.
• Test those private methods too!
• Better, don’t have private methods.
(Pretty m
uch)
Cover Edge Cases
• You can’t possibly test for every value, let alone every combination of every value.
• Edge cases are the important values:
• ±1, 0, ±max, today, expiration date, etc.
• Test edge cases for parameters AND state.
• Test both good input and bad.
• Ensure errors are handled gracefully.
Refactor Your Tests
• Tests are code too.
• Keep ‘em clean and lean.
• Factor duplicate code into setup, teardown, and utility methods.
Profile Your Tests
• Slow tests don’t get run. Period.
• Usually, the tests aren’t slow, the impl is.
• Fixing bottlenecks through testing benefits everyone.
• The tests keep being run, and the code gets faster!
Simple Organization
• 1:1 mapping between files, classes/testsaccount.rbtest_account.rb
• 1:many mapping between impl/test methods, separate by edge case:test_withdrawal (the positive case)test_withdrawal_junk (bad input)test_withdrawal_overdrawn
• Edge cases should be separate to let you detect patterns in failures.
• Suite of 5 tools for TDD with Ruby:
• zentest - Audit tests.
• multiruby - Validate multiple versions.
• Test::Rails - Enhance rails testing.
• unit_diff - Clarify errors.
• autotest - ADD TDD.
Our Secret Weapon: ZenTest
& ./test.rb... 1) Failure:test_conditional4(TestTypeChecker) [./r2ctestcase.rb:1068:in `test_conditional4' ./r2ctestcase.rb:1055:in `test_conditional4']:<t(:if, t(:call, t(:lit, 42, Type.long), :==, t(:arglist, t(:lit, 0, Type.long)), Type.bool), t(:return, t(:lit, 2, Type.long), Type.void), t(:if, t(:call, t(:lit, 42, Type.long), :>, t(:arglist, t(:lit, 0, Type.long)), Type.bool), t(:return, t(:lit, 3, Type.long), Type.void), t(:return, t(:lit, 4, Type.long), Type.void), Type.void), Type.void)> expected but was<t(:if, t(:call,
unit_diffClarify Errors
Which would you rather read/debug???
This:
& ./test.rb | unit_diff... 1) Failure:test_conditional4(TestTypeChecker)12c10< :>,---> :<,
Or this:
autotestADD TDD
• Intelligent Fast Feedback - run the right tests every time you save.
• Focus on Failures - reruns failed tests until they pass.
• Fully pluggable for hours of fun.
Run
all
tests
Run
changed
tests
Run
failures +
changes
Recommended Books
Extreme Programming Explained and Test Driven DevelopmentKent Beck
Working Effectively with Legacy CodeMichael Feathers
RefactoringMartin Fowler
A Pattern Language and A Timeless Way of BuildingChristopher Alexander