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

Thursday, December 2, 2010

Ruby, SWIG, Visual Studio 2008 and Windows 7

If you are like me, you might be wondering why it is so hard to do simple things.

Well this blog is going to be of great use to you if you are trying to have your Ruby code call into your C++ code in a Microsoft development environment. Why does anybody care you ask?

Well, some of us are still stuck in legacy land. The happy, or not so happy, land of C++. The world of software is changing, and those poor souls like you and me are trying to adapt. What are we adapting to you ask? ATDD and TDD.

So if you look around at the world of TDD and C++, the Visual Studio kind, which I might add is not really C++, it is best labeled as VC++, you will find that there are great tools like CPPUTest, Google Test, Boost, etc. This is great for doing TDD and unit testing, but what happens when you want to do ATDD? Well, generally nothing.

But not today. Today is the dawning of a new day, where you can use an awesome language like Ruby to call into your legacy code and start fixing it. Yes, not all of us have the luxury of green field rewrites.

So, here we go:

1. Purpose

To setup a ruby 1.8.7 environment on a windows 7 machine that can be used with SWIG to interface ruby with Visual Studio 2008.

2. Environment
  • Windows 7 64-bit running on a Macbook Pro.
  • Visual Studio 2008
  • Ruby 1.8.7
  • SWIG 2.0.1
3. Detailed steps to get Ruby built and installed

0. Run shortcut for Visual Studio 2008: "%comspec% /k ""c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" x86" as Administrator
1. Extract "ruby-1.8.7-p160.tar.gz" to c:\ruby-1.8.7-p160
1.a. Extract "zlib123.zip" to c:\zlib123
1.b. In the command prompt: cd c:\zlib123
1.c. In the command prompt: nmake -f win32\Makefile.msc
1.d. In the command prompt: nmake -f win32\Makefile.msc test
1.e. In the command prompt: copy C:\zlib123\zlib.h "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include"
1.f. In the command prompt: copy C:\zlib123\zconf.h "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include"
1.g. In the command prompt: copy C:\zlib123\zlib.lib "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib"
1.h. In the command prompt: copy C:\zlib123\zdll.lib "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib"
1.i. In the command prompt: copy C:\zlib123\zdll.exp "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib"
3. In the command prompt: cd c:\ruby-1.8.7-p160\win32
4. In the command prompt: configure
5. In the command prompt: nmake
6. In the command prompt: nmake DESTDIR=c:\ruby187P160 install
7. In the command prompt: cd c:\ruby187P160\bin
8. In the command prompt: copy C:\zlib123\zlib1.dll c:\ruby187P160\bin
9. Add c:\ruby187P160\bin to the environment variable PATH
10. Copy the following files to c:\ruby187P160\bin: dbm3.dll, gdbm3.dll, libeay32-0.9.8-msvcrt.dll, libiconv2.dll, pdcurses.dll, ssleay32-0.9.8-msvcrt.dll, zlib1.dll
11. Create a file: setrbvars.bat

@ECHO OFF
REM Determine where is RUBY_BIN (where this script is)
PUSHD %~dp0.
SET RUBY_BIN=%CD%
POPD

SET RUBYOPT=

REM Add RUBY_BIN to the PATH
REM RUBY_BIN takes higher priority to avoid other tools
REM conflict with our own (mainly the DevKit)
SET PATH=%RUBY_BIN%;%PATH%
SET RUBY_BIN=

REM Display Ruby version
ruby.exe -v

12. In the command prompt: setrbvars
13. Extract "rubygems-1.3.7.zip" to c:\rubygems-1.3.7
14. In the command prompt: cd c:\rubygems-1.3.7
15. In the command prompt: ruby setup.rb
17. open "C:\ruby187P160\lib\ruby\site_ruby\1.8\rubygems\package\tar_input.rb"
18. edit "zipped_stream(entry)" (line 203--216) as follows:

def zipped_stream(entry)
if defined? Rubinius then
zis = Zlib::GzipReader.new entry
dis = zis.read
is = StringIO.new(dis)
else
# This is Jamis Buck's Zlib workaround for some unknown issue
entry.read(10) # skip the gzip header
zis = Zlib::Inflate.new(-Zlib::MAX_WBITS)

######### EDIT START ###
#is = StringIO.new(zis.inflate(entry.read))

__buf = ""
while __temp = entry.read( 128 * 1024 ) do
__buf << zis.inflate( __temp )
end

is = StringIO.new( __buf )
######### EDIT END #####

end
ensure
zis.finish if zis
end

19. In the command prompt: gem install rails rspec rspec-rails webrat cucumber

4. Detailed steps to create a Visual Studio c++ 2008 project to work with Ruby

1. Open Visual Studio 2008
2. Create a new "Visaul C++" Project
3. Choose MFC DLL
4. Name the project "CodingHappy"
5. Just use the defaults
6. Delete all the files but "CodingHappy.h" and "CodingHappy.cpp"
7. Under the Property Pages, in "Configuration Properties" -> "General" choose "Use Standard Windows Libraries"
8. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "General" -> "Additional Include Directories" enter: "C:\ruby187P160\lib\ruby\1.8\i386-mswin32_90"
9. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "Preprocessor" -> "Preprocessor Definitions" enter: "_DEBUG;_WINDOWS;WIN32;_USRDLL;EXAMPLE_EXPORTS;NT=1;IMPORT"
10. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "Code Generation" -> "Code Generation" choose "Multi-threaded Debug (/MTd)"
11. Under the Property Pages, in "Configuration Properties" -> "C/C++" -> "Precompiled Headers" -> "Create/Use Precompiled Header" choose "Not Using Precompiled Headers"
12. Under the Property Pages, in "Configuration Properties" -> "Linker" -> "Input" -> "Additional Dependencies" enter: "C:\ruby187P160\lib\msvcr90-ruby18.lib"
13. Under the Property Pages, in "Configuration Properties" -> "Linker" -> "Input" -> "Module Definition File" enter: ""
14. Add a new item, a C++ file -> "CodingHappy_wrap.cxx"
15. Add a new item, an "i" file -> "CodingHappy.i"
16. Add the following to the file

/* CodingHappy.i */
%module CodingHappy
%{
/* Put header files here or function declarations like below */
extern double pi;
extern int factorial(int n);
extern int permutation(int n,int r);
%}

extern double pi;
extern int factorial(int n);
extern int permutation(int n,int r);

17. In CodingHappy.h enter the following:

#ifndef CODINGHAPPY_H
#define CODINGHAPPY_H

int factorial(int n);
int permutation(int n, int r);

#endif

18. In CodingHappy.cpp enter the following:

// CodingHappy.cpp
//

double pi = 3.14159; //not used yet

int factorial(int n) {
if (n <= 1)
return 1;
else
return n*factorial(n-1);
}

int permutation(int n, int r)
{
return factorial(n) / factorial(n-r);
}

20. In the properties for CodingHappy.i on "Configuration Properties" -> "General" -> "Custom Build Step" -> "General" -> "Command Line" enter: "C:\Users\Public\Documents\BDD\swigwin-2.0.1\swig -c++ -ruby $(InputPath)" - note: make sure your path has no spaces, or hard-code it here
21. In the properties for CodingHappy.i on "Configuration Properties" -> "General" -> "Custom Build Step" -> "General" -> "Outputs" enter: "$(ProjectDir)\$(InputName)_wrap.cxx"