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 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 ...
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