Using semantic meaning in features and user interfaces

Posted by Andrew Premdas Fri, 24 Apr 2009 05:59:00 GMT

WORK IN PROGRESS

This explains why I need to fork webrat, and why I believe features should rely upon css classes and id's.

I believe that the CSS class is generally the best mechanism for expressing the semantic meaning of a particular piece of html (combined with DOM location and html tags). For example if somehow in viewing an order we want to reorder the order then I would first produce

.order
  .reorder
    # re-order goes here

This clearly establishes the semantic meaning - that I can reorder an order. Now the actual UI could be a number of things

  • Instructions: Reorder by phoning 0800 555 5555 and quoting order#
  • a button [reorder]
  • a link click <here> to reorder

Now as far as my business requirement and feature is concerned I don't care about these implementation details, and these details are certainly volatile. However the semantic layout is fixed. Here I see a representation of an order and can reorder the order.

I can now refine this feature to state that I should be able to tell the order to reorder. When I do this I should see some response in the UI which indicates whether my command has been successful.

When I reorder
Then I should see ...

In web applications you can do this in two ways

  • follow a link
  • submit a form (by pressing a button)

Again my feature does not care how I reorder unfortunately with webrat I now have to choose - I can either 'click' or 'press'.

.order
  .reorder
    %a{:href => order_path(:id => order.id)}= h(order.name)

above I am 'clicking' a link, whilst below I am pressing a button

.order
  .reorder
     -form_for @order do |f|
       ... 
       f.submit "reorder"

Now as far as my feature is concerned I don't give a damn how the re-ordering is done I just want to reorder. However I have to change my scenario (yes I know I can hide this change to a degree by re-using steps and having fancy step definition).

Scenario: Reorder
Given there is an order
When I view orders 
And I click reorder
Then ...

     OR 

Scenario: Reorder
Given there is an order
When I view orders 
And I press reorder
Then ...

Webrat compounds this frustration by making it really hard to press or click. Currently it will click only using text, id or an href, and its even more restrictive when it comes to pressing. So my first improvement was to extend click link to also be able to use classes. Now at least I can do

click link reorder

Although I have to change my implementation code

.order
    %a{:clases => reorder, :href => order_path(:id => order.id)}= h(order.name)

Features for a Resource

Posted by Andrew Premdas Tue, 07 Apr 2009 04:33:32 GMT

REST is great, and resources are a great design tool. If we design our rails application around resources we can get alot of things for free. If we apply the same idea to features can we create a standard set of features to give us a quick start for each resource we identify?

Resources - Object and Collection

With standard rails routing we will get various named routes which we can use either directly in our features or indirectly in our step definition

For example if our routing table has

     new_admin_product GET    /admin/products/new
 destroy_admin_product GET    /admin/products/:id/destroy
    edit_admin_product GET    /admin/products/:id/edit
         admin_product GET    /admin/products/:id

              products GET    /products
               product GET    /products/:id

Then we can easily write

When I view products

and match this with

When /^I view product$/ do 
  visit products_url
end

In a similar way we can do things like

Given there is a product with name widgit
When I view the product

matched with

Given /there is a product with name (\S+)$/ do |name|
  Product.generate!(:name => name)
  @x = Product.find_by_name(name)
  @x.should_not be_nil
  @x
end

When /^I view the product$/ do 
  # use the variable x from above
  visit product_url(@x)
end

Its fairly straightforward to extend these features and steps to match the other actions, and with a bit of ruby and regex fu we should be able to generalise these features so we can use them with any resource

Resource Views

The simplest way to do this is to use CSS classes. I take this approach because it is robust. Object are represented in a div with the singular name as a class and collections use the plural. e.g. in Haml

.product 
  product details go here

.products
  list of products go here

Resources - Info (A Basic Data Definition)

I think it might be a nice idea to describe a resource on its list page using some text in an div#info. e.g.

#info
  Products are things that we actually sell. They come from wholesale_products which are supplied to us.
.products

This is just a little convenience which will be replaced/hidden when we start doing specific work with the resource.

Composing Features

Posted by Andrew Premdas Thu, 19 Mar 2009 02:20:24 GMT

Composing Features

Writing good features is an art-form, a very new art-form that people are just beggining to explore. Of course people have been specifying how things should work throughout history, and people have been trying to specify what software should do (with varying degrees of failure!) since software has been written.

You can compare writing features to a new school of art e.g. pontillism. Pontillism built itself on the existing foundations of painting and art - in particular colour theory - and applied these using a new technique to create something a little different. In our new school of features we produce something a little different (a plain text executable) with a new tool (Cucumber) and the application of old and new techniques. To do this well we have to learn what works and what doesn't.

Asking Questions

When we write a feature we need to ask lots of questions. A feature is an exploration of something we want to do. Starting a feature is focused on two things, loosely describing the feature and asking the most important question.

  • Why do we want to do this?

The first temptation when writing features is to skip this question! After all we know we need the feature, otherwise we wouldn't be writing it in the first place. And we want to get on with things! What's the point in wasting time on this question?

Well your about to commit a substantial amount of your time writing this feature and a substantial amount of other peoples time in implementing this feature. If it takes you 20 minutes to answer this question properly and you discard one in fifty of your potential features you will still have saved loads of time and effort.

To help you answer this question you can consider the following questions

  • Whats the point?
  • Can it wait?
  • Is there something more important we need to do first?

Writing Scenarios

Once we get to the stage of writing scenarios, we should be fairly confident that we are not going to be wasting our time writing the scenarios. In addition to specifying what should be done, writing scenarios help us to discover things about our feature. As we write our feature we should be open to the following ideas

  • We need to do something else first!
  • We don't need to do this now!
  • This is pointless!
  • We don't need this!

Hopefully the point is made now!

Writing Scenarios (the details)

As you write a scenario its a good idea to ask a few more questions.

  • What am I assuming?
  • Where does this come from?
  • Is this a compound statement?
  • Do I know what this really means? And even if I do can anyone else?
  • Who can do this?
  • Who should not be allowed to do this?
  • Is this name ambiguous, clumsy, accurate, intuitive?

To write scenarios efficiently we need to develop the skills to ask the write questions at the right time. A warning though - not asking questions, guarantees you will write rubbish, be innefficient, waste other peoples and never develop the skills to write scenarios quickly. Remember this is an art form you have to really work at it to get good.

I'll try below to give some illustrations of applying these questions

Examples (ECommerce Products)

NOTE: Just sketching out things here for the moment

Scenario: A product should

Illustrate composition using VAT Ask the questions

Where does the Supply Price come from? Is it more important to write about this first? Where does the VAT rate come from.

Stop writing this feature and write the feature that sets the VAT rate. As your doing this ask

How many VAT rates? What happens when the VAT rate changes? - to new sales, to old sales How do we change the VAT rate

  • write the feature

Feature As a site admin I need to change the VAT rate for all products In order to comply with the national changes to VAT caused by the credit crunch

Do we need to keep the VAT rate with each purchase?

Is the VAT rate

If the supply price comes from

Complications with users and customers

Posted by Andrew Premdas Sat, 08 Nov 2008 07:13:35 GMT

I find it very easy to get paralysed by complexity. This has been happening on my current project with users and customers. You see I want people to be able to buy things without having to be users. However I also want it to be especially easy for users to buy things. Finally I don't want any particular person to have seperate records in user and customer but a customer can be user.

Now this doesn't really make sense, and I haven't really written the half of it! And all these possibilities have ... well to be frank left me paralysed. Now eventually I might get my head around all of this but is there a better way? Can I use features to explore this problem bit by bit, just dealing with one little piece at a time. Will a reasonable solution emerge

Here is the first scenario I'm going to use

Scenario: Customer with existing user
  Given there is an activated user shirley
  Given I am logged out
    And I am asked for my customer details
   When I fill in my customer details with shirleys details
    And I press "next"
   Then I should see an error explanation

This scenario is at a pretty high level and implemented using a number of compound steps.

What this does is go through a number of things to get to a form where we enter in the details required to become a customer. This is part of a wizard, after the customer step comes the billing step.

So what I'm saying is that if I fill in my customer details with an existing user then I should get an error with an explanation. Making this story pass was actually very simple, perhaps I have made a bit of progress with this problem - so what next?

Well lets assume the error explanation tells the user the correct thing to do then we can

When I login as shirley
Then I should be at billing step

When I login as shirley has a number of design implications which we can now deal with. These are made real by implementing the step. With the stuff I have so far the step would be implemented by

When /^I login as shirley$/ do
  visits login_path
  fills_in(:login, :with => @shirley.login) 
  fills_in(:password, :with => @shirley.password) 
  clicks_button
end

And to make my story work I have to go back to where I was

When I login as shirley
And I go to checkout
Then I should be at billing step

Getting this to work is a good thing. But now I really need to refine this as my customer has to

  1. visit the login path
  2. fill in the login form and submit it
  3. return back to the checkout

and this is far to much to expect. So going back to our original story

When I login as shirley
Then I should be at billing step

we can see that we want to implement When I login as shirley as

When /^I login as shirley$/ do
  fills_in(:login, :with => @shirley.email) 
  fills_in(:password, :with => @shirley.password) 
  clicks_button
end

which means we need a login form on our page, and this form needs the ability to login using an email. A couple of options come to mind here

  1. Implement the login form as part of the error explanation with the login field filled in with the email address.
  2. Have the login form on every page and point the user to it with the error explanation.

So I'll have a think about this for a while; but note how we have made some progress, have a simpler decision in front of us, and have some code which is testing what we are doing and will show whether our solution is working.

Update

By setting

after_filter :store_location, :only => [:edit]

in the OrdersController I can now make

When I login as shirley
Then I should be at billing step

pass, as when I return from the login form to the wizard when I log in. So for now I can postpone the decision about having a login form on this page or on every page and have the alternative of putting some better instructions in response to the email in use error.

To deal with login with email I created a step

When /^I login as (\S+) with my email address$/ do |login|

used this step in the scenario

and implemented the functionality by changing how User authenticates from

def self.authenticate(login, password)  
  u = find_in_state :first, :active, :conditions => {:login => login} # need to get the salt
  u && u.authenticated?(password) ? u : nil
end

to

def self.authenticate(login, password)  
  if RE_EMAIL_OK.match(login)
    u = find_in_state :first, :active, :conditions => {:email => login} # need to get the salt
  else  
    u = find_in_state :first, :active, :conditions => {:login => login} # need to get the salt
  end
  u && u.authenticated?(password) ? u : nil
end

n.b. RE_EMAIL_OK is a regex

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

Features Controllers and HTTP_REFERER

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

Had a problem with a feature failing. Was getting an error

When I add a product to the cart                     # features/steps/cart_steps.rb:11
      Cannot redirect to nil! (ActionController::ActionControllerError)
      /Library/Ruby/Gems/1.8/gems/actionpack-2.1.1/lib/action_controller/base.rb:1044:in `redirect_to'

The additional huge stack trace led me to my cart controller. Here I was using the rather standard sort of code

respond_to do |format|
  format.html { redirect_to request.env["HTTP_REFERER"]} 
end

Problem is that in the test environment request.env["HTTP_REFERER"] is nil. We have a couple of choices

  1. Set HTTP_REFERER in the test environment
  2. Program defensively to deal with nil HTTP_REFERER

In the end did the second (mostly because couldn't get first to work quickly)

  respond_to do |format|
    format.html { redirect_to referer} 
  end
...

private
def referer
 ref = request.env["HTTP_REFERER"]
 ref ||= '/products'
end

I actually quite like this solution.

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'