Testing Legacy Rails Apps

  • Published on

  • View

  • Download


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


<ul><li> 1. Testing Legacy Rails Applications Evan rabble Henshaw-PlathYahoo! Brickhouseanarchogeek.com - testingrails.com presentation slides - slideshare.net/rabble </li></ul> <p> 2. Do We HaveLegacy 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. Testing == Health Food? We started the tests, but havent been updating themWho has time for tests?Testing means writingtwice as much code. 4. Testing == Debugging 5. no, really, it is 6. Starting 7. Dont do it all at once 8. Get Rake Workingbrickhouse:~/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. Create your test db mysqladmin -uroot create railsapp_test 10. Use Migrationsbrickhouse:~/code/legacy_testing rabble$ rake db:migrate (in /Users/rabble/code/legacy_testing) == CreateArticles: migrating ==================== -- create_table(:articles)-&gt; 0.2749s == CreateArticles: migrated (0.2764s) ============= 11. Try Rake Againbrickhouse:~/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. Scaffolding is Broken 13. Ok nowwerereadyto get started 14. One Step At A Time 15. nd a bug - write a test 16. refractor a method write a test 17. Treat EachMethodAs Box 18. Test One Thing At A Time 19. What Goes In? What Comes Out? 20. Running Tests rake &amp; directly 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. running tests directly Test::Unit automatic runner. Usage: blog_controller_test.rb [options] [-- untouched arguments]run them all ruby article_controller_test.rbgive it a test name ruby article_controller_test.rb -n test_showtry regular expressions ruby article_controller_test.rb -n /show/get help: ruby --help 23. An Example def User.nd_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.nd_by_sql(sql) end 24. An Exampledef User.nd_popular(n=20) sql = quot;select u.*, count(j.user_id) as popularityfrom users u, talks_users jwhere u.id = j.user_id group by j.user_idorder by popularity desc limit #{n}quot; return User.nd_by_sql(sql)end def test_popular assert_nothing_raised { users = User.nd_popular(2) } assert_equal 2, users.size, quot;nd_popular should return two usersquot; assert users.rst.popularity &gt; 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. Refactordef self.nd_popular(n=20) return conference.attendees.nd(:all, :select =&gt; quot;users.*, count(talks_users.user_id) as popularityquot;, :joins =&gt; quot;LEFT JOIN talks_users on users.id = talks_users.user_idquot;, :group =&gt; quot;talks_users.user_idquot;, :order =&gt; 'popularity', :limit =&gt; 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: Message: ---Backtrace--- /Users/rabble/code/icalico/trunk/cong/../app/models/user.rb:35:in `nd_popular' user_test.rb:10:in `test_popular' user_test.rb:10:in `test_popular' --------------- 26. Refactordef self.nd_popular(n=20) return self.nd(:all, :select =&gt; quot;users.*, count(talks_users.user_id) as popularityquot;, :conditions =&gt; [quot;users.conference_id = ? quot;, conference.id], :joins =&gt; quot;LEFT JOIN talks_users on users.id = talks_users.user_idquot;, :group =&gt; quot;talks_users.user_idquot;, :order =&gt; 'popularity', :limit =&gt; 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. build test from logs ./log/development.logProcessing ArticlesController#show (for at 2006-07-20 11:28:23) [GET] Session ID: Parameters: {quot;actionquot;=&gt;quot;showquot;, quot;idquot;=&gt;quot;2quot;, quot;controllerquot;=&gt;quot;articlesquot;} Article Load (0.002371) SELECT * FROM articles LIMIT 1Rendering within layouts/articlesRendering articles/show Article Columns (0.007194) SHOW FIELDS FROM articlesCompleted in 0.10423 (9 reqs/sec) |Rendering: 0.08501 (81%) |DB: 0.01022 (9%) |200 OK [http://test.host/articles/show/1] 28. the functional test ./test/functional/article_controller_test.rbrequire File.dirname(__FILE__) + '/../test_helper'require 'articles_controller' # Re-raise errors caught by the controller.class ArticlesController def rescue_action(e) raise e endend 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_truthassert true endend 29. get the params./log/development.log Processing ArticlesController#show(for at 2006-07-20 11:28:23) [GET]Session ID:Parameters: {quot;actionquot;=&gt;quot;showquot;, quot;idquot;=&gt;quot;2quot;, quot;controllerquot;=&gt;quot;articlesquot;}Article Load (0.002371) SELECT * FROM articles LIMIT 1 Rendering within layouts/articles Rendering articles/showArticle 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. 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. writing the test ./test/functional/article_controller_test.rb def test_show get :show, {quot;actionquot;=&gt;quot;showquot;, quot;idquot;=&gt;quot;2quot;, quot;controllerquot;=&gt;quot;articlesquot;}#conrm that the http response was a 200 (i.e. success) assert_response :success#conrm that the correct template was used for this action assert_template 'articles/show'#conrm that the variable @article was assigned a value assert assigns( :article )#conrm that the @article object loaded has the id we want assert_equal 2, assigns( :article ).idend 32. running the test rabble:~/code/tblog/test/functional evan$ rubyarticles_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]: expected but was .1 tests, 4 assertions, 1 failures, 0 errors 33. x the bugThe old code app/controller/articles_controller.rb: def show @article = Article.nd(:rst)endThe x is easy, we update it so the Article.nd methodapp/controller/articles_controller.rb: def show @article = Article.nd(params[:id])end 34. running the testrabble:~/code/tblog/test/functional evan$ rubyarticles_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. test coverage 36. rake statsbrickhouse:~/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: 883Test LOC: 458Code to Test Ratio: 1:0.5 37. rcov test coverage 38. rcov test coverage what needs to be tested? 39. rcov test coverage what gets executed when you run tests 40. rcov test coverage summary 41. rcov test coverage per le reports 42. Heckle more later on this 43. autotestbecause sometimes our tests can run themselves 44. autotest 45. Continuous Integration 46. You can go deeper 47. Fixtures Uglinessar_xtures use mocks xture senarios 48. zentest 49. focus on the bugs 50. Flickr Photos http://ickr.com/photos/maguisso/247357791/ http://ickr.com/photos/jurvetson/482054617/ http://ickr.com/photos/candiedwomanire/352027/ http://ickr.com/photos/mr_fabulous/481255392/ http://ickr.com/photos/dharmasphere/125138024/ http://ickr.com/photos/misshaley/450106803/ http://ickr.com/photos/19684903@N00/317182464/ http://ickr.com/photos/planeta/349424552/ http://ickr.com/photos/izarbeltza/411729344/ http://ickr.com/photos/mikedeant/447379072/ http://ickr.com/photos/fofurasfelinas/74553343/ http://ickr.com/photos/thomashawk/422057690/ http://ickr.com/photos/cesarcabrera/396501977/ http://ickr.com/photos/thearchigeek/418967228/ http://ickr.com/photos/thomashawk/476897084/ http://ickr.com/photos/gini/123489837/ http://ickr.com/photos/neilw/233087821/ http://ickr.com/photos/good_day/450356635/ http://ickr.com/photos/ronwired/424730482/ http://ickr.com/photos/monster/148765721/ http://ickr.com/photos/monkeyc/200815386/ http://ickr.com/photos/charlesfred/243202440 http://ickr.com/photos/dramaqueennorma/191063346/ http://ickr.com/photos/incognita_mod/433543605/ http://ickr.com/photos/licudi/272592045/ http://ickr.com/photos/martinlabar/163107859/ http://ickr.com/photos/gaspi/6281982/ http://ickr.com/photos/iboy_daniel/98784857/ http://ickr.com/photos/silvia31163/199478324/ http://ickr.com/photos/tjt195/68790873/ http://ickr.com/photos/nidriel/103210579/ http://ickr.com/photos/charlietakesphotos/25951293/ http://ickr.com/photos/leia/29147578/ http://ickr.com/photos/90361640@N00/282104656/ 51. Get Your Feet Wet Next: Evan rabble Henshaw-Plath Yahoo! Brickhouse anarchogeek.com Heckle testingrails.com slides: slideshare.net/rabble </p>