Evan ‘rabble’ Henshaw-Plath Yahoo! Brickhouse
anarchogeek.com - testingrails.compresentation slides - slideshare.net/rabble
Testing LegacyRails Applications
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.
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.
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.”
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/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rb" /opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rb" #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'
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)== CreateArticles: migrating ====================-- create_table(:articles) -> 0.2749s== CreateArticles: migrated (0.2764s) =============
Try Rake Again
brickhouse:~/code/legacy_testing rabble$ rake(in /Users/rabble/code/legacy_testing)/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rb" "test/unit/article_test.rb" Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loaderStarted.Finished in 0.316844 seconds.
1 tests, 1 assertions, 0 failures, 0 errors/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rb" "test/functional/blog_controller_test.rb" Loaded suite /opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loaderStarted.Finished in 0.243161 seconds.
1 tests, 1 assertions, 0 failures, 0 errors/opt/local/bin/ruby -Ilib:test "/opt/local/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake/rake_test_loader.rb"
Scaffolding is Broken
Ok nowwe’rereadyto getstarted
One Step At A TimeOne Step At A Time
find a bug - write a test
refractor a methodwrite a test
Treat EachMethod As Box
Test One Thing
At A Time
What Goes In?
What Comes Out?
Running Tests
rake & directly
Running Testswith rake
rake test # Test all units and functionalsrake test:functionals # Run the functional tests in test/functionalrake test:integration # Run the integration tests in test/integrationrake test:plugins # Run the plugin tests in vendor/plugins/**/testrake test:recent # Test recent changesrake test:uncommitted # Test changes since last checkin (svn only)rake test:units # Run the unit tests in test/unit
running tests directlyTest::Unit automatic runner.Usage: blog_controller_test.rb [options] [-- untouched arguments]
run them allruby article_controller_test.rb
give it a test nameruby article_controller_test.rb -n test_show
try regular expressionsruby article_controller_test.rb -n /show/
get help: ruby --help
An Example def User.find_popular(n=20) sql = "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}" return User.find_by_sql(sql) end
An Example def User.find_popular(n=20) sql = "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}" return User.find_by_sql(sql) end
def test_popular assert_nothing_raised { users = User.find_popular(2) } assert_equal 2, users.size, "find_popular should return two users" assert users.first.popularity > users.last.popularity, "should sort popular users" end
$ ruby ./test/unit/user_test -n test_popularbrickhouse:~/code/icalico/trunk/test/unit rabble$ ruby user_test.rb -n /popular/Loaded suite user_testStarted.Finished in 0.290563 seconds.
Refactor def self.find_popular(n=20) return conference.attendees.find(:all, :select => "users.*, count(talks_users.user_id) as popularity", :joins => "LEFT JOIN talks_users on users.id = talks_users.user_id", :group => "talks_users.user_id", :order => 'popularity', :limit => n )end
$ ruby ./test/unit/user_test -n test_popularbrickhouse:~/code/icalico/trunk/test/unit rabble$ ruby user_test.rb -n /popular/Loaded suite user_testStartedFFinished in 0.290563 seconds.
1) Failure:test_popular(UserTest) [user_test.rb:10]:Exception raised:Class: <NoMethodError>Message: <"You have a nil object when you didn't expect it!\nThe error occured while evaluating nil.attendees">---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'---------------
Refactor def self.find_popular(n=20) return self.find(:all, :select => "users.*, count(talks_users.user_id) as popularity", :conditions => ["users.conference_id = ? ", conference.id], :joins => "LEFT JOIN talks_users on users.id = talks_users.user_id", :group => "talks_users.user_id", :order => 'popularity', :limit => n )end
$ ruby ./test/unit/user_test -n test_popularbrickhouse:~/code/icalico/trunk/test/unit rabble$ ruby user_test.rb -n /popular/Loaded suite user_testStarted.Finished in 0.290563 seconds.
build test from logs
Processing ArticlesController#show (for 0.0.0.0 at 2006-07-20 11:28:23) [GET] Session ID: Parameters: {"action"=>"show", "id"=>"2", "controller"=>"articles"} 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]
./log/development.log
the functional test
require 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 < 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 endend
./test/functional/article_controller_test.rb
get the params
Processing ArticlesController#show (for 0.0.0.0 at 2006-07-20 11:28:23) [GET] Session ID: Parameters: {"action"=>"show", "id"=>"2", "controller"=>"articles"} 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]
./log/development.log
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.
writing the test
def test_show get :show, {"action"=>"show", "id"=>"2", "controller"=>"articles"}
#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 ).idend
./test/functional/article_controller_test.rb
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
fix the bug
The old codeapp/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
running the test
rabble:~/code/tblog/test/functional evan$ ruby \articles_controller_test.rb -n test_showLoaded suite articles_controller_testStarted .Finished in 0.426828 seconds.
1 tests, 3 assertions, 0 failures, 0 errors
test coverage
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
rcov test coverage
rcov test coverage
what needs to be tested?
rcov test coverage
what gets executed when you run tests
rcov test coveragesummary
rcov test coverageper file reports
Heckle
more lateron this
autotest
because sometimes our tests can run themselves
autotest
Continuous Integration
You can go deeperYou can go deeper
FixturesUgliness
ar_fixturesuse mocksfixture senarios
zentest
focus on the bugs
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/
Next: Heckle
Evan ‘rabble’ Henshaw-Plath Yahoo! Brickhouseanarchogeek.com testingrails.comslides: slideshare.net/rabble
Get YourFeet Wet
Top Related