Một số lưu ý khi viết RSpec
Bài đăng này đã không được cập nhật trong 6 năm
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
và #
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 when
và with
.
# 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
All rights reserved