RubyMineでRailsをテスト駆動開発(Rspec)してみる(1)

| Component       | version      |
|:----------------|-------------:|
| RubyMine        | 5.0(Windows) |
| Rails           | 3.2.12       |
| Rspec           | 2.13.0       |
| FactoryGirl     | 4.2.0        |
| Faker           | 1.1.2        |
| DatabaseCleaner | 0.9.1        |

## Setup

1. コードを書く

```ruby:Gemfile
group :development, :test do
  gem "rspec-rails"
  gem "factory_girl_rails"
end

group :test do
  gem "faker"
  gem "capybara"
  gem "database_cleaner"
end
```

1. bundle install

1. Tools > Run Rails Script > "rails", "generate rspec:install"

1. Tools > Run Rails Generator > model > Contact firstname:string lastname:string email:string phone:references

1. Tools > Run Rails Generator > model > Phone contact:references phone_type:string phone:string

8. Tools > Run Rake Task > db:migrate

## 1st red

1. テストを書く

```ruby:spec/models/contact_spec.rb
require 'spec_helper'

describe Contact do
  it "is valid with a firstname and lastname"
  it "is invalid without a firstname"
  it "is invalid without a lastname"
  it "is invalid with a duplicate email address"
end
```

1. Run > Run > spec

pending

1. Toggle auto-test

1. テストを書く

```ruby:spec/models/contact_spec.rb
require 'spec_helper'

describe Contact do
  it "is valid with a firstname and lastname" do
    contact = Contact.new(
        firstname: 'Tatsuro',
        lastname: 'Ueda',
        email: 'weed_7777@yahoo.co.jp'
    )
    expect(contact).to be_valid
  end

  it "is invalid without a firstname" do
    expect(Contact.new(firstname: nil).to have(1).errors_on(:firstname))
  end

  it "is invalid without a lastname" do
    expect(Contact.new(lastname: nil).to have(1).errors_on(:lastname))
  end

  it "is invalid with a duplicate email address" do
    Contact.create(
        firstname: 'Tatsuro', lastname: 'Ueda', email: 'weed_7777@yahoo.co.jp'
    )
    contact = Contact.new(
        firstname: 'hoge', lastname: 'fuga', email: 'weed_7777@yahoo.co.jp'
    )
    expect(contact).to have(1).errors_on(:email)
  end
end
```

1. 保存すると自動でテストが走り、こける

1. コードを書く

```ruby:app/models/contact.rb
  validates :firstname, presence: true
  validates :lastname, presence: true
  validates :email, presence: true, uniqueness: true
```

1. 保存すると自動でテストが走り、合格する

## 2nd red

1. テストを書く

```ruby:spec/models/phone_spec.rb
describe Phone do
  it "does not allow duplicate phone numbers per contact"
  it "allows two contacts to share a phone number"
end
```

1. 保存すると自動でテストが走り、pendingになる

1. テストを書く

```ruby:spec/models/phone_spec.rb
describe Phone do
  it "does not allow duplicate phone numbers per contact" do
    contact = Contact.create(
        firstname: 'Tatsuro', lastname: 'Ueda', email: 'weed_7777@yahoo.co.jp'
    )
    home_phone = contact.phones.create(
        phone_type: 'home',
        phone: '01-2345-6789'
    )
    mobile_phone = contact.phones.create(
        phone_type: 'mobile',
        phone: '01-2345-6789'
    )
    expect(mobile_phone).to_not be_valid
  end

  it "allows two contacts to share a phone number" do
    contact = Contact.create(
        firstname: 'Tatsuro', lastname: 'Ueda', email: 'weed_7777@yahoo.co.jp'
    )
    contact.phones.create(
        phone_type: 'home',
        phone: '01-2345-6789'
    )
    other_contact = Contact.new
    other_phone = other_contact.phones.build(
        phone_type: 'home',
        phone: '01-2345-6789'
    )
    expect(other_phone).to be_valid
  end
end
```

1. 保存すると自動でテストが走り、こける

1. コードを書く

```ruby:app/models/contact.rb
  #  belongs_to :phone
  has_many :phones
```

```ruby:app/models/phone.rb
  attr_accessible :phone, :phone_type
  validates :phone, uniqueness: { scope: :contact_id }
```

1. 保存すると自動でテストが走り、合格する

## 3rd red

1. テストを書く

```ruby:spec/models/contact_spec.rb
  describe "#name" do
    it "returns a contact's full name as a string" do
      contact = Contact.new(
          firstname: "Tatsuro", lastname: "Ueda", email: "weed_7777@yahoo.co.jp"
      )
      expect(contact.name).to eq 'Tatsuro Ueda'
    end
  end

```

1. Run > Run > spec

テストが走り、失敗する

1. Toggle auto-test

1. コードを書く

```ruby:app/models/contact.rb
  def name
    [firstname, lastname].join " "
  end
```

1. 自動的にテストが走り、合格する

## 4th red

1. テストを書く

```ruby:spec/models/contact_spec.rb
  describe "#by_letter" do
    it "returns a sorted array of results that match" do
      smith = Contact.create(
          firstname: 'John', lastname: 'Smith', email: 'jsmith@example.com'
      )
      jones = Contact.create(
          firstname: 'Tim', lastname: 'Jones', email: 'tjones@example.com'
      )
      johnson = Contact.create(
          firstname: 'John', lastname: 'Johnson', email: 'jjohnson@example.com'
      )

      expect(Contact.by_letter("J")).to eq [johnson, jones]
    end
  end
```

1. 自動的にテストが走り、失敗する

1. コードを書く

```ruby:app/models/contact.rb
  def self.by_letter(letter)
    where("lastname LIKE ?", "#{letter}%").order(:lastname)
  end
```

1. 自動的にテストが走り、合格する

## リファクタリング

`before`と`context`を使ってテストコードを整理する。

```ruby:spec/models/contact_spec.rb
  describe "filter last name by letter" do
    before :each do
      @smith = Contact.create(
          firstname: 'John', lastname: 'Smith', email: 'jsmith@example.com'
      )
      @jones = Contact.create(
          firstname: 'Tim', lastname: 'Jones', email: 'tjones@example.com'
      )
      @johnson = Contact.create(
          firstname: 'John', lastname: 'Johnson', email: 'jjohnson@example.com'
      )
    end

    context "matching letters" do
      it "returns a sorted array of results that match" do
        expect(Contact.by_letter("J")).to eq [@johnson, @jones]
      end
    end

    context "non-matching letters" do
      it "returns a sorted array of results that match" do
        expect(Contact.by_letter("J")).to_not include smith
      end
    end
  end
```

おしまい。

-----
ブログやってます：[PAPA-tronix !](http://weed.cocolog-nifty.com/wzero3es/)