Một số lưu ý khi viết RSpec

1. Viết miêu tả cho hàm

Nội dung miêu tả RSpec phải được viết rõ ràng. Ví dụ như, nên sử dụng . (hoặc ::) khi đề cập đến tên class method# khi đề cập đến tên instance method.

# BAD

describe 'the authenticate method for User' do
describe 'if the user is an admin' do
# GOOD

describe '.authenticate' do
describe '#admin?' do

2. Sử dụng context

context là một phương pháp mạnh mẽ giúp cho rspec bạn viết rõ ràng cũng như được tổ chức tốt hơn. Khi miêu tả cho context, nên bắt đầu với whenwith.

# BAD

it 'has 200 status code if logged in' do
  expect(response).to respond_with 200
end

it 'has 401 status code if not logged in' do
  expect(response).to respond_with 401
end
# GOOD

context 'when logged in' do
  it { is_expected.to respond_with 200 }
end

context 'when logged out' do
  it { is_expected.to respond_with 401 }
end

3. Miêu tả phải thật đơn giản

40 là số ký tự được viết trong miêu tả, nếu bạn viết quá, nó nên được chỉnh sửa lại, hoặc dùng context.

# BAD

it 'has 422 status code if an unexpected params will be added' do
# GOOD

context 'when not valid' do
  it { is_expected.to respond_with 422 }
end

4. Viết test các kỳ vọng đơn

Việc viết test các kỳ vọng đơn giúp phát hiện lỗi ngay khi nó xảy ra.

# GOOD (ISOLATED)

it { is_expected.to respond_with_content_type(:json) }
it { is_expected.to assign_to(:resource) }
# GOOD (NOT ISOLATED)

it 'creates a resource' do
  expect(response).to respond_with_content_type(:json)
  expect(response).to assign_to(:resource)
end

5. Viết test cho tất cả các trường hợp có thể xảy ra

Viết test là tốt, nhưng nếu nó không bao phủ được tất cả các trường hợp xảy ra thì nó sẽ không còn tốt nữa.

# CODE (DESTROY ACTION)
before_filter :find_owned_resources
before_filter :find_resource

def destroy
  render 'show'
  @consumption.destroy
end
# BAD

it 'shows the resource'
# GOOD

describe '#destroy' do

  context 'when resource is found' do
    it 'responds with 200'
    it 'shows the resource'
  end

  context 'when resource is not found' do
    it 'responds with 404'
  end

  context 'when resource is not owned' do
    it 'responds with 404'
  end
end

6. Sử dụng Expect

Ở các project hiện đại, expect luôn được sử dụng.

# BAD

it 'creates a resource' do
  response.should respond_with_content_type(:json)
end
# GOOD

it 'creates a resource' do
  expect(response).to respond_with_content_type(:json)
end

Nếu viết kỳ vọng hoặc mong muốn trên 1 dòng, bạn nên sử dụng is_expected.to.

# BAD

context 'when not valid' do
  it { should respond_with 422 }
end
# GOOD

context 'when not valid' do
  it { is_expected.to respond_with 422 }
end

7. Sử dụng subject

Nếu bạn có một vài test case dùng chung và có phần liên quan đến nhau, nên sử dụng subject{} để DRY chúng nhé.

# BAD

it { expect(assigns('message')).to match /it was born in Belville/ }
# GOOD

subject { assigns('message') }
it { is_expected.to match /it was born in Billville/ }

RSPEC có khả năng sử dụng tên đã được đặt ở subject

# GOOD

subject(:hero) { Hero.first }
it "carries a sword" do
  expect(hero.equipment).to include "sword"
end

8. Sử dụng let và let!

Khi bạn muốn gán 1 biến thay vì sử dụng before để tạo biến, hãy sử dụng let. let tạo biến, và sau khi được sử dụng trong test case nó sẽ bị cache giá trị cho đến khi test case đó hoàn tất.

# BAD

describe '#type_id' do
  before { @resource = FactoryGirl.create :device }
  before { @type     = Type.find @resource.type_id }

  it 'sets the type_id field' do
    expect(@resource.type_id).to equal(@type.id)
  end
end
# GOOD

describe '#type_id' do
  let(:resource) { FactoryGirl.create :device }
  let(:type)     { Type.find resource.type_id }

  it 'sets the type_id field' do
    expect(resource.type_id).to equal(type.id)
  end
end
# GOOD

context 'when updates a not existing property value' do
  let(:properties) { { id: Settings.resource_id, value: 'on'} }

  def update
    resource.properties = properties
  end

  it 'raises a not found error' do
    expect { update }.to raise_error Mongoid::Errors::DocumentNotFound
  end
end

Sử dụng let! nếu bạn muốn nó được định nghĩa khi block được định nghĩa.

# GOOD

# this:
let(:foo) { Foo.new }

# is very nearly equivalent to this:
def foo
  @foo ||= Foo.new
end

9. Sử dụng mock

Sử dụng mock giúp cho việc viết test của bạn nhanh hơn, nhưng nó rất khó sử dụng. Bạn nên thực sự hiểu về nó để sử dụng tốt.

# GOOD

# simulate a not found resource
context "when not found" do
  before { allow(Resource).to receive(:where).with(created_from: params[:id]).and_return(false) }
  it { is_expected.to respond_with 404 }
end

10. Hãy tạo riêng data nếu bạn cần

Việc này giúp cho hệ thống không phải load quá nhiều dữ liệu hơn mực bạn cần.

# GOOD

describe "User" do
  describe ".top" do
    before { FactoryGirl.create_list(:user, 3) }
    it { expect(User.top(2)).to have(2).item }
  end
end

11. Sử dụng factories

Không nên khai báo dữ liệu bằng cách cố định, thay vào đó hãy sử dụng factories.

# BAD

user = User.create(
  name: 'Genoveffa',
  surname: 'Piccolina',
  city: 'Billyville',
  birth: '17 Agoust 1982',
  active: true
)
# GOOD

user = FactoryGirl.create :user

12. Sử dụng các móc nối của RSpec

Giúp cho việc viết test trở nên đồng bộ và dễ đọc và bào trì hơn. Tham khảo các móc nối tại đây.

# BAD

lambda { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound
# GOOD

expect { model.save! }.to raise_error Mongoid::Errors::DocumentNotFound

13. Shared Examples

shared example giúp cho code trở nên DRY hơn. Giống như ví dụ trên(subject), nó tạo và cho phép sử dụng lại dữ liệu. Thường được sử dụng cho việc viết test controller.

# BAD

describe 'GET /devices' do
  let!(:resource) { FactoryGirl.create :device, created_from: user.id }
  let(:uri) { '/devices' }

  context 'when shows all resources' do
    let!(:not_owned) { FactoryGirl.create factory }

    it 'shows all owned resources' do
      page.driver.get uri
      expect(page.status_code).to be(200)
      contains_owned_resource resource
      does_not_contain_resource not_owned
    end
  end

  describe '?start=:uri' do
    it 'shows the next page' do
      page.driver.get uri, start: resource.uri
      expect(page.status_code).to be(200)
      contains_resource resources.first
      expect(page).to_not have_content resource.id.to_s
    end
  end
end
# GOOD

describe 'GET /devices' do

  let!(:resource) { FactoryGirl.create :device, created_from: user.id }
  let(:uri)       { '/devices' }

  it_behaves_like 'a listable resource'
  it_behaves_like 'a paginable resource'
  it_behaves_like 'a searchable resource'
  it_behaves_like 'a filterable list'
end

Kết luận

RSpec thì coder ruby nào cũng phải viết, dù cho việc viết test còn mất nhiều thời gian hơn cả code. Hi vọng bài viết của mình hữu ích với các coder. (seeyou).

Link bài viết: http://www.betterspecs.org