TDD and Debugging in Ruby/Rails?

Developing a Ruby/Rails application TDD style, and then in the middle of your spec something odd happens: Wouldn’t it be nice to fire up the debugger exactly in that spot? Here is how I am doing this right now, while using Rails, rspec and guard. Well, and pry, which in itself is worth checking out.

Let’s say I have the following spec:

describe User do
  it "does not allow the same user name again" do
    user = User.create(first_name: 'Tom', last_name: 'Sawyer')
    User.create(first_name: 'Tom', last_name: 'Sawyer').should have(1).error_on(:first_name)
  end
end

The test fails as follows, but why? I’ve just added the appropriate uniqueness constraint in the User model, so what’s going wrong?

1) User does not allow the same user name again
 Failure/Error: User.create(first_name: 'Tom', last_name: 'Sawyer').should have(1).error_on(:first_name)
   expected 1 error on :first_name, got 0
 # ./spec/models/user_spec.rb:6:in `block (2 levels) in  (required)>'

Something odd might be happening, so I call pry to the rescue by adding two lines into the test:

describe User do
  it "does not allow the same user name again" do
    user = User.create(first_name: 'Tom', last_name: 'Sawyer')
    require 'pry'
    binding.pry
    User.create(first_name: 'Tom', last_name: 'Sawyer').should have(1).error_on(:first_name)
  end
end

Guard re-runs the test and stops with a REPL. A quick debug session (reproduced below) reveals what the issue is:

Running: spec/models/user_spec.rb
From: /Users/chris/Documents/odr/spec/models/user_spec.rb @ line 7 in RSpec::Core::ExampleGroup::Nested_2#N/A:
   2:
   3: describe User do
   4:   it "does not allow the same user name again" do
   5:     user = User.create(first_name: 'Tom', last_name: 'Sawyer')
   6:     require 'pry'
=>  7:     binding.pry
   8:     User.create(first_name: 'Tom', last_name: 'Sawyer').should have(1).error_on(:first_name)
   9:   end
  10: end
pry(#)> User.count
=> 0
pry(#)> user.errors
=> #,
@messages={:email=>["can't be blank"], :password=>["can't be blank"]}>
pry(#)>

Aha, the first user didn’t even get created! I better provide values for email and password. A quick change to the spec makes guard run it again, this time it succeeds:

describe User do
it "does not allow the same user name again" do
  user = User.create(first_name: 'Tom', last_name: 'Sawyer',
                     email: 'tom@twain.com', password: 'secret')
  User.create(first_name: 'Tom', last_name: 'Sawyer',
             email: 'tom@gmail.com', password: 'whateva').should have(1).error_on(:first_name)
  end
end

Note: I didn’t use anything to facilitate sample data for this test. In order to stay sane using factory_girl or fixtures is the way to go. Ryan Bates has created an excellent 15 minute intro on how he does all that.

Playing with CoffeeScript and Backbone.js

Note to self:

Backbone.js looks promising when it comes to giving JavaScript client-side code for browser-based applications a solid structure. CoffeeScript is a more concise syntax than JavaScript, but it's "just" another syntax and compiles to JavaScript.

In order to understand and work with both in combination, I need to learn some basics (some better JavaScript, actually).

The Backbone.js tutorial by James Yu helped me understanding the basics of Backbone.js.

Automated Black Box Testing for a Browser based App in 30 Minutes

The theory is: Automated testing is a safety net for making changes to an application in development: If I break anything, it will be noticed (by the automated test). But more importantly, it is a safety net for all those changes required in the future, by myself, or by others being responsible for keeping the application alive and its users happy.

But, automated testing comes with a cost: It has to be set up, configured, integrated with the technology stack at hand, and last but not least understood by the developers that are supposed to implement the tests.

Here, I want to show that the effort for set up and configure of automated testing can be small. Here, I use cucumber and watir for this.

The application I'm using as an example here performs some integration features, and there's an http/html based browser interface for configuration. The configuration page is unprotected, and this is exactly what needed to be changed: If I go to http://my-server/config, I should be prompted (via http basic auto) for username and password.

Formulated as a feature and two scenarios, this looks as follows:

Feature
In order to "execute" this, I create a new directory and save the feature text in features/protection.feature. Next, I need cucumber installed, which will make my feature description executable.

I can try executing my feature already now by just running 'cucumber' in my new directory. It will fail, but will tell me what needs to be implemented to succeed:

Pending_step_definitions

This are valid statements in terms of cucumber's DSL, basically matching the given/when/then statements of the scenarios to executable code. Where it says "pending", we have to fill in (Ruby) code.

Before we do this, we need some configuration in support/env.rb, so that we can use the Firefox browser via watir:

Environment

This just tells cucumber that rspec and watir are required, that we use a Firefox browser via a member variable @browser, and what the host is we want to talk to. This is black box testing, so we talk to the application via the browser only.

I also use a Gemfile with bundler to install what I need:

Gemfile

Running 'cucumber' now will still fail, we need to implement the pending step definitions in a file 'step_definitions/basic.rb':

Implemented_step_definitions

These step definitions match the when/then/given statements from the scenarios, and are implemented in ruby.

Now the test succeeds, if the feature is implemented properly. And voila, we've added automated black box testing:

Successful_cucumber_run

Okay, there's one glitch: When using basic authentication this way, the Firefox browser prompts the user with a confirmation dialog. I have to confirm this dialog, otherwise the test will block. There are various solutions to this, see for example this one.

 

Is Testing Worth It?

Scott Ambler stated in an article:

If it's worth building, it's worth testing.

Nobody would seriously argue that you shouldn't test when developing software... the question rather is how to do it. Test driven development suggests that you should write the (automated) test before the actual implementation for any feature or change of a piece of software.

Compelling results of this approach are:

  • Good test coverage
  • Potentially better design, because you use your API even before you define it
  • And probably other benefits, e.g. the developer writes just enough code to make the (originally failing) test pass.

Unfortunately though, being test driven in this way comes with a cost ($$$): If, for example, the feature or change is UI related but the UI cannot be tested via automated tests, creating this test may take a lot of time, the test may be slow to execute, or there may be other reasons why "test first" wouldn't work. So, if the technology choices didn't take testability of all its components into account, costs may outweigh the benefits of a test driven approach. (But you still test, right? ... see Scott's quote.)

I'm excited to see technologies around Ruby and Rails taking these considerations into account from the start, see for example Michael Hartl's Rails Tutorial: Creating the test first, even if its the UI, is a lot cheaper in Ruby/Rails than in Java.

Below is a machine performing a three point flexural test. Probably this test cannot be executed automatically whenever I commit a change to the source code repository...

www.gnu.org/copyleft/fdl.html) or CC-BY-SA-3.0-2.5-2.0-1.0 (www.creativecommons.org/licenses/by-sa/3.0-2.5-2.0-1.0)], from Wikimedia Commons">Three point flexural test

Learning ... just enough to be dangerous

A good attitude to learn new stuff outside of your comfort zone is to be curious... and "learn just enough to be dangerous". Mike Rundle blogged about this and gives good IT-related examples.

The example I want to add: If you believe programming language X (or C or J) is *the* best solution for your challenges or your client's needs, rather dabble a bit in other programming languages (just enough to be dangerous) and think again. You'll probably learn that "it depends".

Photography Prints

How To Learn Something Daily While Having Fun

I like learning stuff. And it is important to constantly learn to keep up with the changes, especially in an IT environment. But more often than not, we practically don't spend (enough) time learning.

I think I found a new way how I can learn something daily, with little effort, and in a way that is fun. It works as follows:

I go to stackoverflow.com and type my topic of interest (e.g. "Java") into the search box. The latest questions asked appear. I pick two of the questions and try to answer them, without looking at the answers. Then I open the answers and read all of them.

  • One question recently was about the exact semantics of the post increment operator ++. The answer is not too hard (I find), but the question was asked by a teacher who couldn't explain why he got the behavior described. This shows how important good teachers are.
  • Yet another question was a beginner not understanding that member variables with identical names don't override but simply hide those in super classes. I found it amazing to read the discussion about the "right" solution.

Now, what have I actually learned?

  1. Something technical. Okay, the details of the post increment operator weren't new to me, but the various ideas about how to properly override "properties" in Java reminded me that the "right" solution depends on many things (i.e. there is no single, "right" solution) and the "right" solution isn't obvious either; rather learn to ask the right questions first…
  2. Something about the variety of skill levels. There are guys out there that are smarter than me and guys that have no clue. It is good to be reminded of where you fit in roughly.

Next time, I'm sure I'll learn something different.

How do you make sure you learn something new daily?

P.S. This one may be useful as an interview question.

 

"Joint problem solving" instead of "deliverables"?

When the team is under pressure and the stakes are high... how can one avoid the team becoming dysfunctional?

I don't have an answer, sorry. But there's some recent research on the topic, and the author, Heidi K. Gardner, even has some recommendations.

One advice that resonated well with me is: Understand the project and the effort of the team as "joint problem solving" and include the client in the team, instead of seeing the project as a "black box deliverable."

 

A pressure cooker... where the pressure is high

Can you imagine the weight of code?

What about the following notion:

A Java code base of 5300 lines of code (measured using Sonar) could be printed as a book, resulting in 187 pages full of Java code. (I've done this "printing" using a2ps on the concatenated Java files in a project I was involved with recently.) That compares to a medium-sized novel.

I can well imagine the weight (or: time to read / digest / whatever) of a medium-sized novel.

Unfortunately I came across code bases with 53'000 lines of code and even 530'000 lines of code in the past. The latter would be 18700 pages of text. This is more than half of Encyclopedia Britannica which has 32640 pages.

When I have to imagine the burdon of maintaining a (Java) code base next time, I'll look at those figures again, they'll help me imagine the weight of that code.

 

Encyclopedia Britannica Hardcover