Capture Screenshots
For a while my tool of choice for creating screen captures has been Cropper, a utility created by Brian Scott, and hosted on CodePlex
There is also a series of plugins that allow you to for example post captures to Flickr.
One thing I have always wanted was to send image captures to my TwitPic account so I can easily link them to my tweets.

Building A Cropper Plugin For TwitPic
So I finally decided to build my own plugin. For that I first studied the way the other plugins were written, especially the one to send to Flickr.
Approach
A Cropper plugin consists of the following parts:
The plugin needs to implement 2 interfaces:
- IConfigurablePlugin – this is how Cropper configures and retrieves our custom settings
- IPersistableImageFormat – this is how Cropper hands over the captured image so we can act on it.
Let’s see what the code looks like. Note: I removed all comments for brevity.
IConfigurablePlugin Implementation
This part is straightforward since it is exposing the settings and allows loading and saving of the settings to or from the dialog.
public BaseConfigurationForm ConfigurationForm
{
get
{
if (_configurationForm == null)
{
_configurationForm = new Options();
_configurationForm.OptionsSaved += Handle_configurationFormOptionsSaved;
_configurationForm.TwitPicID = PluginSettings.TwitPicID;
_configurationForm.TwitPicPassword = PluginSettings.TwitPicPassword;
_configurationForm.ShowDebugInfo = PluginSettings.ShowDebugInfo;
}
return _configurationForm;
}
}
public bool HostInOptions
{
get { return hostInOptions; }
}
public object Settings
{
get { return PluginSettings; }
set { PluginSettings = value as TwitPicSettings; }
}
public TwitPicSettings PluginSettings
{
get
{
if (_settings == null)
_settings = new TwitPicSettings();
return _settings;
}
set { _settings = value; }
}
private void Handle_configurationFormOptionsSaved(object sender, EventArgs e)
{
PluginSettings.TwitPicID = _configurationForm.TwitPicID;
PluginSettings.TwitPicPassword = _configurationForm.TwitPicPassword;
PluginSettings.ShowDebugInfo = _configurationForm.ShowDebugInfo;
}
IPersistableImageFormat Implementation
There are 2 types of responsibilities
- Providing user interface elements (description, menu, etc) to the main Cropper UI:
public string Description
{
get { return "Send screenshot to your TwitPic account."; }
}
public string Extension
{
get { return "Png"; }
}
public override string ToString()
{
return "Send to your TwitPic account";
}
public MenuItem Menu
{
get
{
MenuItem item = new MenuItem();
item.RadioCheck = true;
item.Text = "Send to TwitPic";
item.Click += new EventHandler(this.MenuItemClick);
return item;
}
}
private void MenuItemClick(object sender, EventArgs e)
{
ImageFormatEventArgs args = new ImageFormatEventArgs();
args.ClickedMenuItem = (MenuItem)sender;
args.ImageOutputFormat = this;
this.OnImageFormatClick(sender, args);
}
- Implementing callbacks to handle the image capture process:
public IPersistableImageFormat Format
{
get { return this; }
}
public void Connect(IPersistableOutput persistableOutput)
{
if (persistableOutput == null)
{
throw new ArgumentNullException("persistableOutput");
}
_output = persistableOutput;
_output.ImageCaptured += new ImageCapturedEventHandler(this.persistableOutput_ImageCaptured);
_output.ImageCapturing += new ImageCapturingEventHandler(this.persistableOutput_ImageCapturing);
}
public void Disconnect()
{
_output.ImageCaptured -= new ImageCapturedEventHandler(this.persistableOutput_ImageCaptured);
_output.ImageCapturing -= new ImageCapturingEventHandler(this.persistableOutput_ImageCapturing);
}
public event ImageFormatClickEventHandler ImageFormatClick;
private void OnImageFormatClick(object sender, ImageFormatEventArgs e)
{
if (ImageFormatClick != null)
{
ImageFormatClick(sender, e);
}
}
void persistableOutput_ImageCapturing(object sender, ImageCapturingEventArgs e)
{
this.LogDebugInfo("Capturing an image via Cropper-TwitPic");
}
void persistableOutput_ImageCaptured(object sender, ImageCapturedEventArgs e)
{
// ...
}
The persistableOutput_ImageCaptured method is where all the action will take place such as:
- Writing the image to a memory stream and encoding it:
try
{
ImagePairNames imgNames = e.ImageNames;
this.FileName = e.ImageNames.FullSize;
this.LogDebugInfo(string.Format("Converting the captured image to .png - file name: {0}", this.FileName));
// Convert the captured image as .png and string encode it so we can upload it
MemoryStream imageStream = new MemoryStream();
e.FullSizeImage.Save(imageStream, System.Drawing.Imaging.ImageFormat.Png);
imageStream.Position = 0;
byte[] data = imageStream.ToArray();
this.ImageFileContents = Encoding.GetEncoding("iso-8859-1").GetString(data);
this.PostToTwitPic();
MessageBox.Show(string.Format("Your image is available on TwitPic atn{0}",this.PostedMediaUrl),
"Cropper.SendToTwitPic");
}
catch (Exception ex)
{
// ...
}
- Creating a web request so we can post the image to TwitPic using its upload API – see more details on TwiPic
public void PostToTwitPic()
{
// Reset TwitPic output
this.PostedMediaUrl = string.Empty;
this.TwitPicErrorMsg = string.Empty;
// Create the TwitPic request
string formDataBoundary = DateTime.Now.Ticks.ToString("x");
string formDataEncoding = "iso-8859-1";
this.TwitPicRequest = this.CreateRequestContents(formDataBoundary);
byte[] bytes = Encoding.GetEncoding(formDataEncoding).GetBytes(this.TwitPicRequest);
// Initialize the request for TwitPic
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.TwitPicPostingUrl);
request.Method = "POST";
request.CookieContainer = null;
request.Proxy.Credentials = CredentialCache.DefaultCredentials;
request.PreAuthenticate = true;
request.AllowWriteStreamBuffering = true;
request.ContentType = string.Format("multipart/form-data; boundary={0}", formDataBoundary);
request.ContentLength = bytes.Length;
// Send to TwitPic
using (Stream requestStream = request.GetRequestStream())
{
// Post the data
this.LogDebugInfo(string.Format("Uploading the captured image to: {0}", this.TwitPicPostingUrl));
requestStream.Write(bytes, 0, bytes.Length);
// Get the response
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
this.LogDebugInfo(string.Format("Getting the upload status from TwitPic"));
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string result = reader.ReadToEnd();
XmlDocument docResult = new XmlDocument();
docResult.LoadXml(result);
XmlNode nodeResult = docResult.SelectSingleNode("/rsp");
if (nodeResult == null)
EventLog.WriteEntry(CROPPER_TWITPIC_EVENT_SOURCE_NAME,
"Did not receive a valid status response from TwitPic",
EventLogEntryType.Error,
2);
else
{
string resultCode = nodeResult.Attributes["stat"].Value;
if ("ok" == resultCode)
{
XmlNode nodeMediaUrl = docResult.SelectSingleNode("//mediaurl");
this.PostedMediaUrl = (nodeMediaUrl != null) ? nodeMediaUrl.InnerText : "unknown media url";
this.LogDebugInfo(string.Format("TwitPic media url: {0}", this.PostedMediaUrl));
}
else
{
XmlNode nodeError = docResult.SelectSingleNode("//err");
this.TwitPicErrorMsg = (nodeError != null)
? string.Format("Code:{0} Msg: {1}",
nodeError.Attributes["code"].Value,
nodeError.Attributes["msg"].Value)
: "unknown error message";
EventLog.WriteEntry(CROPPER_TWITPIC_EVENT_SOURCE_NAME,
string.Format("TwitPic error: {0}", this.TwitPicErrorMsg),
EventLogEntryType.Error,
3);
}
}
}
}
}
}
public string CreateRequestContents(string formDataBoundary)
{
string formDataHeader = string.Format("--{0}", formDataBoundary);
string formDataFooter = string.Format("--{0}--", formDataBoundary);
StringBuilder contents = new StringBuilder();
contents.AppendLine(formDataHeader);
// Add "media" field
contents.AppendLine(String.Format("Content-Disposition: file; name="media"; filename="{0}"", this.FileName));
contents.AppendLine("Content-Type: image/png");
contents.AppendLine();
contents.AppendLine(this.ImageFileContents);
// Add username field
contents.AppendLine(formDataHeader);
contents.AppendLine("Content-Disposition: form-data; name="username"");
contents.AppendLine();
contents.AppendLine(this.PluginSettings.TwitPicID);
// Add password field
contents.AppendLine(formDataHeader);
contents.AppendLine("Content-Disposition: form-data; name="password"");
contents.AppendLine();
contents.AppendLine(this.PluginSettings.TwitPicPassword);
contents.AppendLine(formDataFooter);
string stringContents = contents.ToString();
this.LogDebugInfo("Formatted the TwitPic upload request",
Encoding.GetEncoding("iso-8859-1").GetBytes(stringContents));
return stringContents;
}
Source Download
Until I can get the code added to the Cropper Plugins project on CodePlex you can download the full source on my blog. See the README.txt for instructions on how to set the code up.
Demo
Once the plugin code is built and deployed, you’re ready to start.
- when you start Cropper, you can right-click on the Options menu to:

- Then you switch Cropper’s output to the SendToTwitPic plugin:

- Now you can capture an image. If the post is successful, a message box will confirm that and indicate the destination url:
So enjoy this tool!
References and Resources
Sites:
- Cropper
- Cropper Plugins
- Brian Scott’s blog post
- TwitPic API
October 22nd, 2009
Posted by
techarch |
C#, Tools |
no comments
Homage to _why
|
Six or seven years ago, when I read about Ruby, I quickly came across Why’s Poignant Guide To Ruby.
I immediately loved _why’s artistic style, his passion for teaching, as well as the depth of some of the concepts he was trying to present.
On August 12th, 2009, _why went “off-line”,stirring a lot of emotions from the Ruby community.
_why was a prolific developer, artist, and teacher, and inspired many people through his many contributions.
_why’s code treasure trove included Camping, a micro-framework aimed at providing a lean base for your web apps.
|
|
Camping, a Micro-Framework
A micro-framework consists of:
- The bare essentials to host a web app – Camping leverages Rack for the base request and response processing but provides its own basic session mechanim
- A core set of base classes to provide some structure to your apps – Camping promotes the Model-View-Controller pattern
- Constructs to create your models – Camping leverages the modelling conventions introduced by ActiveRecord
- Integration with a persistence engine – Camping uses ActiveRecord
- A templating engine for the view – Camping supports layouts
- A simplified url routing mechanism for controllers
- An easy way to access session and form data inside controllers
Why a micro-framework as opposed to a regular framework? Well consider the following benefits:
- There are less moving parts in the framework making it easier to understand
- There are fewer conventions to learn about (e.g. compared to Rails)
- The minimalistic implementation encourages you to adopt the same approach for your app
- You will be able to focus more on the core concepts of your application
- The resulting total footprint is much smaller and tighter making it possible to run under more stringent infrastructure constraints
A micro-framework like Camping provides just enough structure and core functionality to let you incrementally design and build your application.
Once your application reaches a higher level of sophistication and technical feature requirements, you can migrate the core app to a more feature-rich framework like Rails for example.
Also there is less “friction” when you want to prototype an application to figure out the core concepts. I have been using Camping to try out some ideas,
then realized that the implementation was just fine, so I just went ahead and deployed it as-is. The lower friction comes into play if you are building some of these apps in your own time
where you want to maximize your productive time investment and make measurable progress in less time.
Nathaniel Talbott expanded on that point in his “Why Camping Matters” presentation.
To get started, download IronRuby and get Camping:
gem install camping
Camping Examples
Below are 3 progressively more functional examples illustrating the Model-View-Controller approach in Camping.
A) Controller Sample
Here is a very small example similar to a Pico-Sinatra application with a module including just a controller with one single route.
- The Index class represents a micro-controller for the / route.
- A get method / action is defined for the HTTP GET method on that route.
require 'camping'
Camping.goes :Pico
module Pico
def Pico.create
end
end
module Pico::Controllers
class Index < R '/'
def get
"Welcome to the Pico Camping app - #{Time.now}"
end
end
end
B) Controller And View Sample
Here is a variation of the previous example with a corresponding view module providing a layout serving as a template very much like in Rails.
- An index method is defined in the view module.
- The view uses Markaby to express the content to be rendered.
- The get method on the Index controller calls the render :index method to select the view to render.
require 'camping'
Camping.goes :Nano
module Nano
def Nano.create
end
end
module Nano::Controllers
class Index < R '/'
def get
render :index
end
end
end
module Nano::Views
def layout
html do
body do
self << yield
hr
div.footer! { "#{Time.now}" }
end
end
end
def index
h3 "Welcome to the Nano Camping app!"
end
end
C) Model View Controller Sample
Here is a variation of the previous example with the following additions:
- A new module called Models for our ActiveRecord-based models (line 18)
- A new model class: Announcement mapped to a micro_announcements table (line 19)
- A method to setup the database schema and a demo record (lines 06 and 22)
- Retrieval of the announcements in the get method of the Index controller (line 41)
- Rendering of the announcements in the index method of the view (line 60)
require 'camping'
Camping.goes :Micro
module Micro
def Micro.create
Camping::Models::Base.establish_connection :adapter => 'mssql',
:host => 'localhost',
:database => 'camping',
:username => '*USERNAME*',
:password => '*PASSWORD*'
Micro::Models.create_schema :assume => (Micro::Models::Announcement.table_exists? ? 1.0 : 0.0)
end
end
module Micro::Models
class Announcement < Base
end
class SetupTheDB < V 1.0
def self.up
create_table :micro_announcements, :force => true do |t|
t.column :id, :integer, :null => false
t.column :title, :string, :limit => 255
end
Announcement.create :title => 'Welcome to the Micro Camping app!'
end
def self.down
drop_table :micro_announcements
end
end
end
module Micro::Controllers
class Index < R '/'
def get
@announcements = Announcement.find(:all)
render :index
end
end
end
module Micro::Views
def layout
html do
body do
self << yield
hr
div.footer! { "#{Time.now}" }
end
end
end
def index
@announcements { | a | h3 a }
end
end
These three examples are minimalistic. You have access to the full power of ActiveRecord to express relationships between models, add validation and constraints.
At the controller level Camping also provide a really easy way to access both form data, querystring parameters, and session parameters through hashes (HashWithIndifferentAccess).
- The input variable, available at the controller level, holds on to all form data and querystring parameters.
- The @state variable, available at the view and controller levels, holds all session data
So don't let these examples fool you, the micro-framework is not as "micro" as you might think! For a richer example, check-out the Camping Blog example.
To test Camping on IronRuby, I started with the basic example (pico) and then progressively worked up the level of functionality.
IronRuby Camp Site
I have been actively following the development of IronRuby since the days Microsoft hired John Lam to work on IronRuby.
(See my other post(s) on the topic).
I got pretty excited when I ready Jimmy Schementi's post on getting Rack to run on IIS.
This opened the ability to run Camping since it has very lightweight needs and works on top of Rack.
So as a prerequisite I started to install IronRack and its example.
Unfortunately I could not get the example to work - maybe due to some of the permissions around the LogUtils component.
So I built a slightly altered version / configuration and switched logging to Log4Net.
And bingo! I was able to run the basic IronRack example.
I then created a custom IronCamping web app, modeled very closely after the IronRack model with a few additions:
- Log4Net logging
- Preload of the Camping gem - with the ability to configure the gem version to use
- Customized the config.ru (Rack Up) to start a very tiny Camping app:
require 'rubygems'
require 'rack'
require 'camping'
require 'demo' # where my Camping demo code resides
# Initialize the Demo module using the create method
Demo.create
# Instantiate a Rack-Camping adapter for our Demo module
demo_module_adapter = Rack::Adapter::Camping.new(Demo)
# Run the Camping Demo app
run demo_module_adapter
- I also configured my IIS virtual directory to explicitly map the .* to the ASP.NET ISAPI module
Need A Few More Poles For The Tent!
Ok, so I could not get the famous raising of the tent to work using the traditional statement:
Camping.goes :Pico
So I started to really read the (unabridged!) Camping framework code
to understand the core as well as to isolate code fragments
and compare their execution in IronRuby and in standard Ruby (MRI).
Once I got the minimalistic app to come up, I moved on to _why's Camping Blog application.
I ran into additional sets of issues which required more digging, analyzing and testing.
Here is a summary of my findings and work-arounds:
- The "goes" statement performs some text substitutions in the basic camping module, loads and binds them to the TOPLEVEL_BINDING.
Apparently at the time of this writing, that global constant was not set by the base IronRack code.
A suggestion was made on the mailing list to update the RubyEngine class of IronRack.
I will update the post once this is available.
The work-around was to explicitly require the 'hacks.rb' module from IronRuby like so:
require 'hacks' # for TOPLEVEL_BINDING
require 'camping'
Camping.goes :Pico
- [*'a'..'b'] does not work in IR the same way it does in MRI.
As a result, on line 27 of camping/session.rb the session id cannot be generated correctly leading to an error.
The solution was to explicitly do a to_a conversion and rewrite line 27 as:
# OLD: RAND_CHARS = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
# NEW: User standard interval notation plus an explicit to_a to return an array
RAND_CHARS = ('A'..'Z').to_a + ('0'..'9').to_a + ('a'..'z').to_a
- Not sure exactly why but when the session is loaded, the @state is not restored as a HashWithIndifferentAccess
The solution was to explicitly cast the state as a HashWithIndifferentAccess to explicitly re-cast the hash as a HashWithIndifferentAccess after line 111 of the
service(*a) method of the Session module in session.rb, right after the line initializing @state:
@state = (session[app] ||= Camping::H[])
@state = Camping::H[@state] # Explicit re-cast as a HashWithIndifferentAccess
- On line 399 (of the camping-unabridged.rb),
the code creating the value to be written to the document cookie returns an array instead of a string.
This causes the cookie to not be populated correctly which prevents the session from working.
The solution was to rewrite the line:
# OLD: @headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
# NEW: evaluates and renders the expression as a string using "#{}"
@headers["Set-Cookie"] = "#{@cookies.map{|k,v|"#{k}=#{C.escape(v)}; path=#{self/'/'}" if v != @k[k] } - [nil]}"
- Since I would like the app to run on MS SQL Server, I added the ActiveRecord MS SQL adapter
to my activerecord-2.3.3libactive_recordconnection_adapters folder.
I also made a change to the includes_id_field
method to ensure that the SET IDENTITY_INSERT statement is only generated for the id column.
def includes_id_field(sql)
sql =~ /[id]|[w*_id]/i
end
Then I added the following ActiveRecord configuration statement to my application module:
Camping::Models::Base.establish_connection
:adapter => 'mssql',
:host => 'localhost',
:database => 'camping',
:username => '*USERNAME*',
:password => '*PASSWORD*'
- Finally, the harder problem I had to track down was that the setup of the rack.input context for Rack
(line 159) in the IIS class for IronRack.
env["rack.input"] = RubyEngine.Execute("StringIO.new(__request.body || '')", handle_scope);
The setup would work for a GET but not a POST with form data. This would trigger a [InvalidOperationException: can't convert System::String into String] in the DLR.
The solution was to rewrite the line as follows:
if (request.Body != null)
{
// Explicitly pass the request body as a string
string myInput = request.Body.ToString();
handle_scope.SetVariable("__myinput", myInput);
env["rack.input"] = RubyEngine.Execute("StringIO.new(__myinput.to_s)", handle_scope);
}
else
{
// Original code
env["rack.input"] = RubyEngine.Execute("StringIO.new(__request.body || '')", handle_scope);
}
Raising The Camp Flag
So in summary, once I had:
- tweaked the original Camping code
- tweaked the IronRuby.Rack code
- added and tweaked the ActiveRecord SQL server adapter
- created a test database in SQL Server
- created a login account for the app
- granted the login account access to the test database
- configured the ActiveRecord connection
- added an IIS mapping for the .* extension
I was then able to get the Camping Blog app to run! Yippee! |
 |
As a footnote, I noticed that the new branch of the Camping code maintained by Judofyr includes more recent changes
that may not require the Camping tweaks I made. I will update this post once I get a chance to test that thoroughly.
Running In The IronRuby Console
If you want the ability to run Camping from the command line IronRuby console, you also need to:
- Copy C:IronRubybinirake.bat to C:IronRubybinircamping.bat
- Copy C:rubybincamping to C:IronRubybinircamping
- Create a file named .campingrc in your %USERPROFILE% directory
- Edit .campingrc and add the following content:
database:
adapter: mssql
host: localhost
database: camping
username: *USERNAME*
password: *PASSWORD*
Then you can start your Camping app as follows:
ircamping micro.rb
You can then open up a browser and go to http://localhost:3301/
So What?
I really like the power of Rails for building elaborate apps. But when I want to prototype a concept really quickly,
I enjoy using Camping because of its minimalistic approach combined with the structure of MVC.
And having most of the code in a single file (albeit with some structure) makes it very convenient to make quick changes and test immediately
(thanks to the Camping reloader)
Another nice thing is that Camping has such a small footprint that you can use it on shared web hosts with little processing overhead.
This makes it thin enough to serve REST services using the Restop framework.
Thanks to all the great work of the IronRuby team, I can now enjoy Camping on .NET.
I might even start mixing in .NET classes!!!
Hopefully this will give you an idea of what is possible with Camping and IronRuby.

References and Resources
Software:
- _why's code contributions - new mirror on GitHub
- judofyr's fork of Camping: active branch of Camping with enhancements
- IronRuby v0.9: Source on GitHub
- ActiveRecord
- ActiveRecord MS SQL Server Adapter
- Log4Net: Apache logging for .NET
- Markaby Source: Repository on GitHub
- Camping Blog Application: Full Camping Example
Sites:
- Why's Poignant Guide To Ruby: An cartoon-centric approach to learning Ruby
- IronRuby: Main site
- IronRuby-Core: Mailing list
- Camping - Documentation
- Model-View-Controller Pattern
- IronRuby Wiki: GitHub wiki
- Rack
- Deploying Rack-based Ruby Application on IIS: Jimmy Schementi
- Markaby Doc: Documentation
- Magnus Holm a.k.a. judofyr: Camping project "Coordinator"
- John Lam: Blog
- RestStop: REST extension for Camping
- Camping-PicningSet of extensions
Videos:
- Why Camping Matters: Presentation by Nathaniel Talbott
- RailsCast Camping Episode 1 of 2
- RailsCast Camping Episode 2 of 2
My Other Related Posts:
- From Ruby Silverlight to Ruby Web Service (with a touch of spice)
September 11th, 2009
Posted by
techarch |
Ruby |
no comments
DDT / Development-Driven Tests
For the last several years I have been generalizing the use of automated unit tests on my team (at work) as part of the development process for our C#-based enterprise services and business applications. Circa 2005, I was using NUnit and NAnt and Cruise Control to implement continuous integrations builds. Then as we implemented Team Foundation Server, we switched to MS Test. We created unit tests focused on specific classes as well as integration tests focused on interactions involving multiple classes. I have usually tried to strike the balance on not creating test for the sake of test but mostly to test complex logic and ensure key enterprise services would work reliably. Automated tests have been part of our coding process but not upfront as the Test-Driven Development practitioners would propose. I have been resistant to TDD for a couple reasons:
- Early on, developers need to focus on understanding the target functionality
- Our lifecycle includes some up-front time-boxed design focused on components and their interactions
- It is easy to fall deep in the rabit hole of creating too many detailed tests with all their associated fixture
- If the depth and breadth of tests are inconsistent the cost of the development phase can quickly go up
Our practice is really DDT i.e. Development-Driven Tests, as opposed to TDD (Test-Driven Development).
The Missing Ingredient In The Recipe
So an ingredient is missing: how to get developers to absorb the new functionality in palatable chunks while yet conjuring and capture the interesting scenarios that might influence the structure of the code.
Focus on Behavior And Acceptance Is Key
My outside-work interests in Ruby, Web Frameworks, and Agile approaches recently brought me to dig into the different types of testing frameworks associated with Rails projects such as:
- RSpec – the father of Ruby-based BDD frameworks
- Shoulda – alternative to RSpec trying to make the syntax a bit easier to read
- Cucumber – set the tone by focusing the author on the “Given-When-Then” structure
There are probably other frameworks but so far I have only looked into these three.
These frameworks aim at raising the level of abstraction above the traditional “x-unit”-style unit tests which are typically focused on defining test methods containing specific code-level assertions such as:
- TestUnit Example:
class MyTest < Test::Unit::TestCase
def do_some_work
# ...
assert_equal("OK", result)
end
end
- Rspec Example:
require 'rubygems'
require 'spec'
describe "Skills Tracking" do
it "should track skills for a given user" do
end
end
Running the spec produces:

These frameworks are very convenient to build acceptance either ahead or during development but they still set the expectation that you are writing at least some minimal code. You will also notice that even though help create what is called "syntactic sugar" to help with readability, authoring a spec requires that you at least know quite a bit the syntax. What if the abstraction level could be raised one more level and guide you through the BDD process?
Adding Cucumber To The Recipe
Well, search no more! Cucumber help focus the author on the key tenet of BDD:
- Define each business Feature - using the syntax:
Feature: Skills Tracking
In order to manage my skills
As a person continuously developing skills
I want to be track my skills
- Define multiple Scenarios where a feature can be exercised - using the syntax:
Scenario: Track New Skill
Given a new skill
When I define a new skill
Then the new skill will be tracked
Scenario: Update Skill Proficiency
Given an existing skill
When I have increased my proficiency level with that skill
Then the skill proficiency level will be updated
Notice that I have not used any code and very minimal syntax. The approach also leads you to define scenarios as you specify your feature. I don't need to think about code quite yet.
Tasting The Cucumber
So let's see what minimal infrastructure is needed. I am assuming you have Ruby. If not, check my post titled Getting Started With Ruby And Rails. To keep things to a minimum, I am going to show you how to create your features with only Ruby (as opposed to depending on the whole Rails stack).
- Install the cucumber gem
gem install cucumber
- Create a project folder (e.g. "Cuke")
- Create a file in Cuke, name it rakefile and edit it to enter the following content:
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'cucumber/rake/task'
Cucumber::Rake::Task.new do |t|
t.cucumber_opts = "--format pretty"
end
- Create a file in Cuke, name it cucumber.yml and edit it to enter the following content:
autotest: -r features --format pretty --color
- Create a lib folder below Cuke
- Create a tasks folder below Cukelib
- Create a file in Cukelibtasks, name it cucumber.rake and edit it to enter the following content:
begin
require 'cucumber/rake/task'
Cucumber::Rake::Task.new(:features) do |t|
t.fork = true
t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'pretty')]
end
task :features => 'db:test:prepare'
rescue LoadError
desc 'Cucumber rake task not available'
task :features do
abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
end
end
- Create a features folder below Cuke
- Create a file in Cuke, name it testcuke.feature and edit it to enter the following content:
Feature: Skills Tracking
In order to manage my skills
As a person continuously developing skills
I want to be track my skills
- Open a command window on our Cuke folder and let's run Cucumber
cucumber features/testcuke.feature
The result is:

- Let's add a scenario to the feature:
Scenario: Track New Skill
Given a new skill
When I define a new skill
Then the new skill will be tracked
- Let' re-run Cucumber
cucumber features/testcuke.feature
The result is:

Cucumber identified the steps of the scenario and colored them amber to indicate that they are not implemented yet. So let's tackle one step.
- Create a step_definitions folder below Cuke
- Create a file in Cukestep_definitions, name it skill_step.rb and edit it to enter the following content based on the template suggested by Cucumber:
Given /^a new skill$/ do
pending
end
- Let' re-run Cucumber
cucumber features/testcuke.feature
The result is:

To make the test pass we would need to fully implement the step.
Incremental BDD
So far, everytime we have refined the feature, its scenarios and steps, we have re-run cucumber. What if Cucumber could run as soon as a change has been made (i.e. a file has been updated).
Enter autotest, a "file-watcher" which will trigger a new run of Cucumber.
- Install the ZenTest gem
gem install cucumber
- Set the following environment variables in your command line session
SET AUTOFEATURE=true
SET HOME=.
- Run autotest
autotest
Now when you make a change to any file, you will see autotest run Cucumber.
So What?
Looking back at TDD (or DDT!) practices, it is clear that the focus of testing is more on code artifacts than on functional features.
Behavior-Driven Development can help a development team absorb and distill business requirement into acceptance test scenarios from which automated tests can be derived.
Even if you are not using Ruby or Rails as your development platform, Cucumber and Ruby can still play a role during the design phase. And you can, over time, introduce the use of Ruby to create integration test with non-Ruby components either through web services, databases or web clients.
If your core platform is .NET and you are considering building rich clients with Silverlight, you should also consider IronRuby. In that scenario, Cucumber can directly be leveraged with IronRuby.
Over time, I am envisioning the following for my team:
- We start leveraging Cucumber to map some of the key functional requirements into features (but from a developer's standpoint)
- When ready we derive high-level integration tests using IronRuby
- We create C#-based unit tests
- We then combine IronRuby integration tests with lower-level unit test-level harnesses
I am clearly planning to further develop my knowledge and experience with BDD through Cucumber.
References and Resources
Sites:
- BDD - Behavior-Driven Development
- Dan North first article on BDD
- RSpec - the father of Ruby-based BDD frameworks
- Shoulda - alternative to RSpec trying to make the syntax a bit easier to read
- Cucumber - set the tone by focusing the author on the "Given-When-Then" structure
- Outside-In Development with Cucumber: Ben Mabey
Books:
- The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends
Videos:
- Outside-In Development with Cucumber: Video of Ben Mabey's presentation
- Beyond Test Driven Development: Behaviour Driven Development - Dave Astels
July 25th, 2009
Posted by
techarch |
BDD, Ruby |
no comments
AIR-Sh-m-AIR
Last year with several colleagues we were discussing the various new rich web technologies such as SilverLight, Javafx, and Adobe AIR. At the time I was not too interested in Adobe AIR because my work technical platform is primarily Microsoft-centric. But then we were primarily discussing features like rich presentation and the ability to call web services, all within the browser. Then a few months later as user adoption for Twitter was sharply accelerating, some interesting Twitter client started to appear such as TweetDeck.

The idea being that, simularly to an instant messaging client (like Trillian), users want to stay connected at all times to Twitter and get frequent updates and the ability to quickly toggle over and tweet-back.
So what’s special about the TweetDeck client? Well, it has a very rich and fluid visual interface and communicates with web services exposed by Twitter. It is built on top of the Adobe AIR platform, which encompasses Flex as well as as additional APIs allowing interaction with the desktop (e.g. via drag and drop – something that web clients cannot do due to their sandbox). It even allows for the application to run completely on the desktop.
Pump AIR Into Rich Web-Aware Apps
Since I was more interested in the Silverlight (see other posts), I had dismissed the AIR approach as being another attempt at building a proprietary and competing platform. But I was actually missing a part of the AIR attraction: the fact that you could actually built an application using HTML and Javascript, and call the AIR APIs from your regular Javascript code. So you could limit the need for proprietary features to only what makes AIR special such as:
- providing an application update mechanism letting you publish a new version and have your users know about its availability including the ability to update it. This is a bit like Microsoft ClickOnce but for the Web.
- fluid (non-rectangular) window shape capabilities including transparency
- access to local storage either using files or the SQLite database
- online/offline awareness
- user notification from the system tray
So how does this compare with Microsoft? Well, although I think Silverlight is great in the sense I can leverage .NET languages – either static ones like C# or dynamic ones like IronRuby – the catch is that XAML and the Silverlight framework are proprietary and present a learning curve. Silverlight 3 will offer the out-of-browser experience as well but will still be somewhat hamstrug by the web sandbox. This is can be a benefit and a downside as well.
But with AIR, I can actually include all my favorite Javascript frameworks (e.g. jQuery and its plugins, ActiveJS, etc.) to add fade-in effects, dialog panels, etc.
In the end, what the big deal with AIR?
Well, the big thing for me is that you can leverage web design and development skills and build rich but web-aware client apps.
Breathing It In
So I decided to take a breath of fresh AIR and started on my first project, an app allowing the user to drag and drop media files, organize/tag them, and finally compress and upload them to a web site. I soon realized this was a bit ambitious for a first project so I decided to only focus on a subset of the desktop features such as:
- drag and drop of files
- table display with editing of classification information
- local persistence beyond a user session (stop and pick-up later where you left off)
- auto-update

For my second app, I decided to tackle how to call SOAP web services such as the one offering detail weather data provided by NOAA’s National Weather Service. For that project I leveraged the jqSOAPclient jQuery plugin which allowed me to easily construct SOAP request and automatically convert SOAP responses into JSON object for easier handling in Javascript.

Now Inhale!
I built my apps using the Aptana IDE, which allows you to step through your Javascript code. Plus, AIR gives you “Introspector” a tool similar to FireBug which allows you to inspect your DOM tree and evaluate Javascript statement (useful to test jQuery fragments). And to troubleshoot and visualize my local database I used Lita, which is actually a well-written and fun to use AIR SQLite administration tool. I have to say I had a great amount of fun!
Look for future blog posts covering these 2 apps!
Hopefully you will inhale AIR too and give it a try when building your web-aware rich client!
References and Resources
- Adobe AIR Developer Center for HTML and Ajax
- AIR CheatSheet
- AIR Pocket Guide
- AIR Update Framework
- “Tips for building AIR applications that can be easily updated” by David Deraedt
- Aptana (IDE) AIR Plug-In
- “Lita: AIR-based SQLite Administration Tool” by by David Deraedt – *** MUST HAVE! ***
July 2nd, 2009
Posted by
techarch |
AIR, Ajax |
no comments