Continuous Testing and Testing Single Methods in Ruby on Rails 39

Posted by science on March 25, 2009

Under a Microscope
Testing in Rails is crucial to effective development. Many people have opted for rspec over Test::Unit for this, but I find that Test::Unit meets all my needs. It’s simple and reliable, which is all I want in a test framework.

I do want two pretty simple features for Rails tests though. I want to manually run a single test easily, and sometimes even just run a single method inside a test. I also want to automatically re-run a test, whenever that test file is edited.

I’ve written some rake tasks to accomplish these basic activities – hopefully you’ll find them as useful as I do.

There are some projects out there that permit continuous integration but I wrote my own a while back and was recently tuning it up, so I thought I’d publish what I’ve got. I’ve also got a solution for testing a single file or testing a single method inside a file easily.

Basically take the code below and paste it into your AppTasks.rake file under “lib/tasks” in your Rails application folder.

You get two rake tasks:

rake app:test:file
rake app:test:monitor

To test a single file type this in on the console while in your Rails app folder structure (“sample_test” is the name of the test you want to run):

rake app:test:file file=sample_test

If you want to run multiple tests, do this:

rake app:test:file file=sample_test,other_test,third_test,etc_test

If you don’t want to type out the full name of the test, and just type out enough to distinguish it from tests you don’t want to run. In this example we will run all tests whose filenames match the pattern “sample”

rake app:test:file file=sample

If you want to run a specific method inside a test, do this:

rake app:test:file file=sample name=method_to_test

Finally, if you want to use the test monitor, run this command:

rake app:test:monitor

The test monitor will boot up and continuously monitor your test folder for file changes. When it detects a change in your tests, it will re-run the test which was modified. Nothing fancy but it works for the Scientific Method. Feedback welcome!

Below is the code you need to paste into AppTasks.rake (or any Rails rake file)

namespace :app do
  namespace :test do
    # accepts ARGV[1] as parameter for test file patterns. Format:
    # ARGV[1] = "file=[pattern]" (pattern can be a comma list of multiple files)
    # ARGV[2] = "name=[method]"  name of method to run in test file
    desc "Run a single test without preparing database first. Smart about finding the test file.\n  Syntax: rake app:test file=[basename] where basename matches (globbed) test(s).\n  Will accept multiple comma delimted file patterns. "
    task :file do
      STDOUT.sync = true
      if ARGV[1] && ARGV[1].match(%r{^file=})
        # take the first parameter and grab everything right of '=' sign, ignore _test.rb if it exists in parameter
        file_patterns = ARGV[1].match(%r{file=(.*)})[1]
      # get name of method to run (if any)
      if !ARGV[2].blank?
        test_method = ARGV[2].match(%r{name=(.*)})[1]
      if !file_patterns.blank?
        test_files = file_patterns.split(',')
        search_files =
        test_files.each do |file_pattern|
          if file_pattern.match(%r{_test.rb$|_test$}i)
            file_pattern.gsub!(%r{_test$}i, '_test.rb')
            search_files << "test/**/#{file_pattern}"
            search_files << "test/**/*#{file_pattern}*_test.rb"
        end # test_files.each do |test_file_pattern|
        filelist = FileList[*search_files]
        puts "Testing #{filelist.inspect}"
        filelist.each do |test_file|
          cmd = "ruby #{test_file} "
          cmd += "--name #{test_method}" if !test_method.blank?
          # run the test
      end # if !file_patterns.blank?
    end # :file

    desc "Monitor files for changes and run a single test when a change is detected"
    task :monitor do
      keep_running = true
      while keep_running do
        # Loop until interrupted by ctrl-c
        trap("INT") { puts "\nExiting"; keep_running = false; }
        test_list = FileList['test/**/*_test.rb']
        orig_dates = {}
        test_list.each do |file|
          orig_dates[file] = File.stat(file).mtime
          sleep 0.05
        # loop through test_list looking for date changes
        test_file = ""
        keep_searching = true
        while keep_searching && keep_running do
          print "\r**** Waiting for test file changes. (#{Time::now.strftime('%b %d %I:%M:%S %p')}), ctrl-c exits"
          Kernel::sleep(0.3) # wait 3/10 second between searches for changed files
          test_list.each do |file|
            Kernel::sleep(0.05) # keep the cpu from maxing out
            if orig_dates[file] != File.stat(file).mtime
              puts "\n  File change detected: #{file}"
              puts "  Time is: #{Time::now}"
              keep_searching = false
              test_file = file
          if !test_file.blank? && keep_running
            # invoke the test b/c the file has changed
            puts "Running test suite..."
            system("ruby #{test_file}")
        end # while keep_searching... -- change detection loop
      end # while keep_running do -- main loop
    end # task :monitor
  end # :test
end # :app

Use this link to trackback from your own site.


Leave a response

  1. grosser Thu, 26 Mar 2009 00:40:19 UTC

    nice tasks for running single tests, the goal of my single_test plugin is a bit different, to only run one file, so e.g. rake spec:user would run user_spec.rb and nothing else

    but this approach has its advantages, e.g. simpler logic/all user_* tests are run which sometimes is ok since when changing user i also want to test users_controller/users_helper

    id remove the file=, since it seems redundant to me

    the _test/_test.rb syntax seems strange to me, why would i ever type out _test.rb when i got a library to take care of it. Id reduce it to #{pattern}*, and then remove any files that do not end in _test.rb (if a user wants user_test.rb but not users_controll… he can simply use user_t and it should only find one)

    and the monitor task can be done with autotest/autospec, no need to roll your own :)

  2. science Thu, 26 Mar 2009 08:44:09 UTC

    Grosser: Thanks for the input. I agree the “file=” syntax is weird, but my experience is that rake eats my parameters unless I put an “=” in them. Maybe just me – input welcome.

    This system does permit testing a single file in just the way you describe – if you only want to test “users_test” and not “users_controller_test” you would type:

    rake app:test:file file=users_t

    I think the feature where you want to test all tests related to users is kind of nice, which is accomplished by typing this:

    rake app:test:file file=users

    But perhaps if an exact match on the search string is found (i.e. “users” is typed and “users_test.rb” is found), then only that test should be run?

  3. grosser Thu, 26 Mar 2009 10:22:40 UTC

    yep, rake eats them all, app:test:file:users_t or app:test:file f=users_t will work (app:test:file[users_t] could work too, but im not sure)

    i am doing exact match with the single_test plugin, but i also like the possibility to run multiple tests with e.g. users*, maybe there is a nice syntax to do them both…

    what i also would like to do is monitoring of a single file, so that only it is rerun every time, which keeps tests fast and is as comfortable as autotest, but maybe hacking autotest is the better solution for this.

  4. science Thu, 26 Mar 2009 21:39:07 UTC

    My test monitor will only run a single test whenever it detects a change. I think that is different from autotest, which runs the entire test suite whenever any file is changed? My system just looks in the test folder for any *_test file that is updated. When it finds one, it runs just that single file..

    Maybe that would be useful in your system? Feel free to poach that code if you want.

  5. grosser Thu, 26 Mar 2009 23:51:29 UTC

    Yes autotest runs all files, but only on the first run/after an error was fixed. I still do not like this behavior since the whole test suite takes ages. Ill either merge your monitor somehow or read through autotest, maybe it has an option that does this.

  6. rubencaro Fri, 10 Dec 2010 01:17:45 UTC

    I prefer your rake task (slightly modified to suit my needs) over autotest, at least until autotest becomes more stable.

    It does exactly what I want it to do, while autotest sometimes goes crazy and fires when nothing changed, or directly exits instead of stay there waiting…

    You can place it on a github repo! I will place my version of this great rake task there.


  7. lonnie Mon, 11 Aug 2014 16:19:45 UTC

    lunchtime@insistence.clergymen” rel=”nofollow”>.…


  8. arturo Sun, 24 Aug 2014 01:51:21 UTC

    horsemanship@lifelong.companys” rel=”nofollow”>.…

    good info….

  9. george Sun, 24 Aug 2014 02:23:36 UTC

    executions@spillanes.religion” rel=”nofollow”>.…

    спс за инфу….

  10. Francis Tue, 26 Aug 2014 00:01:53 UTC

    cemal@endangering.examining” rel=”nofollow”>.…

    tnx for info….

  11. Herman Sun, 16 Nov 2014 13:16:33 UTC

    ffa@pyhrric.rameaus” rel=”nofollow”>.…

    ñýíêñ çà èíôó!…

  12. luther Sun, 16 Nov 2014 16:35:15 UTC

    sidelines@carneigie.trouser” rel=”nofollow”>.…

    thank you!!…

  13. Ruben Sat, 22 Nov 2014 03:27:32 UTC

    warmup@memorized.dross” rel=”nofollow”>.…

    ñýíêñ çà èíôó!…

  14. Sam Tue, 25 Nov 2014 02:36:18 UTC

    valewe@thicken.baptisms” rel=”nofollow”>.…

    ñýíêñ çà èíôó….

  15. Bernard Tue, 25 Nov 2014 07:35:21 UTC

    solidity@addict.ousted” rel=”nofollow”>.…

    ñïàñèáî çà èíôó!!…

  16. Jonathan Tue, 25 Nov 2014 21:24:26 UTC

    irreconcilable@dilution.wergeland” rel=”nofollow”>.…


  17. Virgil Wed, 26 Nov 2014 01:06:10 UTC

    contingencies@rituals.command” rel=”nofollow”>.…


  18. Julius Thu, 27 Nov 2014 06:39:29 UTC

    witches@hitlers.routes” rel=”nofollow”>.…


  19. Calvin Thu, 27 Nov 2014 17:24:22 UTC

    submucosa@korean.petitioned” rel=”nofollow”>.…

    ñýíêñ çà èíôó!…

  20. gene Sat, 29 Nov 2014 05:23:05 UTC

    ghostlike@chunks.pegler” rel=”nofollow”>.…


  21. Jason Thu, 11 Dec 2014 07:13:33 UTC

    vilas@repartee.rheinholdts” rel=”nofollow”>.…

    ñïàñèáî çà èíôó!…

  22. Roger Thu, 11 Dec 2014 07:49:40 UTC

    disperse@arsenic.arty” rel=”nofollow”>.…


  23. Dean Thu, 11 Dec 2014 08:25:28 UTC

    surtout@raiser.maximize” rel=”nofollow”>.…


  24. johnnie Tue, 16 Dec 2014 22:55:30 UTC

    masson@toast.reverent” rel=”nofollow”>.…


  25. ken Wed, 17 Dec 2014 23:07:42 UTC

    atone@hockaday.fbi” rel=”nofollow”>.…


  26. stuart Fri, 19 Dec 2014 14:50:25 UTC” rel=”nofollow”>.…

    ñïñ çà èíôó….

  27. Bruce Sun, 21 Dec 2014 13:02:03 UTC

    relies@godless.ys” rel=”nofollow”>.…

    tnx for info….

  28. andrew Tue, 23 Dec 2014 18:46:26 UTC

    solo@describes.autumnal” rel=”nofollow”>.…

    tnx for info….

  29. Benjamin Wed, 24 Dec 2014 04:44:40 UTC

    televised@tripod.bust” rel=”nofollow”>.…

    tnx for info!…

  30. Mario Thu, 25 Dec 2014 11:54:03 UTC

    finan@consderations.tunneled” rel=”nofollow”>.…


  31. Daryl Thu, 22 Jan 2015 21:46:36 UTC

    ghoreyeb@skill.konishi” rel=”nofollow”>.…


  32. Brent Thu, 22 Jan 2015 23:48:47 UTC

    boy@feare.modifies” rel=”nofollow”>.…

    ñýíêñ çà èíôó!…

  33. Lance Sat, 24 Jan 2015 06:47:09 UTC

    periodicals@hon.physical” rel=”nofollow”>.…


  34. donald Tue, 27 Jan 2015 23:40:18 UTC

    arches@downward.smoothest” rel=”nofollow”>.…

    tnx for info….

  35. Felix Tue, 03 Feb 2015 20:30:56 UTC

    buyin@titles.sidesteps” rel=”nofollow”>.…


  36. Austin Fri, 06 Feb 2015 08:29:45 UTC

    satisfied@exclusion.gargantuan” rel=”nofollow”>.…

    tnx for info!…

  37. Wallace Fri, 06 Feb 2015 09:03:06 UTC

    forces@posture.extinction” rel=”nofollow”>.…

    ñïñ çà èíôó!!…

  38. Vincent Fri, 13 Feb 2015 04:37:07 UTC

    bi@pavlovitch.transcultural” rel=”nofollow”>.…

    ñïñ çà èíôó….

  39. Theodore Fri, 13 Feb 2015 15:06:38 UTC

    johnnies@alden.mouthing” rel=”nofollow”>.…

    thanks for information….