47
Electric Kool-aid Acid Testing by Ryan Davis, Seattle.rb

Electric Kool-aid Acid Testing - zenspider.com Kool-aid Acid Testing by Ryan Davis, Seattle.rb. ... End of Boring Stuff ... My Favorite Nerd Quote

Embed Size (px)

Citation preview

Electric Kool-aidAcid Testing

by Ryan Davis, Seattle.rb

Test Driven DesignThe Boring Intro

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

Red

Green

Refactor

TDD Mantra

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

It really is that simple...

End of Boring Stuff

Yay!

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”.

Coming Down& After Effects

• Lets not bother, shall we?

• It gets grisly.

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.

Time#yesterday

How would you implement it?

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.

Writing Good Tests

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:

unit_diff in Action

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

AutoTest in Action

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

Thank You

http://www.zenspider.com/seattle.rb