Features, Specs and Maps

Posted by Andrew Premdas Mon, 31 Aug 2009 01:20:06 GMT

The Map is Not The Territory

I first came across General Semantics in the works of A.E. Van Vogt. His stories from sci-fi's 'golden age' had a common theme about the infinite potential of 'the human nervous system' which was very appealing to a brainy weedy kid. Part of the development of human kind, in these stories, involved a rejection of Aristotelian thinking. This Non-Aristotelian thinking is based upon Korzybski's General Semantics. But wait a minute what has this to do with code and RSpec and Cucumber?

One of General Semantics keys is "the map is not the territory", and that is what this article is all about. Looking at features and specs as maps helps me understand how to use these tools more effectively and also addresses some common frustrations that people new to BDD and these tools experience. But first a little aside into frustration and why its such a good thing.

Frustration is good - read the signs!

Frustration is good! What are you talking about!!

Frustration is your brains way of telling you that something is fundamentally wrong. Your brain is saying STOP! Think Again! So what do we do? ... Well most of the time we ignore our brain, or we blame something/someone, or we 'work our way through it'.

See frustration as a warning light on the dashboard of a car. Don't ignore it or you'll "run out of fuel", "blow up your engine" etc. Frustration is a great early warning system, telling you that you don't really now enough about what you are doing. So stop and use the most formidable thinking machine on the entire planet - your brain!

Features as Maps

If a feature is a map what is the territory? Well I think features are a two way map between territories. These territories are

  1. The intentions of the application sponsor (the person or persons deciding that the application should be built and what its contents will be)
  2. The application

The Qualities of Maps

All maps have a scale, which performs two functions

  1. Show the distance represented by any two points on the map
  2. Determine the level of detail shown on the map

Features have a large scale, they cover a wide area with little detail. They have to be this way. The first territory they map is a vague amorphous entity which cannot be seen, touched or expressed particularly easily. This is difficult terrain to map so features use (as much as they can) one of our most powerful tools - natural language. The second terrain they map is a small precise highly defined territory. In fact it is so precise that it can be accurately described and even executed using just two characters 0 and 1. Features don't really make much of an attempt to map this terrain. They are more interested in executing (in the computational sense) the terrain. However they can be quite useful as a map of other maps of the application, its UI and source code.

Feature Frustrations

This article was inspired by frustration expressed in a post on the Cukes mailing list. This frustration (IMO) is based on not understanding the scale of features map. Detail is not appropriate in features because detail is not in the territory of the application sponsor. If developers pollute the feature map with their level of detail then the application sponsor can no longer read the map. The map becomes too large and to dense.

Fortunately features have other important functions apart from being a map, so it is reasonable (essential?) to compromise on what they show. But once we understand how detrimental detail is to features then it becomes obvious that they must be supplemented with other tools.

Writing this has made obvious something that I was barely aware of that is that software has to be specced and tested by a suite of tools. An application is too complex a territory to have just one map. Once it becomes obvious that an application cannot be mapped by one map it is a small leap to realise that an application cannot be tested with one test tool, specced with one language. Just as we have to map at different levels we have to execute and test at different levels. I used to be frustrated by having to combine features with specs, and then think about controller specs, helper specs, view specs, user acceptance testing ... Now instead of being frustrated I realise that the need to learn a suite of tools and the necessity of creating a range of maps is just a natural result of the character of the territory I am working in.

Searching for empty form fields using rspec

Posted by Andrew Premdas Tue, 21 Jul 2009 10:05:00 GMT

Took me far to long to work out how to do this!

To find an empty input field we can look for

"input[type=text][value='']"

e.g.

response.body.should have_tag("input[type=text][value='']")

This is useful when filling in forms badly to make sure your bad values still remain when you return to the form to edit them.

Pluralisations in features

Posted by Andrew Premdas Wed, 29 Oct 2008 16:46:00 GMT

Been a bit of a dummy in this one. Basically each step definition is a regular expression so if we have

When /^I add (\d+) products to the cart$/ do |n|

we can improve this to make allow both

When I add 1 product to the cart
When I add 2 products to the cart

by changing our definition to

When /^I add (\d+) product[s]? to the cart$/ do |n|

In general a good tip is to put step definitions into rubular so we can play and see what will match.

Thanks to Aslak for this

Problem testing for empty div

Posted by Andrew Premdas Wed, 29 Oct 2008 07:07:00 GMT

Assert_Select is the main Rails tool for testing HTML output. RSpec wraps this function with its have_tag and with_tag methods. Using these methods effectively is key to writing features. However Assert_Select has some problems which caught me out recently.

What I wanted to do is make sure my cart could not be seen when empty

Scenario: Cart is hidden if empty
  Given there are 4 products
  And I am on the products page 
  And the cart is empty  
  Then I should not see the cart

The difficulty was with Then I should not see the cart. The output I get from an empty cart is

<div id='cart'>
</div>

The empty div is there so I can populate it using AJAX at some point. In rspec terms the following statements should be equivalent

response.should_not have_tag("div#cart>*")  

response.should_not have_tag("div#cart") do  
  with_tag("*")
end

response.should have_tag("div#cart") do  
  without_tag("*")
end

However only the first one works. Translating into Assert_Select we get

assert_select("div#cart") do
  assert_select("*",0)
end

assert_select("div#cart", 0) do
  assert_select("*")
end

Neither of these work. See rspec bug

Update

This was invalid when given a block assert_select tries to match the element specified (in this case div#cart) and then yields the matches. so you should be doing

assert_select("div#cart") do |elements|
  elements.each {|element| assert_select("*",0)}
end

My previous code on the other hand asserts that div#cart exists and then asserts that there are no elements at the top level rather than asserting that there are no elements that are children of the match.

Thanks to Frederick Cheung for this see rails bug

Paths in features

Posted by Andrew Premdas Thu, 16 Oct 2008 15:30:34 GMT

Been thinking about specifying paths in stories and steps, and the brittleness they can introduce to tests. This was prompted by a bug in rspecs 'render_template' matcher.

First of all the bug ...

This is rails testcode that works ok

assert_template("/")

This is the rspec equivalent

response.should render_template("/")

which throws the exception

wrong argument type nil (expected Regexp) (TypeError)  /Library/Ruby/Gems/1.8/gems/rspec-rails-1.1.8/lib/spec/rails/matchers/render_template.rb:22:in `match'
 /Library/Ruby/Gems/1.8/gems/rspec-rails-1.1.8/lib/spec/rails/matchers/render_template.rb:22:in `matches?'

Paths Don't Belong In Stories

Feeling more convinced about this the more I think about it. Basically a specific path is an implementation detail which can change depending (obviously) on how you implement. So if you want to view some products your story might be something like

When I click products
Then I should see products

This is more robust than

When I click products
Then I should go to /products

In a similar way when writing the steps you should use the products_path method rather than using '/products'

Getting started with Cucumber

Posted by Andrew Premdas Wed, 08 Oct 2008 13:56:58 GMT

Cucumber is the new runner for Rspec plain text stories. It has a number of improvements that make it essential, and it will soon become the default runner for rspec. To get started we need to do a few things

  1. Install the gem

    sudo gem install cucumber
    
  2. Run the generator for each rails project you want to have cucumber in.

    script/generate cucumber
    
  3. Update my command in ~/.profile that runs rails projects in textmate to include the features folder, and source ~/.profile

     ## this is ~/.profile
     ...
     # edit this directory for rails projects (etdr)
     alias etdr='mate app config features lib db public spec stories test vendor/plugins &'
    

Most importantly cucumber

  1. tells which matcher failed when a story fails
  2. throws an error if there are ambiguous matchers

Setting Up Textmate

Couple of bundles to install

cd ~/Library/Application\ Support/TextMate/Bundles                                                    
git clone git://github.com/bmabey/webrat-tmbundle.git Webrat.tmbundle  
git clone git://github.com/bmabey/cucumber-tmbundle.git Cucumber.tmbundle

Understanding Plain Text Story File Hierarchy

Posted by Andrew Premdas Fri, 20 Jun 2008 15:51:51 GMT

Plain text stories have quite alot of infrastructure that allows you to run the story file. I'm being pretty dumb in understanding this, so I'm going to analyse whats going on in detail so I have a better idea of what is being done and why.

I learnt some important lessons from doing this.

  1. If you don't understand a line of code break it down until you do. This will improve your ruby.

  2. irb is your friend, use it and script/console to explore code you don't understand

  3. Even though it takes time, exploring code is far more productive than skipping over it.

To start with I'll have to analyse various ruby statements in some detail. The first is from visitor.rb

require File.join(File.dirname(__FILE__).gsub(/stories(.*)/,"stories"),"helper")
  • require :loads a library, takes a path and tries to load whatever is there.

  • File : abstracts a file object - can be file, dir, symlink.

  • File.join :takes a bunch of strings and joins them together using the default file seperator (File::SEPARATOR)

From the above we can see that it looks like we are going to join a couple of strings together and that the result will end in helper

  • __FILE__ :name of file containing current code being executed. This will be visitor.rb

  • File.dirname(filename) gives the directory containing the file.

  • File.dirname(__FILE__) this gives the directory of the file containing the code being executed. This directory will be dependent on where the code is being executed from. So if we call the file from the directory its in we will get ".". However if we call from the directory above, and the directory the file is in is called 'story' then we will get "story". The return value will be a String

-- gsub(pattern, replacement) :this is a method of String, that returns a copy of the string with all occurences of pattern replaced by replacement

-- gsub(/stories(.*)/,"stories" :this will remove anything that follows stories in the string.

We know can work out a big part of this code

File.dirname(__FILE__).gsub(/stories(.*)/,"stories")

The idea here is that the file this code is in is somewhere in a file hierarchy under a directory called stories. What we want to do is go up to that directory, and we want to do that in a way that is independent of how far down we may be in the hierarchy. The following illustrates this (run in irb)

>> 'stories/stories/ppo'.gsub(/stories(.*)/,"stories")
=> "stories"

Now we can grok the whole line. The join will concatenate '/helper' giving stories/helper. So the line of code will load the code in the file helper which is located in the stories folder somewhere above this file.

Textmate File Type Detection (RSpec & Rails)

Posted by Andrew Premdas Sun, 01 Jun 2008 04:36:55 GMT

See this Textmate Blog post

Texmate RSpec Bundle

Posted by Andrew Premdas Sun, 01 Jun 2008 04:29:06 GMT

Do following

cd ~/Library/Application\ Support/TextMate/Bundles/
git clone git://github.com/dchelimsky/rspec-tmbundle.git RSpec.tmbundle

Then reload bundles in textmate if its open.

Adding RSpec to Rails Project using Git

Posted by Andrew Premdas Fri, 30 May 2008 15:09:47 GMT

Quick reminder how to do this...

Pre-Requisites

  • rails project
  • using git git init .

Installation

Plugins go in vendor/plugins

git submodule add git://github.com/dchelimsky/rspec.git vendor/plugins/rspec
git submodule add git://github.com/dchelimsky/rspec-rails.git vendor/plugins/rspec-rails

Then run

script/generate rspec

Note: You need to run the following two commands (or at least the last one) every time you clone this repo.

git submodule init
git submodule update

Older posts: 1 2