Monday, January 21, 2008

What do I gain from TDD or BDD?

The question what do I gain from doing TDD/BDD is asked quite often. I understand the reasons people ask for that. I have practised TDD since 2001. This post is an explanation of why I TDD/BDD and what I gain from it.

Why do I BDD?

BDD helps me with 3 aspects of software development: Design, Defense, Documentation.
  • Design
    • When I write the specifications(tests) first, I see that my design is simpler (as compared to write the code first) for given requirements, thus better.
      • I know it's not very intuitive, you just have to try it.
    • When you start with tests, your design is more testable.
      • It makes debugging easier.
    • TDD doesn't replace drawing diagrams.
      • It's just an additional design technique.
      • I still enjoy drawing UML-like diagrams!
  • Defense
    • I feel much safer having my application covered with tests, .
      • Thanks to that, I can do some refactoring when I feel it's needed, run the tests, and if it's 'green' I just check it in.
    • Whenever some of my users find any bug, I prepare a test that covers it.
      • Only then I fix the bug and release the change.
      • There's nothing worse than having a bug that reappears again (after fixing it) in the application.
  • Documentation
    • I'm not a fan of big Word documents that describe all details of the requirements.
      • It's just too difficult to synchronize this document with reality after every requirements change
        • Changes are inevitable.
      • There was one large project I was working on, where nobody from the team (including the project manager) knew what all the requirements were!
        • Yes, it is pathological.
    • I store all of the requirements as executable stories/tests.
      • Before releasing any change I run all the stories.
      • When there is a requirement change I just find the story responsible for that, change it appropriately and change the code.
        • Changes are inevitable.
Do I have to TDD/BDD in order to be successful?

Well... No.

I know some very good developers that don't write any tests and are successful. There are other ways of ensuring good design or documentation. As for defense, you may have a very fast testing department that makes sure you didn't break anything. Or maybe you just prefer to manually go through your application. For me it's just too slow,
I prefer an immediate response that is free of any human mistakes. Chances are, you are just so perfect that you don't introduce any breaking changes. Lucky you!

It's also possible that you separate the analysis, design and implementation phases so strongly, that there is no way of changing anything during the implementation phase. I'm not a big fan of this approach but I know that many people are successful with that. I believe that even in this case you can gain a lot from writing acceptance tests (executable stories). Writing unit tests for your classes is also a good thing. I'll try to cover the topic of testing on different levels (acceptance, units) in a separate post.


But it's difficult and expensive, isn't it?

Yes, it is. All I gain doesn't come for free.

First of all, it requires discipline. It takes some time to get used to write tests first.
Also, it takes some time to learn how to do correct TDD/BDD. The good news is that you can start introducing it gradually.

Another thing is that after creating a lot of tests for your applications the whole suite may take some time to run. It's a problem that is not easy to solve, but believe me you'd prefer this kind of problems than a 5-days debugging session.

Is it fun?

Yes, it's a lot of fun!
Again, you have to try it before you know, what I'm talking about. If you're still new to TDD, look around, maybe there is someone experienced nearby who can show you a quick demo.

What next?

Find some TDD tools for your technology. I believe they exist for every programming language. Find a place in your application where you'd like to start testing. Start with something simple. Maybe assuring that a request to a given page is successful? Or maybe asserting that all the values in the Order class are calculated correctly? Make sure you run those tests after every change of your application.

Feel free to ask me questions about TDD or BDD. I have practised it with Java, C#, IronPython and Ruby. The technology doesn't matter too much in this case. You can use comments for that or simply email me at andrzejkrzywda at gmail.com.

Thursday, January 17, 2008

RSpec User Story example

Today, just a quick example showing what kind of wonderful things you can do with the new RSpec and its support for executable User Stories, a feature I was dreaming about for years...

Specification as a user story:



Story: Creating an order

Scenario: admin user creates an order
Given an admin user
And some orders
And some customers

When he creates an order

Then a new order is created
And a new customer is created

Implementation of the user story



Given "an admin user" do
login_as_admin
end

Given "some orders" do
@orders_count = Order.count
end

Given "some customers" do
@customers_count = Customer.count
end

When "he creates an order" do
post '/orders/create',
"order"=>{"address_attributes"=> {"name"=>"Customer 1"}}
end

Then "a new order is created" do
Order.count.should == @orders_count + 1
end

Then "a new customer is created" do
Customer.count.should == @customers_count + 1
end

The output when the user story is run:



Running 1 scenarios

Story: Creating an order

Scenario: admin user creates an order

Given an admin user
And some orders
And some customers

When he creates an order

Then a new order is created
And a new customer is created

1 scenarios: 1 succeeded, 0 failed, 0 pending

Tuesday, January 15, 2008

5 reasons to try Resolver One

Disclaimer: I am a Resolver employee, have helped develop it and think it is extraordinarily cool.
  • 1. It's an amazing combination of a spreadsheet application and an IronPython IDE.
    • Resolver One shows a typical spreadsheet grid and a code box.
      • Whatever you do with the grid is reflected in a generated code.
      • In the code box you can write some code which manipulates the workbook object, all the changes are then displayed in the grid.
    • Resolver One is now 1.0 !
  • 2. You can create maintainable spreadsheets.
    • Thanks to the two-ways synchronization a spreadsheet is actually a Python program.
    • Thus, you can do everything that you can do with a program.
      • Create modules, classes, methods.
  • 3. It's probably the first time you can actually unit test your spreadsheet logic and furthermore do some refactoring on it!
  • 4. Because now creating a spreadsheet is limited only by your imagination.
  • 5. It's easy to import your existing spreadsheet, script it with Python and export it back.
Are you convinced now?

Then go and grab a copy of Resolver One
Oh, and did I mention it's free for non-commercial use?

More resources:

resolversystems.com
resolverhacks.net

Friday, January 4, 2008

A Guide to Deploying Rails Applications

This article describes the way I deploy my Rails applications.

Thanks to Ruby on Rails I can quickly help my customers. The speed of development with Rails and agile practices make my work really fast. The only time when I slow down a bit is when I create and deploy a new project. If I saved the time somehow I could spend this time solving business problems instead.
I decided to start with a documentation of my deployment/development process. Then, I automated and simplified all I could and had time to. The result is a semi-automatic process which helps me with creating applications. This process is specialised for my needs and it uses the tools that I found useful. It doesn't have to be good for your needs.
All the setup was focused on agility.
I love to check-in frequently and be able to see the changes immediately on the production server. That's why I use tools like Capistrano or Vlad.

A quick description of the tools I use:
  • Rails
    • I don't think I have to explain why :)
    • most of the time I use edge Rails
  • Mongrel
    • A standard way of deploying in the Rails world.
    • fast and stable
    • Easy to configure
  • nginx
    • an http server that is responsible for load balancing
    • easy to configure (even easier if you can read Russian blogs)
    • easy to handle many Rails applications.
  • Capistrano
    • A standard tool for easy deployment.
    • I use Vlad for some of my applications, both are good.
  • Piston
    • Easy plugins management
  • Subversion
  • mysql
Nginx configuration may be tricky when you work on a shared hosting but the main points stay the same also with Apache and other Http servers. Some of my applications are still on a shared machine (I recommend Webfaction), but most of them are on a dedicated machine.

Let's assume that you want to create and deploy a new Rails applications. The whole process can be described in 5 general steps.
  1. Create an SVN repository, create the app and import it to the repository.
  2. Use capistrano to make the remote work easier.
  3. Create the production database.
  4. Prepare mongrel_cluster to work with the new application.
  5. Configure nginx and bind the domain.

Ok, let's go into details.
  • Remote: Create a repository for the application.
    • svnadmin create /var/repos/app
      • I prefer to have a separate repo for each of my applications.
      • It's good to have one directory for all repositories
      • I use svn+ssh protocol, in my case it's easier to have just one source of users (Linux users)
  • Checkout the application to the /tmp/app directory
    • svn co $REMOTE_SVN/app /tmp/app
      • I recommend having environment variables for the svn paths, works nicely with shell autocompletion.
  • Use edge rails to create a rails app.
    • svn up ~/tmp/rails_edge
      • update it first
      • my rails_edge lives there, you can always create it with 'svn co $RAILS_DEV_TRUNK'
    • ruby ~/tmp/rails_edge/railities/bin/rails -dmysql /tmp/app/trunk
      • '-dmysql' because Rails has recently changed its default db to sqlite
    • svn ci -m "initial import"
  • Live on Rails edge
    • piston import $RAILS_DEV_TRUNK vendor/rails
    • svn ci -m "piston imported edge rails"
  • capify
    • capify .
    • It creates two files.
  • modify config/deploy.rb:

set :application, "app"
my_server = "12.34.56.7"
set :repository, "svn+ssh://#{my_server}/var/repos/app/trunk"
set :deploy_to, "/var/www/#{application}"
role :app, my_server
role :web, my_server
role :db, my_server, :primary => true

  • Commit.
    • svn ci -m "capistrano setup"
  • cap deploy:setup
    • it creates all the directories on the remote server
  • cap deploy:cold
    • it checkouts the code, creates a 'current' symlink
    • logs are in the 'shared' directory
    • it fails on db:migrate task which is fine for now.
  • Create the production database
    • Remote: Go to /var/www/app/current
    • Remote: rake RAILS_ENV=production db:create
  • Local: cap deploy:cold
    • Again.
    • It should pass the db:migrate now
    • But it fails on missing script/spin file
  • Create script/spin with the following content:
    • mongrel_cluster_ctl restart
    • Yes, you need mongrel_cluster on the remote server
    • Add this file to svn and commit
  • cap deploy:cold
    • This time it worked!
    • But... we didn't setup mongrel_cluster to restart our app.
  • Mongrel_cluster
    • Create config/mongrel_cluster.yml
---
cwd: /var/www/app/current
log_file: log/mongrel.log
port: 5000
environment: production
group: www-data
user: your-username
address: 127.0.0.1
pid_file: tmp/pids/mongrel.pid
servers: 3
  • Commit this file
  • cap deploy:cold
    • still doesn't work
    • Mongrel_cluster doesn't know that it should use this file
  • Remote: Go to /etc/mongrel_cluster/
  • Make a symbolic link to the yml file
    • sudo ln -s /var/www/app/current/config/mongrel_cluster.yml app.yml
  • cap deploy:cold
    • And it works!
    • you should now be able to connect to localhost:5000 from the remote server.
  • Now we will bind the domain to this mongrel_cluster.
    • We need to configure nginx.
    • I have setup nginx so that for every application I create a single file that is automatically loaded on nginx startup.
    • The file just needs to live in the /etc/nginx/vhosts/ directory.
    • It works because I have the following line in my /etc/nginx/nginx.conf file:
      • include /etc/nginx/vhosts/*.conf;
  • Create /etc/nginx/vhosts/app.conf

upstream app {
server 127.0.0.1:5000;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
}
server {
listen 80;
client_max_body_size 50M;
server_name app.com www.app.com;
root /var/www/app/current/public;
access_log /var/www/app/current/log/nginx.log;

if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html last;
break;
}

location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect false;
proxy_max_temp_file_size 0;
if (-f $request_filename) {
break;
}
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://app;
break;
}
}

error_page 500 502 503 504 /500.html;
location = /500.html {
root /var/www/app/current/public;
}
}

  • Restart nginx
    • sudo /etc/init.d/nginx stop
    • sudo /etc/init.d/nginx start
  • That's it :)
  • From now on you just write the code, commit and call capistrano to deploy.

I hope you found this guide useful. There is still a lot to improve and a lot of duplicates (like defining mongrel ports). Let me know if you think that I could do something in a better way.

Here is a list of articles that I found useful when I was experimenting with Rails deployment.

Installing and Configuring Nginx and Mongrel for Rails

Install Ruby Rails on Gutsy Gibbon (Nginx Version)


Hosting Rails apps using nginx and Mongrel Cluster

A clean slate, Edge Rails recipe

Thursday, January 3, 2008

Ruby, pipes and RMagick

I wanted to be able to run the following:

ls *.jpg | grep -v small | ruby thumbnailise.rb

This command lists all jpg files, excludes the ones that were already converted (thumbnailisied) and calls a Ruby script passing the files using a pipeline.
In order to do that I had to find a way of using Unix pipelines in Ruby. The solution is using STDIN.readlines:

require 'RMagick'
require 'pathname'

input = STDIN.readlines
input.each do |line|
filename = line.strip
image = Magick::Image.read(filename).first
image.crop_resized!(100, 100, Magick::NorthGravity)
path = Pathname.new(filename)
outFile = "#{path.basename(".jpg")}_small#{path.extname}"
image.write(outFile)
end


Do you know any nicer solutions how to do that?