You Can Start at Login!

On one of the lists I hang out at, Marton was wondering about how to start off a project that required authenticated users…

How would you decouple the sign-up feature from the login feature to keep the stories independent and testable from the UI only. The acceptance criteria should not fiddle with any implementation details such as concrete url-s or field types. I’d like to leave that entirely to the developers. Let’s say I have the following story coming to mind in a workshop:

As an Anonymous user I want to sign up to Example.com website to enjoy the benefits of a registered user.

Acceptance Criteria:
1. I navigate to the signup page
2. I enter my full name, desired username and my password
3. I have to verify my password to avoid any typos
4. I submit my information

And here comes the “tricky” bit:
5. I can log in with the credentials I provided.

Some folks suggested he start on real functionality first, others implied login was a lousy first story.

My take: It’s not a hard story to start at and have a meaningful test that can grow over time.

For example, to start, you can simply check that the response has “Login Succeeded” ( or “Login Failed” for testing that a bogus login attempt does indeed fail).

In Cucumber flavor:

Scenario: Registering as a new user
    Given I am a new user
    When I visit the site
    Then I can register

Scenario: Logging in as a registered user
    Given I am a registered user
    Then I can login
    And enjoy the beauty of the website

Or, even more simply:

Scenario: Successful Login
    When I login as admin
    Then I should be logged in

Scenario: Failed Login
    When I login as asdf56ghasdkfh
    Then I should be not logged in

And your steps would hide the logic for filling in the login form and checking for success:

Given /^I login as "([^"]*)"$/ do |login|
  @login_name = login
  visit login_path
  fill_in "login", :with => login
  fill_in "password", :with => "password"
  click_button "login_button"
end

Then /^I should be logged in$/ do
  response.should contain "Login Succeeded"
end

Then /^I should not be logged in$/ do
  response.should contain "Login Failed. Please try again."
end

Your login details can change over time, adding the password confirmation box, handle validation errors, etc.

Your next step might be to show Admin-user-only functionality as I have here (snagged from a current app):

Feature: Quickly exercise the primary UIs just to make sure nothing blows up

  Background: We need to pre-populate the database with the simulator results, and be logged in as admin
    Given I login as "admin"

  Scenario: Brief tour around the Admin UI
    Then I should be able to click on "Dashboard" and see "Admin Management"
    And I should be able to click on "Accounts" and see "Search"
    And I should be able to click on "Sign out" and see "Login to Your Account"

And so it goes, step-by-step.

Hope that helps!

As an update… one of the list contributors went so far as to indicate “Login has Zero business value, always.” No wiggle room there!

well, okay, if you are parsing the act of logging in to achieve a status of an authenticated user to provide a means of secure access as separate from the need to have secure access, well, ok.

yes, i didn’t separate the two. The point was merely to show the OP how you could easily write his desired login and registration story, and it is not that hard.

As an aside, another project I did with two friends required a “marketing” site where you could sign up and see info about the core application. The project also required the site where registered users would do the real work of the application.

Since I knew login would be easy to add, I suggested to my one friend how to do it, and proceeded ahead with the other functionality. This was based on knowing full well that I would be able to feather in the user authentication and access rights into the core app once they were completed in the marketing app.

So, I am not hard over one way or the other on this issue, and in the past 12 months have done it both ways on different projects. Hence my surprise at folks’ having such stark “black and white” edicts banning ever starting at Login as a story.

Now, were I on a project where there was some very risky bit that, should we not solve that problem adequately, nothing else matters, guess where we start? Not login. 🙂

Geesh.

Oh, George posted a reply of sorts here.

Mongo Remembers All Keys

On the MongoMapper group list, Nick was wondering about getting key names from the model. But he noticed it remembered keys that had once been used… He wanted to only be able to see the current state of his MongoMapper class, I suppose… No dice, Nick!

Remember, MongoMapper Don’t Care! MongoMapper also does not forget! You can always see what keys were ever used as demonstrated here:

MongoMapper.database.collection('users').drop
class User
  include MongoMapper::Document

  key :name, String, :required => true
end
User.destroy_all
text = []
text << "After model with key :name, String"
text << User.keys.keys.inspect

text <<  'User.create(:name => "Fred")'
User.create(:name => "Fred")
text <<  User.keys.keys.inspect

text <<  'User.create(:name => "Fred", :email => "me@me.com")'
User.create(:name => "Fred", :email => "me@me.com")
text <<  User.keys.keys.inspect

text <<  'User.destroy_all'
User.destroy_all
text <<  User.keys.keys.inspect

text.each {|t| puts t}

You can see how the model keys reflect what is in the model class and in the actual document store (that is, dynamically added via a create):

After model with key :name, String
["name", "_id"]
User.create(:name => "Fred")
["name", "_id"]
User.create(:name => "Fred", :email => "me@me.com")
["name", "_id", "email"]
User.destroy_all
["name", "_id", "email"]

Now let’s extend the model class to add a new city key:

class User
  include MongoMapper::Document
  key :name, String, :required => true
  key :city, String
end
text = []
text <<  'Extended the class, adding city'
text <<  User.keys.keys.inspect
text.each {|t| puts t}

As expected: there is the new key:

Extended the class, adding city
["city", "name", "_id", "email"]

Removing Keys

If you accidentally added keys, then you should remove them. For example, I accidentally had an uppercase key in the model for a while (oops). Here is how I eradicated it from the database store:

  def self.purge_msid_key
    uppercase_msid_acts = Account.where(:MSID.exists => true).count
    if uppercase_msid_acts > 0
      Account.unset({}, :MSID)
    end
  end

Related MongoMapper Issue: Track Loaded Keys at the Instance Level

You Don’t Always Have to Follow “The” Rules

A user was asking the following on the Agile Modeling list:

What experience does anyone have about standards for stories written for
non-UI components? I’m working with a proxy PO who feels the standard story
format (As a <user> I want to <activity> so that <purpose>) simply won’t
work for something that doesn’t have a user interface. Imagine, for this
example, a project that has the sole purpose of encrypting data without any
user interaction.

For my needs, I often apply the principles behind the concepts, if not always the exact template of a suggested practice. Take for example, the use of my favorite tool, Cucumber, to write Acceptance Tests. Typically, cukes are written from the user point of view in classic “Given – When – Then.” But sometimes I like to use the cucumber style for testing APIs that I am building.

Here is one example at the Cucumber level (with the companion RSpec shown below):

Feature: Version 2.0: As we parse PDFs, we need to be able to collect a list of fonts
  as a way to help discern the structure of the parsed document based on
  heading levels, density of upper case text, and what not.

Scenario: Parsing a simple document
  Given a sample set of text
  |reference|points|value|
  |R9| 10| Patient: FRANKLIN, BENJAMIN|
  |R9| 10| CHIEF COMPLAINT:|
  When i parse the text
  And provide a set of base fonts
  |ref |basefont|
  | R9 | Helvetica-Bold|
  |R10 | Helvetica     |
  Then I should have the following font stats
  |reference|points|upper_cnt|lower_cnt|percent|
  |R9       | 10   | 4       | 1       | 83    |
  And the following font names
  |reference |points|basefont|
  |   R9     |  10  |Helvetica-Bold|
  |  R10     |  10  |Helvetica     |

Scenario Outline: Parsing a simple document
  When collecting a set of text , ,
  Then I should have  and  word counts and  Uppercase stats

  # Note: the counts are cumulative
  Examples:
    |reference|points|value|upper_cnt|lower_cnt|percent|
    | R9| 10| "Patient: FRANKLIN, BENJAMIN" |  2 |  1 | 66 |
    | R9| 10| "CHIEF COMPLAINT:"            |  4 |  1 | 83 |
    | R9| 10| "PRESCRIPTIONS"               |  5 |  1 | 89 |
    |R10|  9| "Motrin 600mg, Thirty (30), Take one q.i.d. as needed for pain, Note: Take with food, Refills: None."| 0 | 13 | 0 |
    |R10|  9| "(Discount Medication) < Michael L. Panera, PA-C 7/13/2010 17:40>"| 0 | 17 | 0 |

And here’s another one:

Feature: Extract meaningful data from Discharge Message

  Scenario: Extract headings
    Given a discharge message
    When the message is parsed
    Then I should see meaningful information, structured as headings and paragraphs
    And I can get formatted values for HTML display

  Scenario: Extract headings from second message
    Given a second discharge message
    When the message is parsed
    Then I can get formatted values for HTML display

But wait! There is more!

At the “Unit Test” level, RSpec’s can be made rather “english friendly” yet still be all about the underlying API as this diagram and snippet show:

RSpec for an API

The Font Collector RSpec tests

describe PdfParser do
  describe PdfParser::FontCollector do

    before(:all) do
      PdfParser::FontCollector.clear_all()
      ...
    end

    context "initialize" do
      it "should reject missing font reference" do
        ...
      end
      it "should reject missing points" do
        ...
      end
      it "should reject missing value" do
        ...
      end
      it "should accept valid inputs" do
        ...
      end

      it "should start off with simple stats" do
        ...
      end

      it "should recognize reference+size is unique" do
        ...
      end
    end

    context "clear" do
      it "should clear the font list" do
        ...
      end
    end

 

Pair Programming Comments

Yves Hanoulle had some questions from class participants on his post about Pair Programming. I am not a very experienced pair programmer, but that didn’t stop me from providing some answers 🙂

* Can you PP over skype?

Yes — or other similar screensharing technologies (iChat) that allow you to switch control. @jbrains and I did pairing for a code retreat a couple of months ago. You can see the code and a video link here on github.

* Do we need somebody to ‘supervise’ the way you do PP ? (Esp in the beginning?)

Might not be a bad way to more quickly learn from someone else’s tips and techniques. But even better, just switch up the pairing so that you gain insights/ideas from other people with more experience.

* What if there’s no option possibility to use some OS/Computer

With no computer to program on, you would have to stretch the limits of the definition of pair programming I suppose. Pair Yapping About Programming — P-YAP?

* What to do if there is a conflict between the two, a discussion??

Try and sketch out the competing ideas in code. Unless the argument is about which beer is best… then you need to take it to the bar and do a double-blind taste test.

* It can’t work with any type of character, how do you manage?

Pairing should be optional.

* What if one uses AZERTY and another one QWERTY keyboard?

You could solve that with technology.

* Doesn’t it make it harder to plan a project or resources people (Yves re-framed resources to people)

I would suspect the team that pairs is not that much different than a team that doesn’t pair. Both teams are probably lousy at estimating <g>. Instead of stressing at how PP affects your team, just make an initial guess at your estimates and alter the guess after each iteration.

* Can someone do PP with 1 person and PP for something else with another person?

I don’t understand the question. If you mean simultaneously, no. If you mean one time someone does Pair Programming with person X, and later does Pair Painting with Person Y — sure, this is possible. But what does it have to do with anything?

* Can you get into “the zone” when you’re PP-ing?

Probably. You’ll just have to try it and see.

* Does Promiscuous Pairing kill the flow? (30′ interrupts)

Never tried it. Let the team try it and see what happens.

* Why not use PP all of the time?

Let the team try it and see what happens.

* PP = More talking = more annoying for other nearby teams?

It is more annoying for people that are bothered by such environments. I suppose some people prefer quiet.

* Do you plan who is going to do what or how do you choose whose turn it is?

Let the team decide.

* Can you do PP with > 2 people?

You can do whatever you like. Just observe if it is effective or not. I suspect you might find diminishing returns.

* How do you handle the “Don’t care”?

What does this have to do with PP? If you have done everything humanly possible to help someone become a productive member of the team and they are intent on being a-holes; and you have warned them of the consequences of being an a-hole to the team; then fire them so that the team morale is not broken by “one bad apple.”

Cucumber Parser Smarter Than Me

So that I don’t lose another 15 minutes… I had a step like this:

Given a valid outpatient A01 Admit message (without extra ZP1 segment) with Patient Num "12345678"

And in the steps.rb file:

Given /^a valid outpatient A01 Admit message (without extra ZP1 segment) with Patient Num "([^"]*)"$/ do |pn|

Which happily kept not working:

You can implement step definitions for undefined steps with these snippets:
And /^a valid outpatient A(d+) Admit message (without extra ZP(d+) segment) with Patient Num "([^"]*)"$/ do |arg1, arg2, arg3|
  pending # express the regexp above with the code you wish you had
end

UGH!

After getting rid of A01 and ZP1, I still had this “error,” but it led me to realize it was ALL in the parentheses in the steps.rb file! Why? Because parentheses are valid regex elements. Duh. Once I escaped those, all worked like a champ:

Given /^a valid outpatient A01 Admit message (without extra ZP1 segment) with Patient Num "([^"]*)"$/ do |pn|

Mystery solved.

MongoDB Honey Badger

In case you don’t know about the Honey Badger—you have to watch this video. Then you will see why MongoDB is a close cousin to this feared and fearless animal!

Developing a new project where your domain classes/tables are changing rapidly?

MongoDB don’t care!

Tired of running rake db:migrate?

MongoDB don’t care!

Need to add a new “column” to your “table?”

MongoDB don’t care!

Want to query your “table” on “columns” that don’t exist?

MongoDB don’t care!

Need to add a new index on the fly?

MongoDB don’t care!

Welcome the Nastyass MongoDB into your development lair, you won’t give a shit about your database growing and changing!

MongoDB don’t care!

Find out more about Honey Badgers here — though Randall already taught us most of the salient points!

 

The Website Development Anti-Pattern

I don’t want to come down too hard on the author, he looks like a real nice, friendly guy. Writing a post like this took time, effort, thought, and care — which I applaud.

But when I read this post on Website Development — A Practical Methodology, I had flashbacks to Waterfall-esque DoD projects. Seriously? A phased approach with no partial deliveries, no iterations, no “sneaking up on the solution?”

Is this a post from, like, 1986? A phased approach?

  • “Once everything has been appropriately documented, it is time for the Implementation Phase.”
  • “Once implementation is complete, it is time to test.”
  • “When the testing is complete… you are ready to deploy.”

I humbly submit that you might want to intersperse some of those activities into a more iterative approach to reduce the time and cost of getting things wrong and discovering it late in the phases.

I just checked the date of the post again.

It is 2011.

Someone pinch me.

MongoMapper Query Overview

There was a question on the MongoMapper Google Group from a Mongoid user about how MongoMapper handles associations. Brandon was surprised that this query returned an Array:

Product.first.releases.where(something)

Let’s break it down, one bit at a time and clear things up:

# This would be an instance of Product
Product.first # Class.

This simply gets the first element in the Array that is returned by the default “All” query on Product. Of course, without sorting, you probably would not want to do this.

# This would be a return value of an array, assuming Product <>----> * Release
Product.first.releases # Array.

In Brandon’s example, I assume “releases” is a many association. That means, an Array. Unless the association has been tweaked to have default sorting via an Association Extension, getting the “first” one might be adventurous.

# This doesn't change the above... merely adds a restrictive query clause
Product.first.releases.where(something) # Array.

Here we simply get the first element of the releases array, narrowed down by the “something” query.

Capisce?

I am not sure why, but for me it seems more logical to start my clauses with the where, and narrow them down further, or modify them… In MongoMapper, I find querying rigor is much more “loose” than say a SQL SELECT query that requires things in proper order… I would tend to write my queries in more or less this fashion:

ModelClass.where(some criteria).[sort | order | another where clause | fields | limit].[all | first | paginate]

In addition, it is important to note that MongoMapper returns a query and does not actually perform the query until you add something that needs the results. For example: all, first, paginate, sort, etc.

I can picture one of those “man page” or SQL style of fancy ways to show you how you can construct a mongomapper query given all the combinations of options for each “position” in the query…

My (unsolicited) advice is to make the query look as “natural” as possible in terms of how you might read it aloud.

Product.releases.where(:major.gt => 1).sort(:minor.desc).first # Get the latest 1.x release

(And, if the releases where clause query is common, you can create an Association Extension)

Use the Console

You can always just output the queries to the console:

>> Patient.where(:last_name=>/john/i).class
=> Plucky::Query
>> Patient.where(:last_name=>/john/i).all.class
=> Array
>> Patient.where(:last_name=>/john/i).all.count
=> 1
>> Patient.where(:last_name=>/john/i).first.class
=> Patient
>> Patient.sort(:created_at.desc).first.class
=> Patient

Association Extension

And to show an example of an extension (when you use it frequently, for example):

class Encounter
  include MongoMapper::Document
  ...
  # Associations :::::::::::::::::::::::::::::::::::::::::::::::::::::
  many :events, :limit => 30, :order => 'msg_timestamp desc' do
    ...
    def images
      where(:type => [EventConstants::EventType.to_text(EventConstants::EventType::IMAGE)]).order(:created_at.desc).all
    end

    def charts
      where(:type => [EventConstants::EventType.to_text(EventConstants::EventType::ED_SUMMARY)],
            :file_version.in => ["P", "F"]).order(:created_at.desc).all
    end

    def admits
      all(:type => [EventConstants::EventType.to_text(EventConstants::EventType::ADMIT)])
    end
  end
  ...
end

# For a given encounter
enc=Encounter.find('4dadad188951a20727000160')
>> enc.events.images.count
=> 7
>> enc.events.images.class
=> Array
>> enc.events.images.first
=> #

Named Scope

If you will need dynamic querying, you could use a Named Scope as follows:

scope :by_days_old,  lambda { |age| where(:msg_timestamp.gt => age.days.ago) }

This can be used as follows:

Encounter.by_days_old(10)
=> #Fri Apr 15 03:35:53 UTC 2011}>

Looking at Gem Code is Easy with GemEdit

Ever want to pop into the source code of some gem that you are using?

Gem Edit is a nice and easy way to do this (if you aren’t using RubyMine, for example):

[sudo] gem install gemedit

In the terminal, simply type:

[develop*]$  jonsmac2-2:source jon$  gem edit -e mate mongo_mapper

And you will see:

Found gem for 'mongo_mapper' with version >= 0
Opening the following gems with mate:
  mongo_mapper-0.8.6 /Library/Ruby/Gems/1.8/gems/mongo_mapper-0.8.6
Running `mate "/Library/Ruby/Gems/1.8/gems/mongo_mapper-0.8.6"`

and Voila, Mongo Mapper appears in TextMate:

Mongo Mapper

Mongo Mapper Source Code

In an IDE, you can often get to the source code for a single method, but not as “completely” as with gemedit and TextMate, IMHO.

 

Mongo Mapper from IDE

Mongo Mapper from IDE

 

@martinstreicher gave me a good tip when using bundler (which I use on my Rails 3 apps):

export EDITOR=mate;bundle open <gem>

Factory Girl and MongoMapper

You were probably hoping for some Rosey the Riveter poster…

Factory Folder

Factory Folder

Instead, I am going to extend my small MongoMapper example to include Factory Girl. The steps are pretty simple:

  1. Go here to install…
  2. Create your factories
  3. Use the factories in Cucumber/RSpec

Factory Construction

I created a new “factories” folder under the spec folder:

The factories for User and Event are quite simple:

Factory.define :user do |u|
  u.name ('a'..'z').to_a.shuffle[0..7].join.capitalize
end

and

require 'factory_girl'
def dummy_word(len=6)
  ('a'..'z').to_a.shuffle[0..len].join.capitalize
end

def dummy_date
  secs_in_day = 24*60*60
  Time.now + (rand(60)*secs_in_day - 30)
end

Factory.define :event do |e|
  e.title "#{dummy_word} #{dummy_word 3} #{dummy_word 10}"
  e.date  dummy_date
end

Refactor Original Setup

Instead of using this style of test data creation:

@event = Event.create(:title => "Code Retreat Timbuktoo", :user => @fred)

We will use the new factory as follows:

@event = Factory(:event, :title => "Code Retreat Timbuktoo", :user => @fred)

Refactor Cucumber

The given went from this:

Given /^A set of events$/ do
  fred = User.find_or_create_by_name("fred")
  (1..10).each do
    Event.create(:title=>"#{dummy_word} #{dummy_word 3} #{dummy_word 10}",
                 :date => dummy_date,
                 :user => fred)
  end
  harry = User.find_or_create_by_name("harry")
  (1..10).each do
    Event.create(:title=>"#{dummy_word} #{dummy_word 3} #{dummy_word 10}",
                 :date => dummy_date,
                 :user => harry)
  end
  Event.count.should == 20
end

to this – including refactoring out dummy_title, and reducing it to one loop:

Given /^A set of events$/ do
  fred = User.find_or_create_by_name("fred")
  harry = User.find_or_create_by_name("harry")
  (1..10).each do
    evt = Factory(:event, :title => dummy_title,
                          :date  => dummy_date,
                          :user  => fred)
    evt = Factory(:event, :title => dummy_title,
                          :date  => dummy_date,
                          :user  => harry)
  end
  Event.count.should == 20
end

Subtle Details

The beauty of having tests is that I could easily mess around with getting some of the Factory Girl configuration stuff in the right place. Try something, run the test, adjust as needed until all are back to green.

The file features/support/env.rb got some additions so that Cucumber could find the factories:

$LOAD_PATH << File.expand_path('../../../app/model' , __FILE__)
require 'user'
require 'event'
require 'spec/factories/events.rb'
require 'spec/factories/users.rb'
load 'config/mongo_db.rb'

All the tests still pass!

More Complicated Example

For a project I work on, my factories look like this, with auto-creation of random IDs:

def random_months(months)
  day_in_secs = (24*60*60)
  (1+rand(months))*30*day_in_secs
end

# ----------- GROUP -----------
Factory.sequence :group_num do |n|
  "99#{n}#{rand(n)}"
end

Factory.define :group do |g|
    g.group_num {Factory.next(:group_num)}
    g.name "Greatest Group"
end
# ----------- ACCOUNT -----------
Factory.sequence :doctor_num do |n|
  "999992#{n}#{rand(200+n)}"
end

Factory.sequence :login do |n|
  "AB#{rand(n*68)}bx#{rand(200+n)}"
end

Factory.sequence :msid do |n|
  "CQ987Z12#{n}#{rand(n)}"
end

Factory.define :account do |a|
  pw = 'password'
  a.msid { Factory.next(:msid) }
  a.doctor_num { Factory.next(:doctor_num) }
  a.first_name "James"
  a.last_name "Jones"
  a.role 'user'
  a.password pw
  a.password_confirmation pw
  a.email Setting.get("AutoEmail")
  a.login { Factory.next(:login) }
end

# ----------- PATIENT -----------
Factory.sequence :patient_num do |n|
  "#{n}#{rand(300+n)}"
end

Factory.define :patient do |pt|
#  pt.patient_num "10000009"
  pt.patient_num {Factory.next(:patient_num)}
  pt.emr_num "1853286"
  pt.first_name "John"
  pt.last_name "Johnson"
  pt.dob {(Time.now - random_months(36))}
  pt.count_public_encounters 1
  pt.count_public_events 2
end