Your SlideShare is downloading. ×
0
Testing Legacy
Rails Applications




                  Evan ‘rabble’ Henshaw-Plath
                       Yahoo! Brickhou...
Do We Have
   Legacy Already?

Yes, thousands of rails apps have been
built and deployed over the last three
years. Many o...
Testing == Health Food?



 “We started the tests, but
haven’t been updating them”
                         “Who has time ...
Testing == Debugging
no, really, it is
Starting
Don’t
do
it
all
at
once
Get Rake Working



brickhouse:~/code/legacy_testing rabble$ rake
(in /Users/rabble/code/legacy_testing)
/opt/local/bin/ru...
Create your test db




mysqladmin -uroot create railsapp_test
Use Migrations

brickhouse:~/code/legacy_testing rabble$ rake db:migrate
(in /Users/rabble/code/legacy_testing)
== CreateA...
Try Rake Again

brickhouse:~/code/legacy_testing rabble$ rake
(in /Users/rabble/code/legacy_testing)
/opt/local/bin/ruby -...
Scaffolding is Broken
Ok
  now
 we’re
 ready
 to get
started
One Step At A Time
find a bug - write a test
refractor a method
    write a test
Treat Each
 Method
 As Box
Test One Thing




  At A Time
What Goes In?




What Comes Out?
Running Tests




      rake & directly
Running Tests
with rake


rake   test               #   Test all units and functionals
rake   test:functionals   #   Run t...
running tests directly
Test::Unit automatic runner.
Usage: blog_controller_test.rb [options] [-- untouched arguments]

run...
An Example
def User.find_popular(n=20)
 sql = quot;select u.*, count(j.user_id) as popularity
        from users u, talks_u...
An Example
 def User.find_popular(n=20)
  sql = quot;select u.*, count(j.user_id) as popularity
         from users u, talk...
Refactor
 def self.find_popular(n=20)
  return conference.attendees.find(:all,
    :select => quot;users.*, count(talks_user...
Refactor
 def self.find_popular(n=20)
  return self.find(:all,
    :select => quot;users.*, count(talks_users.user_id) as po...
build test from logs

 ./log/development.log
 Processing ArticlesController#show
  (for 0.0.0.0 at 2006-07-20 11:28:23) [G...
the functional test
./test/functional/article_controller_test.rb
 require File.dirname(__FILE__) + '/../test_helper'
 requ...
get the params

./log/development.log
Processing ArticlesController#show
 (for 0.0.0.0 at 2006-07-20 11:28:23) [GET]
 Sess...
what to test?
1. Assert that the page action
rendered and returned successful
HTTP response code, i.e. 200.
2. Assert that...
writing the test
./test/functional/article_controller_test.rb

 def test_show
  get :show, {quot;actionquot;=>quot;showquo...
running the test

 rabble:~/code/tblog/test/functional evan$ ruby 
articles_controller_test.rb -n test_show
Loaded suite a...
fix the bug

The old code
app/controller/articles_controller.rb:

 def show
  @article = Article.find(:first)
 end

The fix is...
running the test

rabble:~/code/tblog/test/functional evan$ ruby 
articles_controller_test.rb -n test_show
Loaded suite ar...
test coverage
rake stats

brickhouse:~/code/icalico/trunk rabble$ rake stats
(in /Users/rabble/code/icalico/trunk)
+--------------------...
rcov test coverage
rcov test coverage


what
needs to
be tested?
rcov test coverage


what gets
executed
when you
run tests
rcov test coverage
summary
rcov test coverage
per file reports
Heckle


more later
on this
autotest




 because sometimes our
tests can run themselves
autotest
Continuous Integration
You can go deeper
Fixtures
Ugliness

ar_fixtures
use mocks
fixture senarios
zentest
focus on the bugs
Flickr Photos
•http://flickr.com/photos/maguisso/247357791/
•http://flickr.com/photos/jurvetson/482054617/
•http://flickr.com...
Get Your
Feet Wet


                                Next:
Evan ‘rabble’ Henshaw-Plath
Yahoo! Brickhouse
anarchogeek.com
  ...
Upcoming SlideShare
Loading in...5
×

Testing Legacy Rails Apps

5,865

Published on

Presentation at Rails Conf 2007 about adding tests to legacy ruby on rails applications.

Published in: Technology, Business

Transcript of "Testing Legacy Rails Apps"

  1. 1. Testing Legacy Rails Applications Evan ‘rabble’ Henshaw-Plath Yahoo! Brickhouse anarchogeek.com - testingrails.com presentation slides - slideshare.net/rabble
  2. 2. Do We Have Legacy Already? Yes, thousands of rails apps have been built and deployed over the last three years. Many of us included few or no tests. The test-less apps still need debugging, which should be done with tests.
  3. 3. Testing == Health Food? “We started the tests, but haven’t been updating them” “Who has time for tests?” “Testing means writing twice as much code.”
  4. 4. Testing == Debugging
  5. 5. no, really, it is
  6. 6. Starting
  7. 7. Don’t do it all at once
  8. 8. Get Rake Working brickhouse:~/code/legacy_testing rabble$ rake (in /Users/rabble/code/legacy_testing) /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rbquot; /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rbquot; #42000Unknown database 'legacy_testing_development' /opt/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/vendor/mysql.rb:523:in `read' /opt/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/vendor/mysql.rb:153:in `real_connect' /opt/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/ mysql_adapter.rb:389:in `connect' /opt/local/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/ mysql_adapter.rb:152:in `initialize'
  9. 9. Create your test db mysqladmin -uroot create railsapp_test
  10. 10. Use Migrations brickhouse:~/code/legacy_testing rabble$ rake db:migrate (in /Users/rabble/code/legacy_testing) == CreateArticles: migrating ==================== -- create_table(:articles) -> 0.2749s == CreateArticles: migrated (0.2764s) =============
  11. 11. Try Rake Again brickhouse:~/code/legacy_testing rabble$ rake (in /Users/rabble/code/legacy_testing) /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rbquot; quot;test/unit/ article_test.rbquot; Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader Started . Finished in 0.316844 seconds. 1 tests, 1 assertions, 0 failures, 0 errors /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rbquot; quot;test/ functional/blog_controller_test.rbquot; Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader Started . Finished in 0.243161 seconds. 1 tests, 1 assertions, 0 failures, 0 errors /opt/local/bin/ruby -Ilib:test quot;/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rbquot;
  12. 12. Scaffolding is Broken
  13. 13. Ok now we’re ready to get started
  14. 14. One Step At A Time
  15. 15. find a bug - write a test
  16. 16. refractor a method write a test
  17. 17. Treat Each Method As Box
  18. 18. Test One Thing At A Time
  19. 19. What Goes In? What Comes Out?
  20. 20. Running Tests rake & directly
  21. 21. Running Tests with rake rake test # Test all units and functionals rake test:functionals # Run the functional tests in test/functional rake test:integration # Run the integration tests in test/integration rake test:plugins # Run the plugin tests in vendor/plugins/**/test rake test:recent # Test recent changes rake test:uncommitted # Test changes since last checkin (svn only) rake test:units # Run the unit tests in test/unit
  22. 22. running tests directly Test::Unit automatic runner. Usage: blog_controller_test.rb [options] [-- untouched arguments] run them all ruby article_controller_test.rb give it a test name ruby article_controller_test.rb -n test_show try regular expressions ruby article_controller_test.rb -n /show/ get help: ruby --help
  23. 23. An Example def User.find_popular(n=20) sql = quot;select u.*, count(j.user_id) as popularity from users u, talks_users j where u.id = j.user_id group by j.user_id order by popularity desc limit #{n}quot; return User.find_by_sql(sql) end
  24. 24. An Example def User.find_popular(n=20) sql = quot;select u.*, count(j.user_id) as popularity from users u, talks_users j where u.id = j.user_id group by j.user_id order by popularity desc limit #{n}quot; return User.find_by_sql(sql) end def test_popular assert_nothing_raised { users = User.find_popular(2) } assert_equal 2, users.size, quot;find_popular should return two usersquot; assert users.first.popularity > users.last.popularity, quot;should sort popular usersquot; end $ ruby ./test/unit/user_test -n test_popular brickhouse:~/code/icalico/trunk/test/unit rabble$ ruby user_test.rb -n /popular/ Loaded suite user_test Started . Finished in 0.290563 seconds.
  25. 25. Refactor def self.find_popular(n=20) return conference.attendees.find(:all, :select => quot;users.*, count(talks_users.user_id) as popularityquot;, :joins => quot;LEFT JOIN talks_users on users.id = talks_users.user_idquot;, :group => quot;talks_users.user_idquot;, :order => 'popularity', :limit => n ) end $ ruby ./test/unit/user_test -n test_popular brickhouse:~/code/icalico/trunk/test/unit rabble$ ruby user_test.rb -n /popular/ Loaded suite user_test Started F Finished in 0.290563 seconds. 1) Failure: test_popular(UserTest) [user_test.rb:10]: Exception raised: Class: <NoMethodError> Message: <quot;You have a nil object when you didn't expect it!nThe error occured while evaluating nil.attendeesquot;> ---Backtrace--- /Users/rabble/code/icalico/trunk/config/../app/models/user.rb:35:in `find_popular' user_test.rb:10:in `test_popular' user_test.rb:10:in `test_popular' ---------------
  26. 26. Refactor def self.find_popular(n=20) return self.find(:all, :select => quot;users.*, count(talks_users.user_id) as popularityquot;, :conditions => [quot;users.conference_id = ? quot;, conference.id], :joins => quot;LEFT JOIN talks_users on users.id = talks_users.user_idquot;, :group => quot;talks_users.user_idquot;, :order => 'popularity', :limit => n ) end $ ruby ./test/unit/user_test -n test_popular brickhouse:~/code/icalico/trunk/test/unit rabble$ ruby user_test.rb -n /popular/ Loaded suite user_test Started . Finished in 0.290563 seconds.
  27. 27. build test from logs ./log/development.log Processing ArticlesController#show (for 0.0.0.0 at 2006-07-20 11:28:23) [GET] Session ID: Parameters: {quot;actionquot;=>quot;showquot;, quot;idquot;=>quot;2quot;, quot;controllerquot;=>quot;articlesquot;} Article Load (0.002371) SELECT * FROM articles LIMIT 1 Rendering within layouts/articles Rendering articles/show Article Columns (0.007194) SHOW FIELDS FROM articles Completed in 0.10423 (9 reqs/sec) | Rendering: 0.08501 (81%) | DB: 0.01022 (9%) | 200 OK [http://test.host/articles/show/1]
  28. 28. the functional test ./test/functional/article_controller_test.rb require File.dirname(__FILE__) + '/../test_helper' require 'articles_controller' # Re-raise errors caught by the controller. class ArticlesController def rescue_action(e) raise e end end class ArticlesControllerTest &lt; Test::Unit::TestCase def setup @controller = ArticlesController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end # Replace this with your real tests. def test_truth assert true end end
  29. 29. get the params ./log/development.log Processing ArticlesController#show (for 0.0.0.0 at 2006-07-20 11:28:23) [GET] Session ID: Parameters: {quot;actionquot;=>quot;showquot;, quot;idquot;=>quot;2quot;, quot;controllerquot;=>quot;articlesquot;} Article Load (0.002371) SELECT * FROM articles LIMIT 1 Rendering within layouts/articles Rendering articles/show Article Columns (0.007194) SHOW FIELDS FROM articles Completed in 0.10423 (9 reqs/sec) | Rendering: 0.08501 (81%) | DB: 0.01022 (9%) | 200 OK [http://test.host/articles/show/1]
  30. 30. what to test? 1. Assert that the page action rendered and returned successful HTTP response code, i.e. 200. 2. Assert that the correct template was rendered. 3. Assert that action assigns a value to the @article variable. 4. Assert that the right @article object was loaded.
  31. 31. writing the test ./test/functional/article_controller_test.rb def test_show get :show, {quot;actionquot;=>quot;showquot;, quot;idquot;=>quot;2quot;, quot;controllerquot;=>quot;articlesquot;} #confirm that the http response was a 200 (i.e. success) assert_response :success #confirm that the correct template was used for this action assert_template 'articles/show' #confirm that the variable @article was assigned a value assert assigns( :article ) #confirm that the @article object loaded has the id we want assert_equal 2, assigns( :article ).id end
  32. 32. running the test rabble:~/code/tblog/test/functional evan$ ruby articles_controller_test.rb -n test_show Loaded suite articles_controller_test Started F Finished in 0.625045 seconds. 1) Failure: test_show(ArticlesControllerTest) [articles_controller_test.rb:27]: <2> expected but was <1>. 1 tests, 4 assertions, 1 failures, 0 errors
  33. 33. fix the bug The old code app/controller/articles_controller.rb: def show @article = Article.find(:first) end The fix is easy, we update it so the Article.find method app/controller/articles_controller.rb: def show @article = Article.find(params[:id]) end
  34. 34. running the test rabble:~/code/tblog/test/functional evan$ ruby articles_controller_test.rb -n test_show Loaded suite articles_controller_test Started . Finished in 0.426828 seconds. 1 tests, 3 assertions, 0 failures, 0 errors
  35. 35. test coverage
  36. 36. rake stats brickhouse:~/code/icalico/trunk rabble$ rake stats (in /Users/rabble/code/icalico/trunk) +----------------------+-------+-------+---------+---------+-----+-------+ | Name | Lines | LOC | Classes | Methods | M/C | LOC/M | +----------------------+-------+-------+---------+---------+-----+-------+ | Helpers | 107 | 81 | 0| 9| 0| 7| | Controllers | 517 | 390 | 10 | 52 | 5| 5| | Components | 0| 0| 0| 0| 0| 0| | Functional tests | 416 | 299 | 18 | 58 | 3| 3| | Models | 344 | 250 | 8| 37 | 4| 4| | Unit tests | 217 | 159 | 9| 25 | 2| 4| | Libraries | 257 | 162 | 4| 32 | 8| 3| | Integration tests | 0| 0| 0| 0| 0| 0| +----------------------+-------+-------+---------+---------+-----+-------+ | Total | 1858 | 1341 | 49 | 213 | 4| 4| +----------------------+-------+-------+---------+---------+-----+-------+ Code LOC: 883 Test LOC: 458 Code to Test Ratio: 1:0.5
  37. 37. rcov test coverage
  38. 38. rcov test coverage what needs to be tested?
  39. 39. rcov test coverage what gets executed when you run tests
  40. 40. rcov test coverage summary
  41. 41. rcov test coverage per file reports
  42. 42. Heckle more later on this
  43. 43. autotest because sometimes our tests can run themselves
  44. 44. autotest
  45. 45. Continuous Integration
  46. 46. You can go deeper
  47. 47. Fixtures Ugliness ar_fixtures use mocks fixture senarios
  48. 48. zentest
  49. 49. focus on the bugs
  50. 50. Flickr Photos •http://flickr.com/photos/maguisso/247357791/ •http://flickr.com/photos/jurvetson/482054617/ •http://flickr.com/photos/candiedwomanire/352027/ •http://flickr.com/photos/mr_fabulous/481255392/ •http://flickr.com/photos/dharmasphere/125138024/ •http://flickr.com/photos/misshaley/450106803/ •http://flickr.com/photos/19684903@N00/317182464/ •http://flickr.com/photos/planeta/349424552/ •http://flickr.com/photos/izarbeltza/411729344/ •http://flickr.com/photos/mikedefiant/447379072/ •http://flickr.com/photos/fofurasfelinas/74553343/ •http://flickr.com/photos/thomashawk/422057690/ •http://flickr.com/photos/cesarcabrera/396501977/ •http://flickr.com/photos/thearchigeek/418967228/ •http://flickr.com/photos/thomashawk/476897084/ •http://flickr.com/photos/gini/123489837/ •http://flickr.com/photos/neilw/233087821/ •http://flickr.com/photos/good_day/450356635/ •http://flickr.com/photos/ronwired/424730482/ •http://flickr.com/photos/monster/148765721/ •http://flickr.com/photos/monkeyc/200815386/ •http://flickr.com/photos/charlesfred/243202440 •http://flickr.com/photos/dramaqueennorma/191063346/ •http://flickr.com/photos/incognita_mod/433543605/ •http://flickr.com/photos/filicudi/272592045/ •http://flickr.com/photos/martinlabar/163107859/ •http://flickr.com/photos/gaspi/6281982/ •http://flickr.com/photos/iboy_daniel/98784857/ •http://flickr.com/photos/silvia31163/199478324/ •http://flickr.com/photos/tjt195/68790873/ •http://flickr.com/photos/nidriel/103210579/ •http://flickr.com/photos/charlietakesphotos/25951293/ •http://flickr.com/photos/leia/29147578/ •http://flickr.com/photos/90361640@N00/282104656/
  51. 51. Get Your Feet Wet Next: Evan ‘rabble’ Henshaw-Plath Yahoo! Brickhouse anarchogeek.com Heckle testingrails.com slides: slideshare.net/rabble
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×