Phương pháp tốt nhất để cài đặt thêm một chức năng mới có lẽ là BDD. Thông thường thì hay sử dụng RSpec, bắt đầu bằng việc viết test chức năng ở mức cao, sau đó điều chỉnh việc cài đặt (codding) chức năng theo đặc tả test. Đầu tiên sẽ viết các đặc tả của view liên quan đến chức năng, rồi cài đặt view liên quan dựa trên đặc tả này. Tiếp theo tạo đặc tả của controller để trao những data cần thiết cho các view ở trên, rồi cài đặt controler dựa trên các đặc tả này. Cuối cùng là viết các đặc tả cho model và cài đặt các model này.
- Đối với 1 example thì chỉ nên kỳ vọng 1 kết quả đạt được.
# Cách viết không tốt
describe ArticlesController do
#...
describe "GET new" do
before {get :new}
it do
assigns[:article].should be_a_new Article
response.should render_template :new
end
end
# ...
end
# Cách viết tốt
describe ArticlesController do
#...
describe "GET new" do
before {get :new}
subject {response}
it {should render_template :new}
end
end
-
Có thể sử dụng
describe
vàcontext
tự do khi cần thiết.- Sử dụng
describe
để nhóm theo class, module, method (hay theo action của controller). Trong trường hợp view thì không cần tuân theo quy tắc này. - Trong trường hợp example gắn liền với một task nào đấy thì tạo describe cho task ấy.
- Sử dụng
context
để nhóm các điều kiện của example.
- Sử dụng
-
Đặt tên block của
describe
như sau- Viết giải thích cho trường hợp không là method.
- Trong trường hợp đối với instance method thì gắn thêm "#" như "#method"
- Trong trường hợp đối với class method thì gắn thêm "." như ".method"
class Article def summary #... end def self.latest #... end end # the spec... describe Article do describe "#summary" do #... end describe ".latest" do #... end end
-
Sử dụng factory_girl khi cần tạo các object phục vụ cho test.
FactoryGirl.define do factory :subscription do sequence(:email) { |n| "subscriber#{n}@test.tld" } confirmation_token { Subscription.generate_confirmation_token } confirmed false end end
-
Sử dụng mock hoặc stub khi cần thiết.
-
Mocks là kỳ vọng một phương thức được gọi đến sẽ trả về một hay nhiều giá trị nào đó, trong khi
-
Stubs chỉ quan tâm đến trạng thái trả về của một đối tượng khi nhận một message nào đó.
-
Viết rspec cho đoạn code sau
class DataProcessor Error = Class.new(StandardError) def process(data, validator) raise Error unless validator.valid?(data) # simple logic to show the idea "#{data} processed" end end class Validator def valid?(data) true end end # Dùng Mocks require 'spec_helper' describe DataProcessor do let(:processor) { described_class.new } context 'with valid data' do it 'adds processed to data' do validator = double(:validator, valid?: true) expect(processor.process('foo', validator)).to eq('foo processed') end end context 'with invalid data' do it 'raises Error' do validator = double(:validator, valid?: false) expect { processor.process('foo', validator) }.to raise_error(DataProcessor::Error) end end end # Dùng Stubs require 'spec_helper' describe DataProcessor do let(:processor) { described_class.new } context 'with valid data' do it 'adds processed to data' do # it works because true is default value for Validator expect(processor.process('foo')).to eq('foo processed') end end context 'with invalid data' do it 'raises Error' do allow_any_instance_of(Validator).to receive(:valid?).and_return(false) expect { processor.process('foo') }.to raise_error(DataProcessor::Error) end end it 'calls validator.valid?' do expect_any_instance_of(Validator).to receive(:valid?).with('foo').and_return(true) processor.process('foo') end end
-
-
Khi tạo mock của model thì dùng method
as_null_object
. Khi chúng ta dùng method này thì có thể chỉ xuất ra những message kỳ vọng, tất cả các message khác sẽ được bỏ qua.# mock một model article = mock_model(Article).as_null_object # stub một method Article.stub(:find).with(article.id).and_return(article)
-
Khi tạo data trong example, sử dụng
let
thay chobefore(:each)
để lazy evaluation.# sứ dụng cách này let(:article) {FactoryGirl.create :article} # thay cho cách này before(:each) {@article = FactoryGirl.create :article}
-
Một khi sử dụng
should
thì nhất định phải cósubject
describe Article do subject {FactoryGirl.create :article} it {should be_published} end
-
Không truyền chuỗi ký tự vào tham số của
it
Lý do Thay vì dùng từ ngữ để giải thích nên viết spec để có thể tự giải thích nội dung. -
Không sử dụng
specify
.# không tốt describe Array do describe "with 3 items" do let(:arr) { [1, 2, 3] } specify { arr.should_not be_empty } specify { arr.count.should eq(3) } end end # tốt describe Array do describe "with 3 items" do let(:arr) { [1, 2, 3] } it { arr.should_not be_empty } it { arr.count.should eq(3) } end end
-
Một khi có thể sử dụng
its
thì nhất định phải dùng.# Không tốt describe Article do subject {FactoryGirl.create :article} it {creation_date.should eq Date.today} end # Tốt describe Article do subject {FactoryGirl.create :article} its(:creation_date) {should eq Date.today} end
-
Subjects trong Rspec
Ví dụ chúng ta cần viết một chương trình đơn giản cho những vận động viên chạy bộ, họ cần ghi lại dữ liệu những lần chạy và tổng hợp dữ liệu hàng tuần. Những thông tin cơ bản của một lần chạy bao gồm khoảng cách chạy, thời gian chạy và khoảng thời gian chạy hết quãng đường đó.
Từ bài toán đó chúng ta hiểu rằng phải xây dựng một class Run gồm 3 thuộc tính và có thể miêu tả đơn giản như sau:
describe Run do describe "attributes" do subject do Run.new(:duration => 32, :distance => 5.2, :timestamp => "2018-10-10 20:30") end it { is_expected.to respond_to(:duration) } it { is_expected.to respond_to(:distance) } it { is_expected.to respond_to(:timestamp) } end end
-
Sử dụng
shared_examples
trong trường hợp muốn nhóm spec được chia sẻ tại nhiều test.# Không tốt describe Array do subject {Array.new [7, 2, 4]} context "initialized with 3 items" do its(:size) {should eq 3 } end end describe Set do subject {Set.new [7, 2, 4]} context "initialized with 3 items" do its(:size) {should eq 3} end end # Tốt shared_examples "a collection" do subject {described_class.new([7, 2, 4])} context "initialized with 3 items" do its(:size) {should eq 3} end end describe Array do it_behaves_like "a collection" end describe Set do it_behaves_like "a collection" end
-
Không mock chính bản thân mình trong spec model của mình.
-
Sử dụng factory_girl khi tạo object không mock.
-
Có thể mock các model khác, hoặc các object con.
-
Tạo example để kiểm tra valid của model được factory.
describe Article do subject {FactoryGirl :article} it {should be_valid} end
-
Không sử dụng
be_valid
để kiểm tra việc validation thất bại hay không. Khi kiểm tra validation thì để chỉ rõ lỗi xảy ra ở thuộc tính nào nên dùng methodhave(x).errors_on
.# Không tốt describe "#title" do subject {FactoryGirl.create :article} before {subject.title = nil} it {should_not be_valid} end # Tốt describe "#title" do subject {FactoryGirl.create :article} before {subject.title = nil} it {should have(1).error_on(:title)} end
-
Thêm
describe
riêng biệt cho các thuộc tính cần validation. -
Khi mà test xem thuộc tính nào đấy của model của đảm bảo tính duy nhất hay không thì tên của đối tượng khác đặt là
another_object
.describe Article do describe '#title' do subject {FactoryGirl.build :article} before {@another_article = FactroyGirl.create :article} it {should have(1).error_on(:title)} end end
-
Đồng nhất cấu trúc thư mục spec của view
spec/views
với thư mụcapp/views
. Ví dụ, các file spec của view trong thư mụcapp/views/users
thì đặt trong thư mục tương ứng làspec/views/users
. -
Về quy tắc đặt tên spec của view thì gắn thêm
_spec.rb
vào sau tên view. Ví dụ, spec tương ứng của_form.html.haml
là_form.html.haml_spec.rb
. -
Trong file
spec_helper.rb
chỉ viết những thứ cần thiết cho những spec khác. -
Đối với block describle ngoài cùng thì chỉ định path tới file view đã bỏ đi phần
app/views
. Cái này không phải là chỉ định tham số mà được sử dụng khi methodrender
được gọi.# spec/views/articles/new.html.haml_spec.rb require "spec_helper" describe "articles/new.html.haml" do # ... end
-
Đối với các model trong spec của view thường thì dùng mock. Vài trò của view thì chỉ là để hiện thị.
-
Sử dụng method
assign
để thiết lập các biến instance sử dụng trong view được thiết lập trong controller.# spec/views/articles/edit.html.haml_spec.rb describe "articles/edit.html.haml" do subject {rendered} let(:article) {mock_model(Article).as_new_record.as_null_object} before do assign :article, article render end it do should have_selector "form", method: "post", action: articles_path do |form| form.should have_selector "input", type: "submit" end end
-
Không kết hợp nội dung khẳng định của Capybara với
should_not
, mà nên dùngshould
với nội dung phủ định.# Không tốt page.should_not have_selector "input", type: "submit" page.should_not have_xpath "tr" # Tốt page.should have_no_selector "input", type: "submit" page.should have_no_xpath "tr"
-
Khi sử dụng helper method trong spec của view thì phải dùng stub. Stub helper method trên đối tượng
template
.# app/helpers/articles_helper.rb class ArticlesHelper def formatted_date date # ... end end # app/views/articles/show.html.haml = "Published at: #{formatted_date @article.published_at}" # spec/views/articles/show.html.haml_spec.rb describe "articles/show.html.haml" do subject {rendered} before do article = mock_model Article, published_at: Date.new(2012, 01, 01) assign :article, article template.stub(:formatted_date).with(article.published_at).and_return("01.01.2012") render end it {should have_content "Published at: 01.01.2012"} end
-
Chia spec của helper và spec của view ra, đặt spec của helper trong
spec/helpers
.
-
Trong spec của model nếu cần instance của class model thì dùng mock. Định nghĩa các method của model bằng stub. Nhằm không để kết quả chạy spec của controller ảnh hưởng đến việc cài đặt (implement) model.
-
Controller nên có trách nhiệm, chỉ test những behavior dưới đây.
- Những method chỉ đinh có được thực hiện không
- Những data, biến instance được trả về từ action có được assign không
- Kết quả của action là render chính xác template không, hay redirect chính xác hay không
# Example of a commonly used controller spec # spec/controllers/articles_controller_spec.rb # We are interested only in the actions the controller should perform # So we are mocking the model creation and stubbing its methods # And we concentrate only on the things the controller should do describe ArticlesController do # The model will be used in the specs for all methods of the controller let(:article) {mock_model Article} describe "POST create" do before {Article.stub(:new).and_return(article)} it do expect(Article).to receive(:new).with(title: "The New Article Title").and_return article post :create, message: {title: "The New Article Title"} end it do expect(article).to receive(:save) post :create end it do article.stub(:save) post :create expect(response).to redirect_to(action: :index) end end end
-
Khi mà behavior của action thay đổi thay đổi tuỳ theo params nhận được thì sử dụng context.
# A classic example for use of contexts in a controller spec is creation or update when the object saves successfully or not.
describe ArticlesController do
let(:article) {mock_model Article}
describe "POST create" do
before {Article.stub(:new).and_return(article)}
it do
expect(Article).to receive(:new).with(title: "The New Article Title").and_return(article)
post :create, article: {title: "The New Article Title"}
end
it do
expect(article).to receive :save
post :create
end
context "when the article saves successfully" do
before do
article.stub(:save).and_return(true)
post :create
end
it {expect(flash[:notice]).to eq("The article was saved successfully.")}
it {expect(response).to redirect_to(action: "index")}
end
context "when the article fails to save" do
before do
article.stub(:save).and_return(false)
post :create
end
it {expect(assigns[:article]).to be article}
it {expect(response).to render_template("new")}
end
end
end
-
Bên trong spec của mailer thì tất cả model đều mock. Mailer không phụ thuộc vào model.
-
Trong spec của mailer tiến hành kiểm tra những điều sau.
- Tiêu đề chính xác hay không
- Địa chỉ mail của người nhận có chính xác không
- Thiết lập địa chỉ mail người gửi chính xác không
- Mail có chứa nội dung chính xác không
describe SubscriberMailer do let(:subscriber) {mock_model(Subscription, email: "johndoe@test.com", name: "John Doe")} describe "successful registration email" do subject {SubscriptionMailer.successful_registration_email(subscriber)} its(:subject) {should eq "Successful Registration!"} its(:from) {should eq ["info@your_site.com"]} its(:to) {should eq [subscriber.email]} its("body.encoded") {should match(subscriber.name)} end end