Hi guys!,
I'm exploring the capabilities provided by this beautiful framework, as It is going to be used by our team at work. So I decided, to take advantage of that, and study it a little deeper and share with you which my impressions are.
Installation
This is just typing:
gem install cucumber
And to install in your Rails project, execute this line on your vendor/plugins directory:
git clone git://github.com/aslakhellesoy/cucumber.git
Features
So the first important difference to notice is that Cucumber is about features (not .stories). The idea behind this is to write the old stories in a more BDD way. So, for each new feature that you have to develop, you should first write a file on the features folder (and with .feature extension), and specify the new functionality on it, in such a way it would fail. When the feature is fully implemented, the test should be successful.
You can execute a cucumber features like this:
* cucumber features/my_new_feature.feature or by executing:
* rake features if you configure the Rakefile of you application with the following code:
require 'cucumber/rake/task'
Cucumber::Rake::Task.new
Contents of features files
Cucumber is about Features (instead of StoryRunner's Stories). Inside a Feature, in a similar fashion as StoryRunner, we got to define an Scenario. So one of the first lines of that file should be an Scenario with a key that identifies it. Then every Scenario is composed by a list of one of those keywords: Given, Then, When (same as StoryRunner) AND But.
Here is a simple example of a Cucumber's feature:
Feature: Serve coffee
In order to earn money
Customers should be able to
buy coffee at all times
Scenario: Buy last coffee
Given there are 1 coffees left in the machine
And I have deposited 1$
When I press the coffee button
Then I should be served a coffee
The definition of what to do within each step (a Given, Then, When or But sentence) is loaded from steps's files in a similar way StoryRunner does. But, Cucumber have an important difference here: they are written using Regular Expressions instead of strings. So you can do something like this:
When /(\w+) enters (his|her) email with "(\S+\@\S+)"/ do |name, email|
fills_in field_name, :with => User.find_by_name(name).email
end
As you can see, using Regular Expressions give us more expressiveness while writing our features.
Until this point, every seems pretty much the same as StoryRunner, but I found a big improvement here:
1) Steps are automatically available from step_definitions (or any sub-folder of features) folder. This is a big difference for reutilization compared with StoryRunner which requires to import explicitly any step's files needed by the story.
2) There's a relatively new feature which let's you invoke steps from within other steps. For example, we can have some common steps like: /Given a user with login '(\S+)'/, /And is not logged in/, among others that can be reused in many features (we probably need our user logged in to use most of our site functionality). Then each we can invoke this steps within other steps, like this:
#features/steps/login_steps.rb
Given /a user with login '(\S+)'/ do
..
end
Given /a password '(\S+)'/ do
..
end
Given /is not logged in/ do
..
end
#features/steps/somefile_steps.rb
Given /User (\S+) with password (\S+) is logged in/ do |username, pass|
Given "a user with login #{username}"
Given "a password #{pass}"
Given is not logged in
end
#features
Feature: Some feature
Scenario: Some scenario description
Given User pepe with password pepe is logged in
And ..
Then ..
This way, we can reuse most of our steps, making our test more DRY.
3) Finally, it is possible to define our stories in a different language than English. But as I won't be using this for now, I didn't realize how interesting this might be..as most of my work is written in English, but you never know :P.
That's enough for now, talk to you later ;)
domingo, 30 de noviembre de 2008
martes, 5 de agosto de 2008
RSpec in Ubuntu: Setting up testing environment
Recently, I had a difficult time when my computer crashed.. After reinstalling Ubuntu desktop, and all the tools I realized how important is to maintain documents about how things are installed and configured :P
One of the things that I've been forced to research was rspec (installation and configuration) in order to restore what I've already had. This is the motivation for this entry: document the way I consider useful to install and customize rspec with notifications, and reports, etc.
Install the gems
Assuming that you already have installed rails (http://wiki.rubyonrails.org/rails/pages/RailsOnUbuntu) and an application (let's called it myapp) suitable to use with rspec. In this context, we are going to install the ZenTest gem (which includes rspec & autotest):
After this step, you'll be able to run the autotest tool from the command line.
Configure autotest
Then in the myapp you have to create the configuration file .autotest:
This code hooks in the autotest evaluation process and invokes the command line utility notify-send with the result of the evaluation. It also uses this pictures within their notifications (that enhance the user experience):
Configure Ubuntu notifiactions
Finally, you have to install the following package in your system:
This tool enable us to display pop-ups within Ubuntu's environment.
RSpec reports
Other important file for autotest is /spec.opts, which is located at myapp/spec:
In this configuration I specify that I want autotest to keep running in the console and, at the same time, I want to generate the html report (rspec_results.html).
Run autotest & see results.
After you configure all that I mentioned, you are able to execute the utility autotest from myapp directory and obtain the following results:
This will generate the following output in the console:
You can also check the details results of doc/rspec_results.html in your browser:
One of the things that I've been forced to research was rspec (installation and configuration) in order to restore what I've already had. This is the motivation for this entry: document the way I consider useful to install and customize rspec with notifications, and reports, etc.
Install the gems
Assuming that you already have installed rails (http://wiki.rubyonrails.org/rails/pages/RailsOnUbuntu) and an application (let's called it myapp) suitable to use with rspec. In this context, we are going to install the ZenTest gem (which includes rspec & autotest):
gem install ZenTest
gem install diff-lcs
After that, You'll need to install both rspec and rspec-rails plugins within your Rails application. For that matter, you should check: github.com/dchelimsky/rspec-rails/wikis/home (where it shows the different procedures you should follow to install it depending on your version of Rails).
After this step, you'll be able to run the autotest tool from the command line.
Configure autotest
Then in the myapp you have to create the configuration file .autotest:
#!/usr/bin/env ruby
require 'autotest/redgreen'
def self.notify title, msg, img, pri='low', time=3000
`notify-send -i #{img} -u #{pri} -t #{time} '#{msg}'`
end
Autotest.add_hook :ran_command do |at|
results = [at.results].flatten.join("\n")
output = results.slice(/(\d+)\s+examples?,\s*(\d+)\s+failures?(,\s*(\d+)\s+not implemented)?(,\s*(\d+)\s+pending)?/)
folder = "~/Pictures/autotest/"
if output =~ /([123456789]|[\d]{2,})\sfailures?/
notify "FAIL:", "#{output}", folder+"rails_fail.png", 'critical', 10000
elsif output =~ /[1-9]\d*\spending?/
notify "PENDING:", "#{output}", folder+"rails_pending.png", 'normal', 10000
else
notify "PASS:", "#{output}", folder+"rails_ok.png"
end
end
This code hooks in the autotest evaluation process and invokes the command line utility notify-send with the result of the evaluation. It also uses this pictures within their notifications (that enhance the user experience):
Configure Ubuntu notifiactions
Finally, you have to install the following package in your system:
sudo apt-get install libnotify-bin
This tool enable us to display pop-ups within Ubuntu's environment.
RSpec reports
Other important file for autotest is /spec.opts, which is located at myapp/spec:
#myapp/spec/spec.opts
--colour
--format html:doc/rspec_results.html
--format progress
--loadby mtime
--reverse
In this configuration I specify that I want autotest to keep running in the console and, at the same time, I want to generate the html report (rspec_results.html).
Run autotest & see results.
After you configure all that I mentioned, you are able to execute the utility autotest from myapp directory and obtain the following results:
marcelo@host:~/workspace/myapp$ autotest
loading autotest/rails_rspec
/usr/bin/ruby1.8 -S script/spec -O spec/spec.opts spec/controllers/users_routing_spec.rb spec/views/users/show.html.erb_spec
...
This will generate the following output in the console:
You can also check the details results of doc/rspec_results.html in your browser:
jueves, 10 de julio de 2008
RSpec: fixtures vs mocks
Hi there!,
It's been a while since the last time I wrote but better late than ever ;)
This time I'm going to dissert a little about the way we write our specs. In particular, as I am a rookie with rspec, I would like to know which is the right way to load data into our tests.
For that matter, I've found that there are mainly two approaches for that: 1) use fixtures or 2) use mocks/stubs instead.
Using Fixtures
Fixtures enable us to define data for our models in a human readable way using YAML syntax. A simple example, as you may have seen for Test:Unit, is shown below.
#spec/fixtures/user.yml
david:
id: 1
name: David Hasselhof
birthday: 1500-01-01
profession: Be-a-god
hashed_password: <%= EncryptionLibrary.encrypt("old_password") %>
Then, in your spec you only have to make reference to the fixture you want to load to make that happened. This approach is very handly for two reasons:
With these fixtures:
Mocks and stubs way
The other option is to use mocks and stubs. Although there is a lot of information in the web about it, to help our discussion, I will describe it very shortly. From my experience & research, I understand that the main difference between those two is the following:
Stubbing a method is all about replacing the method with code that returns a specified result (or perhaps raises a specified exception). Mocking a method is all about asserting that a method has been called (perhaps with particular parameters).
As you can see, stubbing lets you define methods that are not currently implemented or we decided not to depend on. On the other hand, as we mock objects we specify the behavior that we would expect on the mocked object, making our test more Behavior Driven Development.
This is the recommended way of using Rspec as it defines the way the objects must collaborate to accomplish a certain task.
Here we show how we implement the chat's example using mocks.
As you can see, the idea behind mocks is to define some behavior that should be part of certain task, and it is very human readable as it uses many features of Rspec mocks.
Conclusions
As we did with fixtures, we have to mention that Mocking has some drawbacks compared with fixtures, such as: it tights the tests with the data they need and forces us to write, perhaps, to much testing code.
Despite this, I prefer Mocks/Stubs because I think they provide a very elegant way to work with BDD. I think this way because I find in BDD a solid foundation to agile development, then I choose to prioritize it over the other alternatives. Please, make a comment debate this interesting subject that interests me so much ;)
It's been a while since the last time I wrote but better late than ever ;)
This time I'm going to dissert a little about the way we write our specs. In particular, as I am a rookie with rspec, I would like to know which is the right way to load data into our tests.
For that matter, I've found that there are mainly two approaches for that: 1) use fixtures or 2) use mocks/stubs instead.
Using Fixtures
Fixtures enable us to define data for our models in a human readable way using YAML syntax. A simple example, as you may have seen for Test:Unit, is shown below.
#spec/fixtures/user.yml
david:
id: 1
name: David Hasselhof
birthday: 1500-01-01
profession: Be-a-god
hashed_password: <%= EncryptionLibrary.encrypt("old_password") %>
Then, in your spec you only have to make reference to the fixture you want to load to make that happened. This approach is very handly for two reasons:
- This decouples the data to be tested with the specs (tests) itself.
- Is very simple to understand for small bunch of data.
- It works directly with the database which highly degrees the test's performance.
- It is hard to maintain, as it is used by all of your specs (for the description's block that invokes it), and for that: a single change may trigger many errors among different tests.
- It is very implementation dependant, it strongly relies on the model and the current specification of the database's schema.
#spec/views/chats/show.html.erb_spec.rb
describe "views/chats/show.html.erb" do
fixture :chats, :messages
before(:each) do
@chat = chats(:myChat)
render "/chats/show.html.erb"
end
it "should render attributes in paragraph" do
response.should have_text(/MyString/)
end
it "should render all messages for the chat" do
response.should have_tag("tr>td", "Hello!", 1)
response.should have_tag("tr>td", "How are you?", 1)
response.should have_tag("tr>td", "Excellent!", 1)
end
end
With these fixtures:
#spec/fixtures/chats.yaml
myChat:
name: MyString
id: 1
#spec/fixtures/messages.yaml
one:
data: Hello
chat_id: 1
version: 1
user_id: 1
two:
data: How are you?
chat_id: 1
version: 2
user_id: 1
three:
data: Excellent!
chat_id: 1
version: 3
user_id: 1
Mocks and stubs way
The other option is to use mocks and stubs. Although there is a lot of information in the web about it, to help our discussion, I will describe it very shortly. From my experience & research, I understand that the main difference between those two is the following:
Stubbing a method is all about replacing the method with code that returns a specified result (or perhaps raises a specified exception). Mocking a method is all about asserting that a method has been called (perhaps with particular parameters).
As you can see, stubbing lets you define methods that are not currently implemented or we decided not to depend on. On the other hand, as we mock objects we specify the behavior that we would expect on the mocked object, making our test more Behavior Driven Development.
This is the recommended way of using Rspec as it defines the way the objects must collaborate to accomplish a certain task.
Here we show how we implement the chat's example using mocks.
#spec/views/chat/show.html.erb_spec.rb
describe "/chats/show.html.erb" do
include ChatsHelper
before(:each) do
@chat = mock_model(Chat)
@chat.stub!(:name).and_return("MyString")
@chat.stub!(:id).and_return(1)
@chat.stub!(:to_param).and_return(1)
message_1 = mock_model(Message)
message_1.stub!(:data).and_return("Hello!")
message_1.stub!(:version).and_return(1)
message_2 = mock_model(Message)
message_2.stub!(:data).and_return("How are you?")
message_2.stub!(:version).and_return(2)
message_3 = mock_model(Message)
message_3.stub!(:data).and_return("Excellent!")
message_3.stub!(:version).and_return(3)
@messages = [message_1, message_2, message_3]
@chat.stub!(:messages).and_return(@messages)
@message.stub!(:data)
assigns[:chat] = @chat
render "/chats/show.html.erb"
end
it "should render attributes in paragraph" do
response.should have_text(/MyString/)
end
it "should render all messages for the chat" do
response.should have_tag("tr>td", "Hello!", 1)
response.should have_tag("tr>td", "How are you?", 1)
response.should have_tag("tr>td", "Excellent!", 1)
end
end
As you can see, the idea behind mocks is to define some behavior that should be part of certain task, and it is very human readable as it uses many features of Rspec mocks.
Conclusions
As we did with fixtures, we have to mention that Mocking has some drawbacks compared with fixtures, such as: it tights the tests with the data they need and forces us to write, perhaps, to much testing code.
Despite this, I prefer Mocks/Stubs because I think they provide a very elegant way to work with BDD. I think this way because I find in BDD a solid foundation to agile development, then I choose to prioritize it over the other alternatives. Please, make a comment debate this interesting subject that interests me so much ;)
domingo, 30 de marzo de 2008
acts_as_node: visualize the information
Hello everyone,
Once and again, I've been occuping my time coding another plugin, this one, I got to tell, I really enjoy doing it.
Why ? Because I love the topic: "Information Visualization", and this is the very purpose for this plugin, to help you, or your clients, to see the data in graphic form in the way it can help you to inferred information using the perceptual senses.
acts_as_node plugin allows you to see the relationships between specific instances of the application models as it renders a graph drawing based on that information.
It is very straighforward to use, because you don't have to modify any model or controller, instead you just need to use the graph_drawimg helper that makes all the magic within the view.
Installation
First of all, you got to download the plugin in the following way:
./script/plugin install http://acts-as-node.googlecode.com/svn/trunk/acts_as_node
Then, the plugin needs to generate the necesesary files and assets with the generate command:
./script/generate acts_as_node
Finally, we got to define a route for the plugin, that will be used to generate an intermediate xml file which is the input for the graph drawing algorithm. The route should look like this:
#config/routes.rb
map.graph_generator '/graph_generator', :controller => "acts_as_node", :action => "generate_graph"
That's all we need to get started. Now we can just try it out!
Usage
First of all, you have to add the following line to the application's layout file (or where you want to use it):
#app/views/layout/application.rhtml
..
<%= javascript_include_tag :acts_as_node %>
..
Then, all you have to do is use the graph_drawing helper. For that matter, I am going to explain the graph_drawing helper signature to understand how it works:
graph_drawing class_name, root_id, optional_parameters
The parameters represents:
- class_name. The model name of the root of the graph. (Eg. :ProductCategory)
- root_id. The id of root instance that will generate the graph (Eg: 1)
- depth. The depth of the graph involved, counting from the root node.
- layout. This enable us to set which layout we want to render the graph. The available options are: :SnowflakeLayout & :ForceDirectedLayout.
- content. The DOM elment's id that will contain the drawing. If this value is not set, it will be add to document.body object.
- childRadius. The lenght of the edge between nodes
- fanAngle. The angle between silbling nodes, applies only for the Snowflake layout.
- mass. This value represents the mass of each node for the algorithm, it is used to calculate the forces & the separation between the nodes, applies only for ForceDirected layout (default value: 0.3)
- children_accesors. This is an important parameter, it indicates which methods of the model we will invoked to inspect for children nodes. It MUST be defined as a hash like the following: { :ProductCategory => [:children, :compaines] }. In this example, the graph_drawing helper will inspect the children's and companies's collections of the ProductCategory instances that the algorithm reaches.
- children_fields. This enables the algorithm to set the appropiate title, content & picture attributes of the nodes. For example, in the followin definition for children_fields => { :ProductCategory => { :title => :name, :content => :description, :picture => :picture_uri}, the graph_drawing helper will render all ProductCategory nodes with a tooltip that contains a title assosiated with the name field of the instance, the content of the tooltip will be the value of description field of the instance, and the node itself will be rendered with the image provided by the picture_uri field.
- image_width. This parameter enables you to set the width of the image for the pictures of the nodes.
- image_height. Likewise The same concept of image_width related to the height of the pictures.
#sample.rhtml
..
<%= graph_drawing :NetworkNode, 1, :container => "$('graph_id')", :children_fields => { :NetworkNode => {:title => :name, :content => :descrption, :picture => 'picture' }} %>
..
This configuration generates an output similiar to the picture shown at the beginning of the article. In that case, the algorithm will start with the object NetworkNode.find(1), provided that the class_name parameter is :NetworkNode and the root_id is 1. The drawing will be displayed within the DOM element with 'graph_id' identificator. The values used to display the attributes of the tooltip are :name (for the header), and :content (for the body). The image is obtained by invoking the picture method of the instance. It is worth noticing that :children_accesors are not defined, this way the helper assumes :children method is present (this will be the case if the model uses acts_as_tree, acts_as_nested, among other well known plugins).
Another sample, a little bit more comlex:
<%= graph_drawing :ProductCategory, 1, :layout => :ForceDirectedLayout, :mass => 0.3, :container => "$('graph_id')", :children_accessors => { :ProductCategory => [:children, :companies, :legal_questions], :Company => [], :LegalQuestion => [:legal_categories], :LegalCategory => []}, :children_fields => { :ProductCategory => {:title => :name, :picture => 'picture', :content => :name}, :Company => {:title => :name, :content => :name}, :LegalQuestion => {:title => :title, :content => :answer_html}, :LegalCategory => {:title => :name, :content => :name}} %>
In this example, the graph will be generated starting from a ProductCategory instance (the one with id = 1). And it will be asked for the following collections: children, companies, legal_questions. Then for each instance that the algorithm reaches, it will look up in the children_accesors hash to see what collection it has to cover. For example, when it reach a LegalQuestion instance, it will only process the legal_categories collection (as it is the only collection name defined for :LegalQuestion in the hash).
Improvment & Comments
If you have any comments, questions, and/or suggestions please don’t hesitate to send me an email here.
Etiquetas:
graph drawing,
information visualization,
plugin,
rails
martes, 15 de enero de 2008
Active_poll Plugin
Finally, I am glad to announce that I have released the active_poll plugin, which by the way, is my first Ruby On Rails plugin. This plugin is intended to provide poll functionality to applications that register the votes associated with users or, if not desired, just count the votes for anonymous participants.
Install plugin
To install it, you have to run the following command:
This command will run a script that will prompt you which is the name of the model you want to associate with a user vote (typically the User model). If you don't want to track which users made the votes for a particular
poll, you just can configure the poll as anonymous, this way, the
plugin just increments a counter for each selected answer.
This will copy the plugin's files and the corresponding migration into your project. As usual, it means that you will need to run:
Create poll
At this point, we got our plugin properly installed. Now, in order to start using it, we got to create our first poll, with the following command:
Show poll in views
For us to see the poll up & running in our application there are three things left to do:
This plugin contains multiple views, the one that would be rendered (using the helper mentioned in the section above) depends of the circumstances. We describe each one of them here:
Check for logged users
This plugin realizes that the current user is logged in by asking the session for the user_id, in this way:
user_model_id = session[:user_model_id]
If this value is set, then it is assumed that the user is logged, otherwise the user is treated as anonymous.
In particular, this methodology integrates transparently with the restful_authentication which uses the same strategy behind the scenes.
Improvment & Comments
If you have any comments, questions, and/or suggestions please don’t hesitate to send me an email here.
Install plugin
To install it, you have to run the following command:
script/plugin install http://active-poll.googlecode.com/svn/trunk/active_pollThis command will download the plugin code and run a script. The next step is run the generator command:
./script/generate active_poll
This command will run a script that will prompt you which is the name of the model you want to associate with a user vote (typically the User model). If you don't want to track which users made the votes for a particular
poll, you just can configure the poll as anonymous, this way, the
plugin just increments a counter for each selected answer.
This will copy the plugin's files and the corresponding migration into your project. As usual, it means that you will need to run:
rake db:migrate
Create poll
At this point, we got our plugin properly installed. Now, in order to start using it, we got to create our first poll, with the following command:
The script is intended to configure the poll. It will ask you which are the answers, which is the questions displayed, among other configurations (allows multiple selection, who is allowed to vote: registered users, anonymous, etc.) for the poll.
script/runner vendor/plugins/active_poll/create_poll.rb
Show poll in views
For us to see the poll up & running in our application there are three things left to do:
- Use View helper. Insert the active_poll helper within the views that we want to show the poll, as it is shown below:
- poll_name. This is the name that identifies the poll to display. This is the only required parameter.
- :in_place. Enables AJAX functionality to the poll if it is set to true, otherwise the whole page would be reload while submitting a vote.
- :redirect_to. This is a url to redirect after the vote.
- :view_dir. If present, it loads the poll pages (show_poll, already_vote, user_not_logged, etc) from the directory set by this parameter. Otherwise, all the active_poll pages would be take from the views directory inside the active_poll plugin's folder.
- Insert act_as_vote_handler statement. You have to append act_as_vote_handler statement (highlighted with red in the code below) to the controller that handles the view (where the poll is displayed). Here we can see an example:
class SomeController
...
acts_as_vote_handler
..
end - Modify routes.rb accordingly. The acts_as_vote_handler hook defines the ap_vote_registered method which effectivity process the votes. For that matter, we have to enable the route for that method on this controller, which would look like this:
#routes.rb
map.connect 'somecontroller/ap_vote_registered', :controller => 'some', :action => 'ap_vote_registered'
Where:
#someview.rhtml (where you want to show the poll)..
< %= active_poll ( poll_name, { :in_place => true, :redirect_to => some_url, :view_dir => view_directory }
) %>
..
This plugin contains multiple views, the one that would be rendered (using the helper mentioned in the section above) depends of the circumstances. We describe each one of them here:
- after_vote.rhtml. Shows a message after the user votes.
- already_voted.rhtml. Shown when the plugin finds out that you already (by means of database for logged users or cookies for anonymous) voted for the current poll.
- error.rhtml. This view is rendered by means of an internal error (poll delete while someone is voting, for example)
- max_votes_exceeded.rhtml. When the user chooses more options that is allowed in the poll's configuration it shows this view.
- no_vote.rhtml. This is rendered when the user press "Vote" but he/she doesn't select any option.
- poll_not_found.rhtml. Polls are identified by the name, defined with at the time we run the create_poll.rb script. When we use a the active_poll helper with a poll's name that is not defined in the database we show the poll_not_found.rhtml view.
- poll_outdated.rhtml. This view is rendered after the end_date (date of finalization) of the poll is reached.
- show_poll.rhtml. Displays the poll itself, with the appropriate form that submits the vote.
- show_results.rhtml. This view shows the current results of the poll.
- user_not_logged.rhtml. It is displayed when the poll is configured to logged users only and the user is not logged in.
Check for logged users
This plugin realizes that the current user is logged in by asking the session for the user_id, in this way:
user_model_id = session[:user_model_id]
If this value is set, then it is assumed that the user is logged, otherwise the user is treated as anonymous.
In particular, this methodology integrates transparently with the restful_authentication which uses the same strategy behind the scenes.
Improvment & Comments
If you have any comments, questions, and/or suggestions please don’t hesitate to send me an email here.
Suscribirse a:
Entradas (Atom)