Saturday, October 3, 2009

Cucumber Driven Development with CouchDB

‹prev | My Chain | next›

Last in the ingredient index feature is ignoring ingredients that are too common to be worth including in the index. The Cucumber scenario describing this:
jaynestown% cucumber features/ingredient_index.feature:17                       
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Ingredient index for recipes

As a user curious about ingredients or recipes
I want to see a list of ingredients
So that I can see a sample of recipes in the cookbook using a particular ingredient

Scenario: Scores of recipes sharing an ingredient # features/ingredient_index.feature:17
Given 120 recipes with "butter" # features/ingredient_index.feature:19
When I visit the ingredients page # features/step_definitions/ingredient_index.rb:22
Then I should not see the "butter" ingredient # features/ingredient_index.feature:21

1 scenario (1 undefined)
3 steps (1 skipped, 2 undefined)
0m1.342s

You can implement step definitions for undefined steps with these snippets:

Given /^120 recipes with "([^\"]*)"$/ do |arg1|
pending
end

Then /^I should not see the "([^\"]*)" ingredient$/ do |arg1|
pending
end
The given-120-recipes step can be defined with a simple 120.times block:
Given /^(\d+) recipes with "([^\"]*)"$/ do |count, ingredient|
date_st = Date.new(2009, 10, 3)

count.to_i.times do |i|
date = date_st - i
permalink = date.to_s + "-recipe"

recipe = {
:title => "Yet another #{ingredient} recipe",
:type => 'Recipe',
:published => true,
:date => date,
:preparations => [{'ingredient' => {'name' => ingredient}}]
}

RestClient.put "#{@@db}/#{permalink}",
recipe.to_json,
:content_type => 'application/json'
end
end
The second step in that scenario, visiting the index of ingredients page, is the same as the step in the previous scenario. In other words, I do not have redefine that step. This leaves me the last step, asserting that this ingredient is not included in the index:
Then /^I should not see the "([^\"]*)" ingredient$/ do |ingredient|
response.should_not have_selector(".ingredient",
:content => ingredient)
end
That step fails, with lots, and lots of <a> tags:
jaynestown% cucumber features/ingredient_index.feature:17
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Ingredient index for recipes

As a user curious about ingredients or recipes
I want to see a list of ingredients
So that I can see a sample of recipes in the cookbook using a particular ingredient

Scenario: Scores of recipes sharing an ingredient # features/ingredient_index.feature:17
Given 120 recipes with "butter" # features/step_definitions/ingredient_index.rb:22
When I visit the ingredients page # features/step_definitions/ingredient_index.rb:43
Then I should not see the "butter" ingredient # features/step_definitions/ingredient_index.rb:64
expected following output to omit a <.ingredient>butter</.ingredient>:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title>EEE Cooks: Ingredient Index</title>
<link href="/stylesheets/style.css" rel="stylesheet" type="text/css">
</head>
<html><body>
<div id="header">
<div id="eee-header-logo">
<a href="/">
<img alt="Home" src="/images/eee_corner.png"></a>
</div>
</div>
<h1>
Ingredient Index
</h1>
<table><tr>
<td class="col1">
<p>
<span class="ingredient">
butter
</span>
<span class="recipes">
<a href="/recipes/2009-06-26-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-06-27-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-06-28-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-06-29-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-06-30-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-01-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-02-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-03-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-04-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-05-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-26-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-27-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-28-recipe">Yet another butter recipe</a>, <a href="/recipes/2009-07-29-recipe">Yet another butter recipe</a>,
...
</body></html>
</html>
(Spec::Expectations::ExpectationNotMetError)
features/ingredient_index.feature:21:in `Then I should not see the "butter" ingredient'

Failing Scenarios:
cucumber features/ingredient_index.feature:17 # Scenario: Scores of recipes sharing an ingredient

1 scenario (1 failed)
3 steps (1 failed, 2 passed)
0m1.734s
To get that passing I need to drop down into the CouchDB view. Specifically, when "reducing", I need to check the arrays of recipes being associated with the ingredient. If there are more than 100 recipes using said ingredient, I need to return an empty list:
function(keys, values, rereduce) {
if (rereduce) {
var ret = [];
for (var i=0; i<values.length; i++) {
ret = ret.concat(values[i]);
}
return ret.length > 100 ? [] : ret;
}
else {
return values;
}
}
It was noted the other day that my "reduce" here is not really reducing. In this case it works, but at some point I would hit a performance wall (at least according to the documentation). If I were looking to to avoid that wall, I might do so here—possibly even getting the reduce down to the O(logn) suggested by the documentation. For now, I do not need it—performance of the view is perfectly fine with ~500 recipes and ~5000 ingredients—so I will stick with what I have.

Before the scenario will pass, I have to update the Haml template to ignore ingredients with no recipes:
...
- @ingredients.each_slice((@ingredients.size.to_f/2).round) do |batch|
- col = col + 1
%td{:class => "col#{col}"}
- batch.reject{|i| i['value'].empty?}.each do |ingredient|
%p
...
All of my unit (RSpec) tests still pass. And now the scenario does as well:
jaynestown% cucumber features/ingredient_index.feature:17
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Ingredient index for recipes

As a user curious about ingredients or recipes
I want to see a list of ingredients
So that I can see a sample of recipes in the cookbook using a particular ingredient

Scenario: Scores of recipes sharing an ingredient # features/ingredient_index.feature:17
Given 120 recipes with "butter" # features/step_definitions/ingredient_index.rb:22
When I visit the ingredients page # features/step_definitions/ingredient_index.rb:43
Then I should not see the "butter" ingredient # features/step_definitions/ingredient_index.rb:64

1 scenario (1 passed)
3 steps (3 passed)
0m1.699s
Tomorrow I will deploy my latest features before deciding where to move onto next.

No comments:

Post a Comment