Specifying raising of errors in RSpec

RSpec allows a developer to specify that an error is raised within a block with the raise_error method. It's a nice expressive way of saying that your code should fail when it needs to.

But my tiny brain has often been confused with using it at times, more so when the error class requires parameters for instantiation and when used in conjunction with the and_raise method on a stub or a mock

Consider the snippet below where my Widget depends on Thingamajig to do its funky thing in order to run. But Thingamajig is rigged to explode in a mass of funkyness and make Widget all useless.

CODE:
  1. describe Widget do
  2. it "should re-raise errors as a Widget::UnrecoverableError" do
  3. # expectations
  4. thingamajig = stub(Thingamajig)
  5. thingamajig.should_receive(:do_funky_thing).and_raise(Thingamajig::FunkyExplosion.new('The funky thang exploded yo'))
  6.  
  7. # our SUT
  8. widget = Widget.new(thingamajig)
  9.  
  10. #verification
  11. lambda { widget.run }.should raise_error(Widget::UnrecoverableError, 'The funky thang exploded yo')
  12. end
  13. end

Do you notice the inconsistency between the way errors are declared in the expectation and the actual verification?

CODE:
  1. # expectations
  2. thingamajig.should_receive(:do_funky_thing).and_raise(Thingamajig::FunkyThingExplosion.new('The funky thang exploded yo'))
  3.  
  4. #verification
  5. lambda { widget.run }.should raise_error(Widget::UnrecoverableError, 'The funky thang exploded yo')

The expectation on the stub, 'thingmajig' needs the exception instantiated first while verification requires the class name and parameters used to instantiate the error instance.

And no, doing it like this doesn't work as expected:

CODE:
  1. lambda { widget.run }.should raise_error(Widget::UnrecoverableError.new('The funky thang exploded yo'))

It's an unfortunate impedance mismatch that might be caused due to the way Ruby handles the raising of errors.

Comments

Using cruisecontrol.rb with repositories without anonymous access

I decided to use cruisecontrol.rb for continuous integration of an application that I'm working on at Bezurk. So I downloaded the 1.2.1 release from RubyForge and proceeded to add the project repository to the local installation of cruisecontrol.rb.

douglas@macbookpro:~$ ./cruise add MyProject --url http://path.to/repository --username 'douglas' --password 'guessable'
douglas@macbookpro:~$ ./cruise build MyProject
Builder for project 'MyProject' started
Logging to: /Users/douglas/Development/Ruby/cruisecontrolrb-1.2.1/log/MyProject_builder.log
Build loop failed
BuilderError: svn: PROPFIND request failed on '/svn/my_project/trunk'
./script/../config/../app/models/subversion.rb:98:in `execute_in_local_copy'
./script/../config/../lib/command_line.rb:86:in `call'
./script/../config/../lib/command_line.rb:86:in `e'
./script/../config/../lib/command_line.rb:84:in `popen'
./script/../config/../lib/command_line.rb:84:in `e'
./script/../config/../lib/command_line.rb:71:in `execute'
./script/../config/../lib/command_line.rb:70:in `chdir'
./script/../config/../lib/command_line.rb:70:in `execute'
./script/../config/../app/models/subversion.rb:89:in `execute_in_local_copy'
./script/../config/../app/models/subversion.rb:85:in `chdir'
./script/../config/../app/models/subversion.rb:85:in `execute_in_local_copy'
./script/../config/../app/models/subversion.rb:44:in `latest_revision'
./script/../config/../app/models/project.rb:228:in `new_revisions'
./script/../config/../app/models/change_in_source_control_trigger.rb:8:in `revisions_to_build'
./script/../config/../vendor/rails/actionpack/lib/../../activesupport/lib/active_support/core_ext/symbol.rb:10:in `__send__'
./script/../config/../vendor/rails/actionpack/lib/../../activesupport/lib/active_support/core_ext/symbol.rb:10:in `to_proc'
./script/../config/../app/models/project.rb:223:in `collect'
./script/../config/../app/models/project.rb:223:in `revisions_to_build'
./script/../config/../app/models/project.rb:202:in `build_if_necessary'
./script/../config/../app/models/polling_scheduler.rb:13:in `run'
./script/builder:79
./script/builder:78:in `catch'
./script/builder:78
./cruise:14:in `load'
./cruise:14:in `builder'
./cruise:68:in `send'
./cruise:68
/opt/local/lib/ruby/1.8/fileutils.rb:121:in `chdir'
/opt/local/lib/ruby/1.8/fileutils.rb:121:in `cd'
./cruise:67

Hmm, what's with the svn: PROPFIND error? Looking at the stracktrace doesn't tell me alot about what's going wrong here, let's try logging errors to the console.

--SNIP--
douglas$ svn --non-interactive info --xml
douglas$ svn --non-interactive log --revision HEAD:20 --verbose --xml
svn: PROPFIND request failed on '/repository/trunk'
svn: PROPFIND of '/repository/trunk': authorization failed (http://svnhost.com)
--SNIP--

It happens that my repository does not have anonymous access and requires a subversion user account to do anything useful. So it should be obvious that cruisecontrol.rb is trying to get log info from the repository but subversion is quitting with authentication errors because no user credentials are being supplied.

I need to have cruisecontrol.rb make use of the --username and --password options when making queries to the repository when I give it the credentials for access.

My first stop is the app/models/subversion.rb. Only the checkout method uses the username and password instance variables. Subversion should only include the --username and --password options when executing svn commands when both the username and password instance variables are present.


RUBY:
  1. test/unit/subversion_test.rb
  2.  
  3. def test_svn_command_uses_user_password_when_provided
  4.     svn = Subversion.new(:username => 'jer', :password => "crap")
  5.  
  6.     svn.expects(:info).with(dummy_project).returns(Subversion::Info.new(10, 10))
  7.     svn.expects(:execute).with(["svn", "--non-interactive", "log", "--revision", "HEAD:10", "--verbose", "--xml",
  8.                                 "--username", "jer", "--password", "crap"],
  9.                                 {:stderr => './svn.err'}).yields(StringIO.new(LOG_ENTRY))
  10.  
  11.     svn.latest_revision(dummy_project)
  12. end
  13.  
  14. app/models/subversion.rb
  15.  
  16. def checkout(target_directory, revision = nil, stdout = $stdout)
  17.     @url or raise 'URL not specified'
  18.  
  19.     options = [@url, target_directory]
  20.     options <<"--revision" <<revision_number(revision) if revision
  21.  
  22.     # need to read from command output, because otherwise tests break
  23.     execute(svn('co', options)) do |io|
  24.     begin
  25.         while line = io.gets
  26.             stdout.puts line
  27.         end
  28.         rescue EOFError
  29.         end
  30.     end
  31. end
  32.  
  33. def svn(operation, *options)
  34.     command = ["svn"]
  35.     command <<"--non-interactive" unless @interactive
  36.     command <<operation
  37.     command += options.compact.flatten
  38.     command += ['--username', @username, '--password', @password] if @username and @password
  39.     command
  40. end

The username and password would then be injected into the project's Subversion instance in the cruise_config.rb file for each project.


RUBY:
  1. Project.configure do |project|
  2.     project.source_control.username = 'douglas'
  3.     project.source_control.password = 'guessable'
  4. end

I've submitted a ticket along with a patch for this on cruisecontrol.rb's tracker. Keep a lookout for it if you happen to encounter the same problem.

2 Comments

Writing tests first is…?

What would you do if your co-developers say "Writing tests first is too hard/slows me down/waste of time"?

  1. Evangelise test first development by surreptitiously providing publications on the topic
  2. Practice it on projects and try to test infect other people.
  3. Grow weary of going at it alone and move on.

Comments

Behaviour Driven Development != Testing

Testing: Executing a program with the specific intent of uncovering errors.

Software Engineering: A Practitioner's Approach - Roger S Pressman

The definition of testing by Pressman states that the purpose of performing software testing is to detect errors in a program. This encompasses a wide range of techniques such as black box/white box testing, basis path testing, fault based testing and at a more thorough level, control structure testing.

So where do the specifications in Behaviour Driven Development (BDD) fit into the picture? In this respect, BDD isn't about testing at all. We write specifications to say that the software exhibits a specific behaviour when its in a certain state. The specifications serve to reinforce the notion that the program is working as expected under known conditions.

Having 100% coverage for code certainly does not mean that a program is free of errors, there are still edge cases that may be too difficult or complex to replicate with an automated test suite. Traditional QA testing is still very much relevant to software projects with BDD employed religiously throughout development.

This has been discussed at length before but it bears repeating: BDD is a design technique that gives you executable documentation of what functions the software is expected to provide.

Comments