Thursday, February 3, 2011

Eclipse, Java, JRuby, Cucumber and ATDD

Background

So what is it that we want to do? Quite simply. We want to be able to build a software system from the ground up based upon acceptance criteria rather than some dream or vision a particular person had after eating some strange food.

How do we do this if our language of choice is Java, we are using Eclipse, and we are running on Mac?

Prerequisites for Eclipse

1. You are running Mac OS 10.6.6
2. You have installed Eclipse Helios

Installing JRuby from source

The hard way (for us geeks):

1. Download the latest jruby from "http://jruby.org/download"

2. It should download something like: "jruby-bin-[VERSION].tar.gz", e.g. "jruby-bin-1.6.0.RC1.tar.gz"; all subsequent steps will refer to the example version

3. Create a directory: "/Library/jruby", from a command line: mkdir /Library/jruby

4. Copy the jruby source to "/Library/jruby"

5. Extract the jruby directory to "/Library/jruby", from a command line: "tar -xvf jruby-bin-1.6.0.RC1.tar.gz"

6. Add the following to your ~/.bash_profile:
a. "export JRUBY_HOME=/Library/jruby/jruby-1.6.0.RC1"
b. "export PATH=$PATH:$JRUBY_HOME/bin"

7. Restart your command line, or resource your ~/.bash_profile: "source ~/.bash_profile"

8. Test by running "jruby -v"

The easy way (for us efficient people that happen to be running macports):

1. Run from command line: sudo port install jruby

Setting up Cucumber and JRuby

1. Run from command line: sudo jruby -S gem install cucumber

Get going

1. Create a new workspace in Eclipse named "calculator"

2. Create a new java project called "calculator"

3. Create a directory named "features" under the root of calculator

4. Create a new file under "features" named "add_two_numbers.feature"

5. Add the following to the file


Feature: Add two numbers

In order to add not have to use my head

I want to add two numbers

Scenario: Add two numbers

Given I have a calculator

When I add the numbers 1 and 2

Then I get 3


6. From the command prompt in your project directory, run: "jruby -S cucumber features". You should see the following output:

Feature: Add two numbers
In order to add not have to use my head
I want to add two numbers

Scenario: Add two numbers # features/add_two_numbers.feature:5
Given I have a calculator # features/add_two_numbers.feature:6
When I add the numbers 1 and 2 # features/add_two_numbers.feature:7
Then I get 3 # features/add_two_numbers.feature:8

1 scenario (1 undefined)
3 steps (3 undefined)
0m0.046s

7. Now, create a directory called "steps" under your "features" directory

8. In your steps directory, create a file called "calculator_steps.rb"

9. Add the following to the file:


require 'rspec'

require 'java'

$CLASSPATH << File.expand_path('.') + "/bin/"

include_class Java::com.seventytimessevengroup.Calculator


Given /I have a calculator/ do

end


When /I add the numbers (\w+) and (\w+)/ do |first_number, second_number|

<

end


Then /I get (\w+)/ do |result|

end


10. From the command prompt in your project directory, run: "jruby -S cucumber features".

You should see the following output:
Feature: Add two numbers
In order to add not have to use my head
I want to add two numbers

Scenario: Add two numbers # features/add_two_numbers.feature:5
Given I have a calculator # features/steps/calculator_steps.rb:5
When I add the numbers 1 and 2 # features/steps/calculator_steps.rb:8
Then I get 3 # features/steps/calculator_steps.rb:11

1 scenario (1 passed)
3 steps (3 passed)
0m0.032s

11. This is great, we are all green, now, let's try to call the code we wish we had. Add this code to your steps:


require 'rspec'

require 'java'

$CLASSPATH << File.expand_path('.') + "/bin/"

include_class Java::com.seventytimessevengroup.Calculator


Given /I have a caclulator/ do

@calculator = com.seventytimessevengroup.Calculator.new

end


When /I add the numbers (\d+) and (\d+)/ do |first_number, second_number|

end


Then /I get (\w+)/ do |result|

end

12. From the command prompt in your project directory, run: "jruby -S cucumber features".

You should see the following output:
Feature: Add two numbers
In order to add not have to use my head
I want to add two numbers

Scenario: Add two numbers # features/add_two_numbers.feature:5
Given I have a calculator # features/steps/calculator_steps.rb:5
uninitialized constant Calculator (NameError)
./features/steps/calculator_steps.rb:6:in `/I have a calculator/'
features/add_two_numbers.feature:6:in `Given I have a calculator'
When I add
the numbers 1 and 2
# features/steps/calculator_steps.rb:9
Then I get 3 # features/steps/calculator_steps.rb:12

Failing Scenarios:
cucumber features/add_two_numbers.feature:5 # Scenario: Add two numbers

1 scenario (1 failed)
3 steps (1 failed, 2 skipped)
0m0.027s

13. Maybe we should now make our class called "Calculator.java" and add the following code to it.

package com.seventytimessevengroup;


public class Calculator {

public Calculator() {

}

}


14. From the command prompt in your project directory, run: "jruby -S cucumber features". You should see the following output:

Feature: Add two numbers
In order to add not have to use my head
I want to add two numbers

Scenario: Add two numbers # features/add_two_numbers.feature:5
Given I have a calculator # features/steps/calculator_steps.rb:5
When I add
he numbers 1 and 2
# features/steps/calculator_steps.rb:9
Then I get 3 # features/steps/calculator_steps.rb:12

1 scenario (1 passed)
3 steps (3 passed)
0m0.032s

15. Great, now let's continue down our scenario and add two numbers. Add the following to your "calculator_steps.rb":


require 'rspec'

require 'java'

$CLASSPATH << File.expand_path('.') + "/bin/"

include_class Java::com.seventytimessevengroup.Calculator


Given /I have a caclulator/ do

@calculator = com.seventytimessevengroup.Calculator.new

end


When /I add the numbers (\d+) and (\d+)/ do |first_number, second_number|

@calculator.add(first_number.to_i, second_number.to_i)

end


Then /I get (\w+)/ do |result|

end


16. From the command prompt in your project directory, run: "jruby -S cucumber features".

You should see the following output:
Feature: Add two numbers
In order to add not have to use my head
I want to add two numbers

Scenario: Add two numbers # features/add_two_numbers.feature:5
Given I have a calculator # features/steps/calculator_steps.rb:5
When I add the numbers 1 and 2 # features/steps/calculator_steps.rb:9
undefined method `add' for # (NoMethodError)
./features/steps/calculator_steps.rb:10:in `/I add the numbers (\d+) and (\d+)/'
features/add_two_numbers.feature:7:in `When I add the numbers 1 and 2'
Then I get 3 # features/steps/calculator_steps.rb:13

Failing Scenarios:
cucumber features/add_two_numbers.feature:5 # Scenario: Add two numbers

1 scenario (1 failed)
3 steps (1 failed, 1 skipped, 1 passed)
0m0.033s

17. Now let's add our add method to our Calculator object.


package com.seventytimessevengroup;


public class Calculator {

private int result;


public Calculator() {

}

public void add(int firstNumber, int secondNumber) {

result = firstNumber + secondNumber;

}

}

18. From the command prompt in your project directory, run: "jruby -S cucumber features".

You should see the following output:
Feature: Add two numbers
In order to add not have to use my head
I want to add two numbers

Scenario: Add two numbers # features/add_two_numbers.feature:5
Given I have a calculator # features/steps/calculator_steps.rb:5
When I add the numbers 1 and 2 # features/steps/calculator_steps.rb:9
Then I get 3 # features/steps/calculator_steps.rb:13

1 scenario (1 passed)
3 steps (3 passed)
0m0.031s

19. Now, to complete our feature step definition:


require 'rspec'

require 'java'

$CLASSPATH << File.expand_path('.') + "/bin/"

include_class Java::com.seventytimessevengroup.Calculator'


Given /I have a calculator/ do

@calculator = com.seventytimessevengroup.Calculator.new

end


When /I add the numbers (\d+) and (\d+)/ do |first_number, second_number|

@calculator.add(first_number.to_i, second_number.to_i)

end


Then /I get (\w+)/ do |result|

@calculator.result().should == result.to_i

end


20. Now we need to finish our Calculator object.


package com.seventytimessevengroup;


public class Calculator {

private int result;


public Calculator() {

}

public void add(int firstNumber, int secondNumber) {

result = firstNumber + secondNumber;

}

public int result() {

return result;

}

}


21. From the command prompt in your project directory, run: "jruby -S cucumber features". You should see the following output:
Feature: Add two numbers
In order to add not have to use my head
I want to add two numbers

Scenario: Add two numbers # features/add_two_numbers.feature:5
Given I have a calculator # features/steps/calculator_steps.rb:5
When I add the numbers 1 and 2 # features/steps/calculator_steps.rb:9
Then I get 3 # features/steps/calculator_steps.rb:13

1 scenario (1 passed)
3 steps (3 passed)
0m0.035s

22. And there you have it, a calculator fully acceptance-test driven.

23. And if you really wanna have fun, but not sure if you need it, add a junit file "CalculatorTest.java"


package com.seventytimessevengroup.tests;


import static org.junit.Assert.*;


import org.junit.Test;


import com.seventytimessevengroup.Calculator;


public class CalculatorTest {

private final Calculator calculator = new Calculator();

@Test

public void addTwoNumbers() {

calculator.add(1, 2);

assertEquals(3, calculator.result());

}

}


24. How about that, fully test-driven as well, not sure why you need this last one though.

The full source code can be found here: https://github.com/jpavlic/Calculator

3 comments:

  1. Hi there. Nice writeup.

    I went through your tutorial, however, I am getting strange characters in the output.

    I am using JRuby 1.6.3 with Cucumber 1.0.2. When I run the feature (jruby -S cucumber features\add_two_numbers.feature) I am getting:

    Feature: Add two numbers
    In order to add not have to use my head
    I want to add two numbers
    Scenario: Add two numbers←[90m # features\add_two_numbers.feature:7←[0m

    ←[32mGiven I have a calculator←[90m # features/steps/calculator_steps.rb:11←[0m←[0m
    ←[32mWhen I add the numbers ←[32m←[1m1←[0m←[0m←[32m and ←[32m←[1m2←[0m←[0m←[32m←[90m#features/steps/calculator_steps.rb:18←[0m←[0m
    ←[32mThen I get ←[32m←[1m3←[0m←[0m←[32m←[90m # features/steps/calculator_steps.rb:25←[0m←[0m
    1 scenario (←[32m1 passed←[0m)
    3 steps (←[32m3 passed←[0m)
    0m0.043s

    What are those numbers with arrow and square bracet in the output?
    How do I get rid of them?

    ReplyDelete
  2. Ok. So the answer to your problem is that it is a command line color issue. run the following command and it will remove it:

    jruby -S cucumber --no-color features/add_two_numbers.feature

    Or you can install the gem: term-ansicolor

    ReplyDelete
  3. Hi,

    Thanks for such a descriptive note.

    I have done all the steps as you describe above, when I run the test I got below error:

    F:\Automation Tools\360logica\Calculator>jruby -S cucumber --no-color features/a
    dd_two_numbers.feature
    cannot load Java class com.seventytimessevengroup.Calculator (NameError)

    I have resolved this error by adding
    "java_package 'com.seventytimessevengroup'
    java_require 'Calculator'"
    Please see below code:

    require 'rspec'
    require 'java'
    $CLASSPATH << File.expand_path('.') + "/bin/"
    #@str = import('com.seventytimessevengroup.Calculator')
    java_package 'com.seventytimessevengroup'
    java_require 'Calculator'
    #include_class Java::com.seventytimessevengroup.Calculator
    Given /I have a calculator/ do
    @c = Java::Calculator()
    p "first done"
    #@calculator = com.seventytimessevengroup.Calculator.new
    end

    When /I add the numbers (\d+) and (\d+)/ do |first_number, second_number|
    @c.add(first_number.to_i, second_number.to_i)
    p "Second done"
    end

    Then /I get (\w+)/ do |result|
    @c.result().should == result.to_i
    p "third done"
    end

    Now when I run this , I got Java package `calculator' does not have a method `add' (ArgumentError) error.While I am passing the correct argument.

    Please help me ASAP.

    Thanks.

    ReplyDelete